Skip to content

fix(tui): show a visible marker where /rebuild inserted a context boundary - #1940

Closed
wqymi wants to merge 1 commit into
mainfrom
wq/tui-rebuild-boundary-marker
Closed

fix(tui): show a visible marker where /rebuild inserted a context boundary#1940
wqymi wants to merge 1 commit into
mainfrom
wq/tui-rebuild-boundary-marker

Conversation

@wqymi

@wqymi wqymi commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

The UX gap

After a manual /rebuild, the TUI shows nothing. The user cannot tell that a rebuild happened, and cannot tell where the context boundary is.

checkpoint.insertRebuildBoundary (src/session/checkpoint.ts:1444-1490) inserts exactly one role: "user" message whose parts are:

  • one { type: "checkpoint", coveredUpTo, ... } part
  • two-to-three { type: "text", synthetic: true } parts (the rendered context + index + actor registry)

Neither renders in the TUI:

  • PART_MAPPING in src/cli/cmd/tui/routes/session/index.tsx:1845-1849 only maps text, tool, reasoning — a checkpoint part falls through <Show when={component()}> and renders nothing.
  • UserMessage only draws its bubble when a non-synthetic text part exists (index.tsx:1513, gated at index.tsx:1615), so all the synthetic context text is hidden too.

Net effect: the whole boundary message renders as zero rows.

Compaction is locatable by comparison, because SessionCompaction.processCompaction writes a real assistant message (agent: "compaction", summary: true, src/session/compaction.ts:341-346) whose text part renders normally — you see the summary block sitting in the transcript. Rebuild has no equivalent: its summary lives inside synthetic: true user text parts, which are never displayed.

(filterCompacted, src/session/message-v2.ts:1029, breaks the context slice on either part type, so both are equally real boundaries — only one of them was visible.)

The fix

Render the checkpoint part as a one-line marker row in UserMessage, reusing the badge-row pattern the TUI already uses for cron fires and actor notifications (same box marginTop/paddingLeft={2}, same backgroundElement badge + muted trailing text):

 ⟲ context rebuilt  earlier messages summarized

Placed inline at the boundary message's position, so it marks where the rebuild happened, not just that it happened.

New i18n keys (en / zh / zht; other locales fall back to English via the base merge in context/language.tsx:18):

  • tui.session.rebuild_boundary.labelcontext rebuilt / 上下文已重建 / 上下文已重建
  • tui.session.rebuild_boundary.detailearlier messages summarized / 较早消息已摘要 / 較早訊息已摘要

No rebuild or compaction logic is touched — this is purely making an already-existing boundary visible.

Note on consistency

The web/share renderer (packages/ui) does have a dedicated divider: PART_MAPPING["compaction"]MessageDivider label={i18n.t("ui.messagePart.compaction")} (packages/ui/src/components/message-part.tsx:1325-1328), and session-turn.tsx:233 already treats a checkpoint part as a divider trigger. The TUI has no compaction divider at all — compaction is only visible there via its summary message. This PR does not add a TUI compaction divider (out of scope); the new marker follows the TUI's own inline badge-row language rather than importing the web divider.

Verification

  • bun typecheck (packages/opencode) — exit 0
  • bun test test/cli/cmd/tui/ — 68 pass / 0 fail
  • bun test test/session/checkpoint-rebuild-unify.test.ts — 3 pass / 0 fail (this already pins the invariant the marker keys off: expect(boundary.parts.some((p) => p.type === "checkpoint")).toBe(true) at line 168)
  • New test/cli/cmd/tui/rebuild-boundary-marker.test.ts pins the marker copy in en/zh/zht and asserts zh/zht are actually translated rather than silently falling back to English. There is no render/snapshot harness for TUI message parts (the existing TUI tests all cover pure helpers), so there was no compaction-marker snapshot test to mirror.

To see it live: open a TUI session with some history, run /rebuild, and the marker row appears inline at the point the boundary was inserted.

…ndary

A `/rebuild` inserts one user message whose parts are a `checkpoint` part
plus `synthetic: true` text parts. The TUI's PART_MAPPING only covers
text/tool/reasoning, and UserMessage only renders when a non-synthetic
text part exists — so the whole boundary message rendered as nothing and
the user had no way to tell that a rebuild happened, or where.

Compaction, by contrast, leaves a visible summary assistant message
behind, so the boundary is at least locatable there.

Render the checkpoint part as a one-line marker row, using the same badge
row pattern already used for cron fires and actor notifications. No
rebuild or compaction logic changes.
@wqymi

wqymi commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

Folding this into #1752 — it is the UX completion of the same /rebuild feature, not a standalone change. Commit 054775d now carries the badge row on #1752's branch, plus a test pinning the model-context semantics behind the decision to keep this render-only.

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.

1 participant