Summary
Measured on luvus 0.14.2 (pane under luvus --session, host terminal Ghostty 1.3.1 on Linux/Hyprland): the kitty-graphics APC passthrough path — the one #324 is building — forwards small APCs inline at the correct cell, but corrupts or stalls once in-band image traffic grows past a few hundred KB. Two distinct failure modes, both reproduced with the self-contained script below.
This is real-world validation data for the #324 path: whatever currently forwards APCs needs backpressure/drain handling before wider rollout.
Bug 1 — one large write into the pane pty stalls the passthrough parser
A child writing a single contiguous ~1.4 MB burst (a complete chunked kitty transmit + delete + placement, as one terminal-cell update) stalls forwarding partway: the outer-terminal recording stops mid-APC ~19 KB into the burst. Bytes that follow are then forwarded at whatever cursor luvus has drifted to — observed at its own bottom chrome row — because a real kitty placement (a=p) anchors at forward-time cursor. Symptom: the image appears at the bottom of the screen, on top of the user's input, and persists (placements are terminal-side state the multiplexer never repaints).
Bug 2 — unpaced chunked APC streams silently drop payload segments
The same ~1.4 MB transmit written as 342 separate ≤4 KiB APC chunk writes (kitty's 4096-byte payload limit), with no delay, loses contiguous base64 runs of ~2.5-10 KB around the 350-425 KB mark (one run starts exactly at 98304 = 96 KiB, suggesting a fixed accumulation buffer). The image data after the gap is corrupt (PNG no longer decodes). Pacing each chunk write 5 ms apart forwards 1.4 MB fully intact; 2 ms per chunk still loses data.
Repro
https://gist.github.com/tobias-hui/24a090640351d8ad87b1c8b019a030d3 — self-contained bash, generates its own probe PNG, three modes: giant, chunked, paced. Run inside a luvus pane while recording the outer-terminal byte stream (run the luvus TUI under a pty recorder, e.g. script -qec "luvus --session repro" capture.bin, or equivalent). Verify by extracting every \x1b_G...;<base64>\x1b\\ APC payload from the recording, reassembling, and comparing against the transmitted base64 / decoding the PNG.
Small APCs (single-chunk transmits, a=p placements, U=1 keys) forward verbatim and correctly anchored — the failure mode is volume, not key handling.
Environment
- luvus 0.14.2, server + TUI client same host,
LUVUS_ENV=1 in pane
- Host terminal: Ghostty 1.3.1-arch2,
TERM=xterm-256color, Linux x86_64 Hyprland (Wayland)
Workaround (client side)
We pace chunk writes ≥5 ms apart and keep large transmits out of single cell writes in jcode (commit 89e0535f); noting it here in case it helps others until the passthrough handles backpressure.
Summary
Measured on luvus 0.14.2 (pane under
luvus --session, host terminal Ghostty 1.3.1 on Linux/Hyprland): the kitty-graphics APC passthrough path — the one #324 is building — forwards small APCs inline at the correct cell, but corrupts or stalls once in-band image traffic grows past a few hundred KB. Two distinct failure modes, both reproduced with the self-contained script below.This is real-world validation data for the #324 path: whatever currently forwards APCs needs backpressure/drain handling before wider rollout.
Bug 1 — one large write into the pane pty stalls the passthrough parser
A child writing a single contiguous ~1.4 MB burst (a complete chunked kitty transmit + delete + placement, as one terminal-cell update) stalls forwarding partway: the outer-terminal recording stops mid-APC ~19 KB into the burst. Bytes that follow are then forwarded at whatever cursor luvus has drifted to — observed at its own bottom chrome row — because a real kitty placement (
a=p) anchors at forward-time cursor. Symptom: the image appears at the bottom of the screen, on top of the user's input, and persists (placements are terminal-side state the multiplexer never repaints).Bug 2 — unpaced chunked APC streams silently drop payload segments
The same ~1.4 MB transmit written as 342 separate ≤4 KiB APC chunk writes (kitty's 4096-byte payload limit), with no delay, loses contiguous base64 runs of ~2.5-10 KB around the 350-425 KB mark (one run starts exactly at 98304 = 96 KiB, suggesting a fixed accumulation buffer). The image data after the gap is corrupt (PNG no longer decodes). Pacing each chunk write 5 ms apart forwards 1.4 MB fully intact; 2 ms per chunk still loses data.
Repro
https://gist.github.com/tobias-hui/24a090640351d8ad87b1c8b019a030d3 — self-contained bash, generates its own probe PNG, three modes:
giant,chunked,paced. Run inside a luvus pane while recording the outer-terminal byte stream (run the luvus TUI under a pty recorder, e.g.script -qec "luvus --session repro" capture.bin, or equivalent). Verify by extracting every\x1b_G...;<base64>\x1b\\APC payload from the recording, reassembling, and comparing against the transmitted base64 / decoding the PNG.Small APCs (single-chunk transmits,
a=pplacements,U=1keys) forward verbatim and correctly anchored — the failure mode is volume, not key handling.Environment
LUVUS_ENV=1in paneTERM=xterm-256color, Linux x86_64 Hyprland (Wayland)Workaround (client side)
We pace chunk writes ≥5 ms apart and keep large transmits out of single cell writes in jcode (commit
89e0535f); noting it here in case it helps others until the passthrough handles backpressure.