Skip to content

fix(graphics): abort an unfinished kitty upload on delete, sixel or RIS - #612

Merged
vyncint merged 1 commit into
mainfrom
fix/kitty-upload-abandon
Oct 27, 2026
Merged

vyncint merged 1 commit into
mainfrom
fix/kitty-upload-abandon

Conversation

@vyncint

@vyncint vyncint commented Oct 27, 2026

Copy link
Copy Markdown
Owner

While a chunked kitty upload (m=1) was in progress, three things that cannot continue it completed it instead:

  • A sixel was built on top of it: one payload, described as a sixel but carrying the kitty s=, c=/r=, t=, i=, o=, whose data was the kitty chunk and the sixel run together — it decoded to a transparent 4x6 instead of the red it painted.
  • A delete (a=d) was taken as the upload's last chunk: kitty() counted an image, deletes() missed the teardown. The protocol: "the partial upload must be aborted".
  • Any APC, not only G, was taken as a chunk (and, with no m=1, as the last one); after RIS the next transmission completed the upload.

Fix. Only a G APC continues an upload. A delete aborts it and is then counted as a delete; a sixel or RIS abandons it. DECSTR keeps it, as kitty's soft reset does (it skips the grman_clear the hard reset runs — kitty screen.c / graphics.c, read at 2a70f21). An abandoned upload is no image; its chunks still count in bytes(), which counts traffic, not pictures, so bytes() is unchanged for every stream.

Tests (unit, emu/seq.rs, the issue's exact sequences): sixel mid-upload (plus its decode under decode), delete mid-upload, RIS vs DECSTR, a non-G APC. All five failed on main showing the issue's symptoms; restoring each piece of the old behaviour on its own turns exactly its test red. DESIGN.md's counter paragraph also said continuations "carry no control block at all" — they carry m=; corrected.

Not changed (outside the issue, noted for later): a=p/a=c or a new non-direct t= sent mid-upload, which kitty handles separately and the protocol forbids.

Closes #587

While a chunked kitty upload (`m=1`) was waiting for its last chunk,
escapes that cannot continue it completed it instead (#587).

A sixel was built on top of the upload, so it came out as one payload
described as a sixel carrying the kitty `s=`, `c=`/`r=`, `t=`, `i=`
and `o=`, which decoded to a transparent 4x6 rather than the red it
painted. A delete was read as the last chunk, its control block
ignored, so `kitty()` counted an image and `deletes()` missed the
teardown; the protocol says the partial upload must be aborted. RIS
did not reset the upload, so the next transmission completed it. Any
APC that did not open with `G` was taken as a chunk too.

Only a kitty escape continues an upload now. A delete aborts it and is
counted; a sixel or RIS abandons it; another APC leaves it alone, as
kitty ignores one. An abandoned upload is no image, but its chunks
still count in `bytes()`, which counts wire traffic. DECSTR keeps the
upload: kitty's soft reset skips the image clear its hard reset does,
so the next chunk there still completes it.

Closes #587

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 2b28e5f into main Oct 27, 2026
16 checks passed
@vyncint
vyncint deleted the fix/kitty-upload-abandon branch October 27, 2026 09:31
vyncint added a commit that referenced this pull request Oct 27, 2026
Resolves the CHANGELOG conflict with #612: both sets of entries kept.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An unfinished kitty upload is never abandoned: a sixel or a delete completes it

1 participant