Skip to content

fix(tui): render instruction updates as compact notices - #41900

Merged
kitlangton merged 1 commit into
v2from
instructions-notice
Aug 12, 2026
Merged

fix(tui): render instruction updates as compact notices#41900
kitlangton merged 1 commit into
v2from
instructions-notice

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

What

Instruction-update notices in the TUI now always render as a compact one-liner (◈ Instructions updated: core/codemode) instead of sometimes dumping the entire model-facing update text — up to a full multi-hundred-line Code Mode catalog — into the transcript.

Before / After

Before: the TUI had two divergent paths for session.instructions.updated, and which one you saw depended on how the message reached you:

  • Live: while connected, the client-side event fold synthesized a system message with compact text Instructions updated: <source keys>. This is the small notice users are used to.
  • Projected: on session load or reconnect — which is always the path after a server restart — the projected message carries the full frozen model-facing text, and SessionNoticeMessageV2 rendered message.text verbatim. A restart-triggered Code Mode catalog update therefore appeared as a wall of text.

The verbatim render regressed in #36254 (July 10), which replaced the previous hardcoded "Instructions updated" label with props.message.text. The two paths also disagreed on whether to show a message: the projector skips text-less deltas (initial baseline, empty renders), but the live fold only skipped the initial baseline — so a live-shown notice could silently vanish on reload.

After: both paths produce the same message shape and the renderer never shows the full text:

  • The projected System message gains an optional descriptionInstructions updated: <delta keys> — built by the projector from the durable event's delta.
  • The live fold emits the same shape (full text, compact description) and now skips text-less deltas exactly like the projector.
  • SessionNoticeMessageV2 renders description ?? "Instructions updated" for system messages. The plain-label fallback covers messages projected before this change.

The model-facing contract is untouched: message.text still carries the full frozen update, which to-llm-message.ts sends as the system message on every request rebuild and compaction summarizes. Only human display changes.

How

  • packages/schema/src/session-message.tsSession.Message.System gains optional description, mirroring Synthetic.
  • packages/core/src/session/message-updater.ts — projector populates description from event.data.delta keys.
  • packages/tui/src/context/data.tsx — live event fold mirrors the projector: skips text: undefined events, stores full text plus compact description.
  • packages/tui/src/routes/session/index.tsxSessionNoticeMessageV2 shows description ?? "Instructions updated", never text.
  • packages/client/src/promise/generated/types.ts — regenerated (bun run generate from packages/client).
sequenceDiagram
    participant Core as Projector (core)
    participant TUI as TUI event fold
    participant View as SessionNoticeMessageV2

    Note over Core,TUI: session.instructions.updated { delta, text? }
    Core->>Core: skip if text undefined
    Core->>View: System { text, description: "Instructions updated: keys" }
    TUI->>TUI: skip if text undefined (now matching)
    TUI->>View: same shape, synthesized live
    View->>View: render description ?? "Instructions updated"
Loading

Scope

  • Does not change what the model sees: the frozen full text remains the durable payload and the LLM-request content.
  • Does not backfill description onto previously projected messages; they fall back to the plain "Instructions updated" label. Projections are rebuildable if exact parity is ever wanted.
  • The upstream cause of the most common phantom notice (restart snapshot race) is fixed separately in fix(core): gate tool snapshot on initial MCP registration #41884; this PR makes even legitimate updates render compactly.

Testing

From package directories:

  • packages/schema, packages/core, packages/tui, packages/client: bun typecheck — clean.
  • packages/core: bun test test/instruction-state.test.ts test/session-runner.test.ts test/bus.test.ts — 204 pass.
  • packages/tui: bun test test/cli/tui/data.test.tsx test/cli/tui/inline-tool-wrap-snapshot.test.tsx — 52 pass. The data test previously asserted the divergent live behavior (message for a text-less delta, compact string stored in text); it now asserts the unified shape and the text-less skip.

@kitlangton
kitlangton merged commit caae28e into v2 Aug 12, 2026
10 checks passed
@kitlangton
kitlangton deleted the instructions-notice branch August 12, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant