Charts in VS Code/kitty: stay on screen, fit their pane; table fits its pane (ink-uplot 0.2.14) - #13
Merged
Merged
Conversation
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
This was referenced 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
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
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In the VS Code terminal the chart appeared and then disappeared almost immediately.
Root cause
In VS Code,
ink-uplotdraws 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-uplotredraws 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 patchedink-uplotwith 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 givenheight = viewportSize(terminal rows − 8, a number sized for the list panes) andwidth = 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.DataViewnow measures a flexible box that fills the rest of the pane (Ink'smeasureElement) and gives the chart exactly that size.ink-uplot0.2.15 (Fix kitty reply leak, inline-image cleanup, cursor handling and kitty image IDs ink-uplot#15 and #31):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.Table fits its pane (Table view overflows the data pane and breaks the layout #14): the table showed
viewportSizerows (terminal rows − 8, sized for the list panes) and overflowed the data pane over its border and the status bar.DataViewnow measures one body box for both table and chart (flex basis 0, clipped) and reports its height;Appuses that as the table's page size (minus the header row).prepublishOnlyclearsdist/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:
useListNavigationre-clamps the scroll offset on viewport changes).DataViewreports a 0-height body too.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: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
DataViewtable 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 planteddist/stale-leftover.jsis removed anddist/is rebuilt.With
ink-uplot0.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-uplot0.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: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 auditis clean.Follow-up (not in this PR)
ink-uplotresolution and remaining review items: Kitty charts are downsampled to 8x8 px per cell by chafa ink-uplot#17–#30.