Skip to content

fix(chat): /compact reads "Done" while it runs, shows again after /clear, and reopened images jump #2548

Closed

Description

@jpcarranza94

What happened

Three small faults in Chat. Two are about command chips. One is about Markdown images.

  1. A Claude /compact chip says "Done · transcript" too early. I sent /compact from the Chat composer to Claude Code. Two seconds later its bubble read "Done · transcript", with "Working…" under it. The compaction ran for 49 more seconds. The "Conversation compacted" divider came only then.
  2. After /clear, the old /compact shows in the new chat. I ran /compact, then /clear. The new, empty chat showed the greeting and a sent bubble "/compact · Done · transcript" from the old conversation. One prompt poll later, the bubble became "/clear · No result seen".
  3. A reopened chat redraws each Markdown image from a placeholder. An answer with a local image (![shot](/tmp/shot.png)) is first drawn with a 16:9 loading slot as wide as the lane. Each time the chat is opened again, or the row scrolls back into the list, the slot comes back. After a stat request and a new download, the row changes to the image's real height. Everything below it moves.

Expected: a /compact chip says "Done" once the conversation is compacted. A cleared chat shows no row from the conversation before it. A reopened image row has its final height in its first frame.

Cause

The live runs used Claude Code 2.1.316 (haiku) in an isolated herdr session, with Ovis at feb3ae0 in headless Chromium. Line numbers are on origin/main (53542d6).

1. /compact (server). Claude Code writes /compact as a plain user line when the command starts. The compact boundary, the summary, the <command-name> echo and the output come only when it ends. transcriptEvidence takes "a native user turn with the exact command text" as the result (server/outcome.ts:224-225). So the chip turns result-observed on the first poll after the start, and src/lib/outcomeChip.ts:39 labels it "Done · transcript".

transcript  00:57:55.560Z  type=user  "/compact"                      ← the start
poll        00:57:55.561Z  /compact accepted
poll        00:57:57.585Z  /compact result-observed transcript   ← "Done · transcript"
transcript  00:58:44.286Z  type=system compact_boundary          ← the compaction, 49 s later

server/core/status.test.ts ("shows /compact at work while it runs") already models this start and end split.

2. /clear (client). ChatView keeps the outcomes from the last 2 s prompt poll (src/components/ChatView.tsx:1688). The conversation read moves to the new history first, and the history switch (ChatView.tsx:1507) keeps the old list. In the empty history, outcomeRows (outcomeChip.ts:102-131) draws every outcome no turn records as a provisional sent row. So the old /compact stands in the new chat until the next poll. The server scopes outcomes to the agent session (server/prompt.ts:379-397), but only once herdr names the new session. That can be a poll after the conversation read.

poll        00:58:50.249Z  [/compact result-observed]
conv        00:58:51.894Z  history=…-clear-n9d turns=0           ← the new, empty chat, with /compact in it
poll        00:58:52.270Z  [/clear unknown]                      ← the row becomes "/clear · No result seen"

The recorder flagged the leftover row: shown 336 ms, 5 frames. "/clear · No result seen" is on purpose: a session change is no proof that the command completed (server/prompt.ts:379-380).

3. Images (client). MarkdownImage starts with no preview on every mount (src/components/Markdown.tsx:216) and calls resolve(src) (Markdown.tsx:234). That goes through ChatMarkdownImages.tsx:21 and markdownImagePreview to fetchFileInfo (src/lib/api.ts:843, GET /api/fs/stat). Nothing is cached, and the resolver is new on each mount (src/lib/machineContext.tsx:28). Until the stat answers and the bytes decode, ReservedImage (Markdown.tsx:179) holds the 16:9 slot (src/components/ChatView.css:454-460). The decoded size is not remembered. /api/fs/file is sent with cache-control: private, no-store (server/file-view.ts:996), so the bytes download again too.

Demo, the answer row on reopen, each change by frame:

Image Slot Stat answered Decoded
160×90 382 px at 22 ms 376 px at 182 ms 261 px at 199 ms
1440×900, no-store 382 px at 52 ms 376 px at 245 ms 1155 px at 329 ms

Each reopen sends two new /api/fs/stat requests, one for each image.

Proposed fix

One PR for 1 and 2:

  • Server: for a Claude /compact, only a compact_boundary record after the send is the result. The start line and the echo are not. The chip stays "Accepted" until the fold is written.
  • Client: on a switch to another history, ChatView hides the outcomes the chat knew from the history left behind, while the new history shows. The newest /clear, /new, /reset or /resume made the switch: it and the sends after it keep their rows. A lagging server answer then cannot draw the old rows either.

Known and open: 3 is not in that PR. The fix needs a cache of the resolved preview and of the decoded size, per PC, pane and image source. MarkdownImage then starts from the cache, and the image takes its remembered size (width and height) in the first frame, while the stat runs again to check it. This changes ReservedImage, which chat thumbnails and file previews share. It needs its own browser regression in scripts/chat-markdown-images-browser-qa.ts.

How often

  • 1: every Claude /compact sent from Chat, for the whole compaction (7 s and 49 s in two runs).
  • 2: every /compact followed by /clear, until the next prompt poll (about 0.3–2 s). Longer while herdr has not named the new session.
  • 3: every reopen of a chat with a local Markdown image, and every time such a row scrolls back into the list.

Ovis version

main (53542d6, v0.4.21). The live runs used feb3ae0. The code cited above works the same there.

How it runs

bun run server / bun run dev (development)

Server PC

macOS 26.5.2, Apple M4 (Mac mini), herdr 0.9.0

Device and browser

Headless Chromium at 1280×800. The cause is not specific to a device.

Agent and PC

Claude Code 2.1.316 (haiku), local PC

Activity

  1. added a commit that references this issue on Nov 4, 2026
    4fd51f8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions