Skip to content

Charts in VS Code/kitty: stay on screen, fit their pane; table fits its pane (ink-uplot 0.2.14) - #13

Merged
planadecu merged 6 commits into
mainfrom
fix/vscode-chart-persistence
Sep 29, 2026
Merged

planadecu merged 6 commits into
mainfrom
fix/vscode-chart-persistence

Conversation

@planadecu

@planadecu planadecu commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Problem

In the VS Code terminal the chart appeared and then disappeared almost immediately.

Root cause

In VS Code, ink-uplot draws the chart as an iTerm2 inline image written directly to stdout, outside Ink's frame. By default Ink repaints the whole frame whenever any text changes: it erases every line and writes it again. The live price ticker changes the text about twice a second, and each repaint wiped the image. ink-uplot redraws inline images on a 120 ms timer in real iTerm2, but deliberately skips that in VS Code, on the assumption that VS Code keeps images through repaints. It doesn't.

I measured this by running the app in a 30×115 pseudo-terminal: 720 line erases in 12 s of idle after the chart was drawn, which is the whole frame about twice a second.

Fix

  • incrementalRendering: true (src/cli.tsx): Ink rewrites only the lines that changed. Ticker updates now rewrite only row 0 (the header); the chart starts at row 7.

  • useRedrawAfterInput (src/hooks/useRedrawAfterInput.ts): keypresses can still repaint the rows under the chart. For example, Tab recolours the pane borders on every row. So the chart is redrawn once, 150 ms after the last keypress, rather than on a timer. A patched ink-uplot with the 120 ms timer enabled for VS Code kept the chart but flickered several times a second.

  • Chart sizing (src/components/DataView.tsx): once the chart stayed on screen, it turned out to be drawn too big. It was given height = viewportSize (terminal rows − 8, a number sized for the list panes) and width = termWidth − 86 (a guess at the pane layout). The data pane also holds the parameter bar, which wraps to 2 lines when the pane is narrow, and the metric line. The inline image is drawn at exactly the requested size, so it spilled about 3 rows over the pane border and the status bar. DataView now measures a flexible box that fills the rest of the pane (Ink's measureElement) and gives the chart exactly that size.

  • ink-uplot 0.2.15 (Fix kitty reply leak, inline-image cleanup, cursor handling and kitty image IDs ink-uplot#15 and #31):

    • kitty image commands are quiet (q=2), so the terminal's replies no longer leak into stdin as keypresses. That had typed \ into search and, combined with the redraw after input, looped.
    • inline images are blanked before Ink's next frame with normal blank cells. That fixes the black box after switching to table view and the leftover axis labels.
    • the cursor is saved and restored around every graphics write, and images are placed relative to Ink's cursor.
    • each chart gets its own kitty image IDs.
    • resizing no longer damages the screen (found in review of this PR): after a resize it drew a stale image at the old size, blanked old coordinates, and blanked rows below Ink's cursor line onto the status bar. Images are now also placed only after Ink has written the frame they were positioned for.
  • Table fits its pane (Table view overflows the data pane and breaks the layout #14): the table showed viewportSize rows (terminal rows − 8, sized for the list panes) and overflowed the data pane over its border and the status bar. DataView now measures one body box for both table and chart (flex basis 0, clipped) and reports its height; App uses that as the table's page size (minus the header row).

  • prepublishOnly clears dist/ first (prepublishOnly builds on top of an existing dist/ (stale files can be published) #15), so stale compiled files can't be published.

  • Review fixes:

    • The table's selected row stays visible when the page shrinks (useListNavigation re-clamps the scroll offset on viewport changes).
    • DataView reports a 0-height body too.
    • The redraw after keypresses only runs for inline-image charts (iTerm2 protocol, sixels) in chart view, so kitty and text charts don't re-send the image for nothing.

Closes #14, closes #15, closes #18.

Verification

  • New test/hooks/useRedrawAfterInput.spec.tsx: the counter bumps once after a burst of keys and never while idle. I confirmed it fails when the bump is removed. All 30 tests pass and the build passes.

  • Pseudo-terminal harness with TERM_PROGRAM=vscode, 30×115, API mode:

    • Idle for 4 s: only the ticker row is written and the chart is never redrawn, so there's no flicker.
    • After each Tab: the chart is redrawn exactly once, and no chart row is rewritten after the final redraw.
  • To confirm by eye: open a chart in the VS Code terminal and watch it for about 10 s while prices tick, then press Tab a few times.

  • New test/components/DataView.spec.tsx: the chart gets exactly the pane's inner space (80×20 pane → 78×16) and shrinks when the parameter bar wraps. Both tests failed before the fix. 32 tests pass.

  • Terminal emulator (pyte) at 30×115, 40×160 and 24×90: the image lies entirely inside the data pane at every size. Before this fix, at 30×115 the image covered rows 7–28 while the pane's inside ends at row 25.

  • Table view in the emulator: at 30×115 and 40×160 the table fits the data pane (18 and 30 rows), the pane border is intact and the status bar is correct. Before, the rows ran over the border and the status bar. At 24×90 the data pane is clean too, but the list panes still overflow by one row because the status bar wraps to 2 lines; that's issue Narrow terminals: fixed-width asset/metric panes squeeze the data pane #16.

  • New DataView table tests: the reported body height is 16 in an 80×20 pane, and a 50-row table never draws past the border or squeezes out the parameter bar and metric line. Both fail without the fix. 34 tests pass.

  • prepublishOnly: a planted dist/stale-leftover.js is removed and dist/ is rebuilt.

  • With ink-uplot 0.2.14 from npm, the chart is still placed at row 7 inside the data pane. While idle there are no erases and no redraws; after Tab, one redraw.

  • Two independent reviews (Opus 5.5): one of this PR and one of Fix screen damage when the terminal is resized with an inline-image chart ink-uplot#31. All findings are addressed. The resize regression was reproduced, then fixed and re-verified.

  • Final check with ink-uplot 0.2.15 from npm, using an image-aware emulator (inline images painted as cells that text writes and erases remove, like xterm.js), in VS Code and iTerm2 modes:

    • resizes to 110×30, 160×24 and 200×40
    • Tab, table and back, and typing in search

    Every case has 0 stale image cells, intact pane borders and status bar, and a full-size chart (none in table view). 38 tests pass and pnpm audit is clean.

Follow-up (not in this PR)

planadecu and others added 2 commits September 29, 2026 09:57
In VS Code, ink-uplot draws the chart as an iTerm2 inline image written straight
to stdout. Ink repainted the whole frame (erase + rewrite every line) on each
price-ticker update, erasing the image; ink-uplot doesn't re-stamp in VS Code.

- Render with Ink's incrementalRendering: only changed lines are rewritten, so
  ticker updates touch the header row only and the chart survives.
- useRedrawAfterInput: a keypress (pane focus, navigation, search) can still
  repaint the chart rows, so re-draw the chart once, 150ms after the last key,
  instead of on a timer (which flickers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
The chart was given height = viewportSize (terminal rows - 8, sized for the
list panes) and width = termWidth - 86 (a guess at the pane layout). The data
pane also holds the param bar (wraps to 2 lines when narrow) and the metric
line, so an inline-image chart, drawn at exactly the requested size, spilled
~3 rows over the pane border and status bar.

DataView now measures a flex-grow box filling the rest of the pane
(measureElement) and gives the chart exactly that size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
@planadecu planadecu changed the title Keep inline-image charts on screen in VS Code Keep inline-image charts on screen and inside their pane (VS Code) Sep 29, 2026
…e publish

- ink-uplot 0.2.14: quiet kitty commands (no replies leaking into stdin as
  keypresses), inline images blanked before Ink's next frame, cursor saved and
  restored around graphics writes, per-chart kitty image ids.
- Table view (#14): the table showed viewportSize rows (terminal rows - 8, sized
  for the list panes) and overflowed the data pane. DataView now measures one
  body box for both table and chart (flex-basis 0, clipped) and reports its
  height; App uses it as the table's page size.
- prepublishOnly (#15) removes dist/ before building, so stale compiled files
  can't be published.

Closes #14, closes #15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
@planadecu planadecu changed the title Keep inline-image charts on screen and inside their pane (VS Code) Charts in VS Code/kitty: stay on screen, fit their pane; table fits its pane (ink-uplot 0.2.14) Sep 29, 2026
planadecu and others added 3 commits September 29, 2026 22:47
Measuring takes a couple of render passes; a fixed 50ms wait was too short on
the Node 22 CI runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
…te redraw

- useListNavigation: when the viewport size changes, clamp the scroll offset so
  the selection stays visible and no empty rows show past the end (e.g. the
  selected table row scrolled out of view after the pane got shorter).
- DataView reports its measured body height even when it is 0; App uses the
  measurement once one exists (page size >= 1) instead of the estimate.
- useRedrawAfterInput only runs for inline-image charts (iTerm2 protocol,
  sixels) in chart view: kitty's graphics layer and text charts survive Ink
  repaints, so bumping there re-sent the whole image for nothing. Closes #18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
Fixes the review finding on this PR: resizing the terminal with an inline-image
chart on screen (VS Code / iTerm2) left rows wiped — ink-uplot drew a stale
old-size image after the resize, blanked it at stale coordinates, and blanked
rows below Ink's cursor line onto the status bar. 0.2.15 also places images
only after Ink has flushed the frame they were positioned for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
@planadecu
planadecu merged commit 3efda85 into main Sep 29, 2026
3 checks passed
@planadecu
planadecu deleted the fix/vscode-chart-persistence branch September 29, 2026 23:33
@planadecu planadecu mentioned this pull request Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant