Repository navigation
Fix kitty reply leak, inline-image cleanup, cursor handling and kitty image IDs - #15
Merged
Merged
Conversation
…xt frame Kitty: every graphics command now carries q=2. Without it kitty answers each transmit/delete with an OK reply on stdin, which Ink parses as keypresses (stray `\` characters in the host's inputs; with a host that redraws on input, an endless reply -> redraw -> reply loop). Inline images (iterm2/sixels, e.g. VS Code): they live in the text cells, so the old image must be blanked when the chart unmounts or changes size. That ran as a passive effect, i.e. after the host's next Ink frame had been written, and painted black: it covered the replacement view (e.g. a table) and, with incremental rendering, nothing repainted it. It now runs as a layout-effect cleanup (during commit, before Ink writes the frame), uses default attributes, and also fires on size changes (stale parts of a larger old image stayed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
…e ids Cursor: Ink writes each frame relative to where it left the cursor, but every graphics write moved it (absolute CUP to the image, then the image advanced it) and positioned at an absolute row that assumes the frame starts at terminal row 1. Now every out-of-band write (stamp, re-stamp, blank, kitty delete) is wrapped in DECSC/DECRC, positions relative to Ink's cursor (fullscreen: on the frame's last line; otherwise the line after), and asks the terminal not to move the cursor (iTerm2 doNotMoveCursor=1, kitty C=1). Kitty ids: every chart used ids 1/2, so two charts overwrote each other and unmounting one deleted the other's image. Each chart now gets its own pair from a pid-offset counter, and deletes use d=I so the image data is freed, not just its placement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
- Bump to the newest versions >= 7 days old: path2d 0.3.4; dev: ink 7.1.1, react 19.3.0, @types/node 26.6.2, @types/react 19.3.0, tsx 4.23.15, vitest 5.0.1 (major), vite 8.3.0 (now explicit; vitest 5 takes it as a peer, the lockfile kept a vulnerable 7.3.1). - Override tsup's esbuild to ^0.28.2 (tsup pins ^0.27; no patched 0.27). - pnpm audit: 15 -> 0 (all were dev-only: vitest/vite/postcss/esbuild/picomatch). - Dependabot: weekly grouped minor/patch npm updates with a 7-day cooldown, monthly grouped GitHub Actions updates. Supersedes #14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PzJHgXssByMwBXH1PGbXCL
This was referenced Sep 29, 2026
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.
Problems (found in glassnode-terminal)
Kitty: stray
\\characters typed into inputs. Kitty graphics commands didn't setq=2, so kitty replied to every image transmit and delete with an OK message (ESC _G…;OK ESC \\) on the app's input. Ink parsed the reply as keypresses, and the trailing\\landed in the host app's search box. If the host redraws the chart after input, this becomes an endless reply → redraw → reply loop.Inline images (iTerm2 protocol, used for VS Code): black box after switching views. Inline images live in the terminal's text cells. On unmount the image was cleared in a passive effect, which runs after the host's next Ink frame is written, and it painted the cells black. That covered the replacement view (for example a table), and with Ink's incremental rendering nothing ever repainted those lines.
Inline images: leftovers after a size change. When a new, smaller image replaced a larger one, the parts of the old image outside the new one stayed on screen (for example a duplicated row of x-axis labels).
Graphics writes moved Ink's cursor (found in a full-library review). Ink writes each frame relative to where it left the cursor, but every graphics write jumped to an absolute row that assumes the frame starts at terminal row 1, and the image then moved the cursor further. In a frame that doesn't start at row 1, the image lands in the wrong place and Ink's next frame is written from the wrong line. This may explain a missing header row seen in VS Code.
Kitty image IDs were shared. Every chart used IDs 1 and 2, so two charts overwrote each other and unmounting one deleted the other's image. Deletes used
d=i, which removes the image from the screen but keeps its data in terminal memory.Fix
format.ts:q=2added tokittyDeleteand injected bykittyTagImage, so every kitty command is quiet.InkUPlot.tsx: inline images are cleared in a layout-effect cleanup, which runs during React's commit and before Ink writes the host's next frame, so that frame lands on top. The cleanup uses default attributes (no black) and also runs when the chart changes size, not only on unmount. The kitty unmount delete also moved to a layout effect. I corrected the comment claiming VS Code keeps inline images through later text; it doesn't.ESC 7…ESC 8) and positioned relative to Ink's cursor: on the frame's last line when the frame fills the terminal, otherwise on the line after it. The terminal is also asked not to move the cursor (iTerm2doNotMoveCursor=1, which xterm.js safely ignores; kittyC=1).d=I, which also frees the image data.Dependencies and security (added before release so this ships as one version):
path2d0.3.4ink7.1.1,react19.3.0,@types/node26.6.2,@types/react19.3.0,tsx4.23.15,vitest5.0.1 (major version) andvite8.3.0viteis now listed directly. vitest 5 takes it as a peer dependency, and the lockfile had kept a vulnerable 7.3.1.tsup'sesbuildto 0.28.2.tsuppins^0.27, and no 0.27 release is patched.pnpm audit: 15 findings → 0. All 15 were development-only (vitest, vite, postcss, esbuild, picomatch); none of the runtime dependencies were affected..github/dependabot.yml: weekly grouped minor and patch updates with a 7-day cooldown, and monthly grouped GitHub Actions updates. Supersedes chore(deps-dev): bump vitest from 4.1.10 to 4.1.11 #14.Verification
test/inline-cleanup.test.tsx: on unmount the old cells are blanked with default attributes before the replacement frame; on a size change the whole old area (8 rows) is blanked before the new image is drawn. Both tests fail on the previous code. The kitty tests informat.test.tsnow requireq=2.pnpm vitest run: 81 tests pass.tsupbuild andtsc --noEmitpass.test/cursor-and-ids.test.tsx, for both iTerm2 and kitty: every write is wrapped in cursor save/restore and uses no absolute positioning, and a chart below a header line is placed 4 lines up at column 1. A second test runs two kitty charts: they get different IDs, and unmounting one doesn't delete the other's image. These tests fail on the previous commit. 85 tests pass.q=2. Every transmit hasC=1, the chart uses its own pair of IDs, deletes used=I, and every graphics write is wrapped inESC 7…ESC 8.pnpm auditreports no known vulnerabilities, the build (now on esbuild 0.28) andtsc --noEmitpass, and 85 tests pass. The new build in glassnode-terminal passes its 32 tests and still places the chart correctly in the emulator.