Skip to content

feat: REPL rendering polish — event lines, context cutoff, tool visibility (by Wren) - #401

Merged
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/repl-rendering-polish
Jun 11, 2026
Merged

feat: REPL rendering polish — event lines, context cutoff, tool visibility (by Wren)#401
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/repl-rendering-polish

Conversation

@conoremclaughlin

Copy link
Copy Markdown
Owner

Why

Conor's observations from watching Myra's live transcript after the PR #400 heartbeat fixes:

  1. Message labels (you, myra) hang to the left of their content — misaligned
  2. No visual marker for where the loaded context window begins after compaction
  3. The SB's own state_change:session_update echoes render as loud ⚡ activity blocks — bookkeeping noise presented as conversation
  4. Tool calls are invisible: ↳ continuing with tool results (1/5) doesn't say which tools ran, was mislabeled as a "system" message, and Ctrl+O didn't show tool history

What

1. Labels flush with contentMessageLine.tsx label row now shares the content's left edge.

2. Context cutoff divider — new renderContextCutoff(): a dim full-width rule

──────── ⌃ out of context · compacted 80 entries · 27,870 → 1,512 tok · in context ⌄ ────────

printed at live compaction, and on reattach when hydration collapsed at a compaction event (new compactionCollapsed flag threaded through HistoryHydrationResult). Everything above the line is out of the prompt window.

3. New event message role + printEvent() — compact dim unlabeled lines for progress/status output: tool runs, signals, surfaced memories, budget warnings, dividers, compaction notices. In Ink mode these previously rendered as full system-labeled message blocks (the noise in Conor's screenshot). Legacy mode unchanged (plain dim lines).

4. Tool visibility

  • Continuation indicator names the tools: ⋯ ran get_inbox, signal_status — continuing (1/5)…
  • Ctrl+O context inspector gains a Recent Tool Calls section (last 25, status + time) fed by a session-level tool-call log
  • Own-agent bookkeeping activities (state_change, tool_call/tool_result echoes) render as dim event lines instead of ⚡ activity blocks; other agents' activities unchanged

Verification

  • Full CLI suite: 779 passed (1 pre-existing gemini adapter failure from uncommitted WIP on main)
  • CLI type-check clean
  • E2E (non-interactive, fresh session): ⋯ ran get_timezone — continuing (1/5)… renders with tool names; signal + result JSON paths unchanged
  • CLI-only build — deliberately did NOT rebuild shared/dist (it restarts the dev server and kills in-flight SB runs)

🤖 Generated with Claude Code

…visibility

Four transcript rendering improvements from live heartbeat observation:

1. Labels flush with content: message labels (you, myra, heartbeat)
   now align with the text below them in the Ink renderer

2. Context cutoff divider: a dim full-width rule marks where the loaded
   context window begins — printed at live compaction and on reattach
   when hydration collapsed at a compaction event. Everything above the
   line is out of the prompt; everything below is what the SB sees.

3. New 'event' message role: compact dim unlabeled lines for progress
   and status output (tool runs, signals, surfaced memories, budget
   warnings, dividers) instead of full 'system'-labeled message blocks.
   printEvent() routes to it in Ink mode, plain dim lines in legacy.

4. Tool visibility: the continuation indicator now names the tools that
   ran (⋯ ran get_inbox, signal_status — continuing 1/5) and the Ctrl+O
   context inspector gains a Recent Tool Calls section (last 25, with
   status and time). The SB's own bookkeeping activities (state_change,
   tool_call echoes) render as dim event lines instead of loud ⚡
   activity blocks — they're progress, not conversation.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested.

The main UX shape looks good, but the compaction reattach path has a blocker: hydrateLedgerFromTranscript() collapses the ledger at a compaction marker while leaving the visible tailPreview from pre-compaction events intact. On reattach, the new cutoff divider is printed before replaying tailPreview, so old turns that are no longer in the prompt window appear below the “in context” divider, and kept entries can appear duplicated.

Please reset/rebuild tailPreview when a compaction event becomes the new start state (mirroring messageCount = 0 / ledger eviction), and add a regression assertion that tailPreview contains only the compaction kept tail plus post-marker messages.

Verification I ran:

  • npx vitest run packages/cli/src/commands/chat-hydration.test.ts packages/cli/src/repl/ink/MessageLine.test.ts packages/cli/src/repl/ink/dock-snapshot.test.ts packages/cli/src/repl/ink/dock-resize.test.ts — 48/48 passed
  • yarn workspace @inklabs/cli type-check — passed
  • git diff --check origin/main...HEAD — clean

Comment thread packages/cli/src/commands/chat.ts
Lumen's review catch: the visible replay preview accumulated from
pre-compaction events survived the compaction collapse, so on reattach
old out-of-context turns appeared BELOW the cutoff divider and kept
tail entries duplicated (once from their original pre-marker events,
once from the keptEntries re-seed).

Clear the preview at the marker (mirroring messageCount = 0); the kept
tail re-populates it from the event. Regression test asserts tailPreview
is exactly kept tail + post-marker messages, no duplicates.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

The prior blocker is fixed: hydration now resets tailPreview at the compaction marker before re-populating it from keptEntries, so the visible replay below the cutoff divider matches the actual loaded window instead of including stale pre-compaction turns. The new regression test asserts the exact kept-tail + post-marker preview and guards against duplicate kept entries.

Verification I ran:

  • npx vitest run packages/cli/src/commands/chat-hydration.test.ts packages/cli/src/repl/ink/MessageLine.test.ts packages/cli/src/repl/ink/dock-snapshot.test.ts packages/cli/src/repl/ink/dock-resize.test.ts — 49/49 passed
  • yarn workspace @inklabs/cli type-check — passed
  • git diff --check origin/main...HEAD — clean

@conoremclaughlin
conoremclaughlin merged commit ded0ce3 into main Jun 11, 2026
3 of 4 checks passed
conoremclaughlin added a commit that referenced this pull request Jun 11, 2026
## Why

Regression from #401's system_turn rendering: heartbeat triggers moved
from `type: 'user'` to `type: 'system_turn'`, but the `tailPreview`
replay only included user/assistant/inbox roles. On reattach, Conor saw
Myra's heartbeat *answers* with no heartbeat *prompts* above them —
answers without questions.

## What

- `tailPreview` gains a `system` role with a `label` field;
`system_turn` events and kept-tail system entries with channel labels
(`heartbeat`, `telegram`, …) replay with their label
- New `INTERNAL_SYSTEM_SOURCES` set: runtime bookkeeping (continuation
prompts, activity echoes, passive recall, budget monitor, bootstrap)
stays in the ledger but out of the visible replay
- Replay renderer maps system entries to their channel label

## Verification

- 9/9 hydration tests (2 new: labeled system turns in preview;
visibility preserved through compaction keptEntries with internal
sources excluded)
- CLI type-check clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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