Chat turn visual specification

Message surfaces (no avatars, user bubble, edit box, action row), execution timeline (no outer frame, icons connected vertically, consistent rows), manual function runs, sub-agent parent/child branch navigation, file-change summaries and Review, and left-edge message navigation. All examples are interactive. This is the sole visual authority for chat turns; related implementations are in apps/web/components/chat/messages/ and app/styles/chat/.
Timeline folding and copying specification · implemented Scope: Chat / workflow / sub-agent presentation Continuous circle expand/collapse animation and function-column alignment · implemented Message surfaces: raised bubble, edit box and action row · implemented Exact file attribution, Review, and safe Undo · pending

1Default (collapsed): one turn = one muted summary line + reply

Execution has no outer frame. The collapsed state is a plain-text summary ›, without a button background, aligned with body text, and brightening text only on hover. The summary says what the calls did, in the order they happened (“Ran a command, created pill-shadow.html”). A red “· N failed” note and the file card’s +N −M pill follow when they apply. The sentence truncates to one line and the hover tip shows it whole. The grammar is defined in Tool group summary. Click to expand the timeline.
Can you start an agent to check today’s weather? I’m at the Asia Financial Center in Beijing.
Ran a command, ran sub-agent check Beijing weather›
Thinking First verify today’s date, then choose the query…
The user is at the Asia Financial Center in Beijing and wants today’s weather. First use bash to check the system date, then start a sub-agent for a live query and summarize brief advice.
Function call bash · date '+%Y-%m-%d %H:%M %Z' → 2026-07-10 16:32 CST
Sub-agent: check Beijing weather Completed
Thinking The sub-agent result is reliable; summarize travel advice…
China Weather and Open-Meteo agree. Summarize current conditions, daily range, and advice in three lines.
Beijing today:light rain becoming heavy rain, currently 25.8°C and 89% humidity; daily range 24–28°C, approximately 98% chance of precipitation.Bring an umbrella and minimize unnecessary outings.
Rules: no outer border or background; the collapsed row is a plain-text summary + ›, without a button background, aligned to the body column. Every collapsed row is exactly 28px tall, with its text and arrow vertically centered in the row, without top, transform, or translate offsets. The transcript has no avatar column; the text column lines up with the composer’s left and right edges. Reply text is always directly visible. The bottom timestamp and copy/retry actions appear together only on message hover or keyboard focus within the footer.Time contract:User messages record local time on successful send. Agent replies and independent function/runtime messages record start time when their first visible state is created. Thinking and ordinary tool steps inside an Agent share that Agent message’s start time; independent runtime/sub-agent messages keep their own. Subsequent streaming, terminal updates, and hierarchy moves preserve it; after reload, prefer persisted authoritative time. Temporary thinking after send/retry but before the first reply content occupies one row, with start time immediately after status text and no normal message footer. Switch to the formal message layout when reply content arrives. Execution summaries, body text, and timestamps use different font sizes and line heights, so body-to-footer spacing is 14px visually, keeping visible paragraph gaps consistent without changing collapsed-row height.
Functions and execution trees initiated by an Agent reply belong only to that reply’s execution timeline and must not also create top-level runtime cards. Only user-invoked functions appear as independent runtime messages.
Expand downward only: use grid 0fr↔1fr height transitions (0.18s), pushing lower content down on expansion and smoothly removing it on collapse;the viewport must not jump. Disable native scroll anchoring in chat (overflow-anchor:none), and suspend bottom following for 600ms after user clicks. The toggle row stays fixed on screen while expanding/collapsing. Unmount children after collapse animation to eliminate rendering cost while folded.Execution always defaults to collapsed: streaming and historical messages show only the summary, with the original text shimmer while running. Users can expand it; new steps append downward with a 0.22s fade/slide. Running rows use the original small status dot, and thinking notes show the latest line. Manual choices persist through later streaming and finalization. Streaming and finalized states use one timeline renderer: synthetic blocks derive from live fields until real blocks replace them after finalize.

2Timeline rows: circles indicate both expandability and open state

The left column retains type icons: filled Solar Bold Duotone glyphs (24×24), the same set as the composer; generic function rows use the sledgehammer. Colors indicate type:purple thinking / orange function / cyan LLM / green sub-agent. Dark and light modes each use clear, vivid type colors. Rows with direct child content always show a circular state:a uniform light solid circle when collapsed, a hollow ring when expanded, independent of hover. Collapsed circles have one uniform surface without separate center and rim. Mix 8% type color in light mode and 14% in dark mode with the page background to create an opaque color that fully covers the timeline behind it. An independent static outer ring is fixed at 2px, with outer diameters of 26px for main rows and 23px for children. The outer ring never transitions or animates; opening and closing change only the inner fill between matching color and page background. During expansion, the inner circle shrinks over 220ms to a 1.5px-wide, 12px-high vertical segment, moves downward from the center to the ring’s lower edge, and fades out to reveal the existing neutral timeline. Do not draw a second translucent line over it. Child content simultaneously uses 0fr→1fr, 220ms height, 140ms opacity, and -3px→0 translation. Collapse reverses these transitions exactly; repeated clicks reverse from current animation progress. Circle centers, outer diameters, and icon positions do not animate. Leaf rows have no circle. Add no arrows or descendant-step counts. Interaction still has two regions:underlined titles open specific information on the right; clicking icons, summaries, or row whitespace only expands/collapses.
Thinking ×1 · Functions ×2 (running)›
Thinking Expanded rows: content aligns to the title row width
Expanded content has one light background, no border, and no header of its own—the row above is its header. Content’s left edge aligns with the title row, without extra indentation. Click the row again to collapse. Only thinking expands inline; function/sub-agent details appear in the right panel.
Function call research_agent · has internal child calls → expands to an indented child timeline
web_search “Beijing Chaoyang district weather today” → 12 results
LLM Both sources agree; use China Weather as primary…
read_page weather.com.cn/101010300.shtml
Function call deep_lookup · click outside the title to expand each level
search_index18 candidate matches
LLMFilter and merge duplicates
write_reportResearch file written
Function call web_fetch · weather.com.cn (running)
Function call bash · exit code 1 — failed rows use a red ✗ icon
Every step row starts with a 24px rounded-square tile (7px corners) tinted with the step tone: a 16% tone fill over the page background, a 1px inset ring at 26%, and the glyph at full strength. Tones and glyphs come from tool-presentation.ts: commands green (terminal), file edits amber (pen), reads blue (file), searches cyan (search), web and browser indigo (globe or pointer), sub-agents green (bot), memory fuchsia, planning slate, media rose, system stone, other functions orange (hammer); thinking stays purple and LLM cyan. Failed = red tile with ✗; running = the normal glyph with a conic sweep orbiting the tile. Row text: a verb title (Ran, Edited, Searched the web…) at weight 500, then the identifying argument as a monospace chip, then the first line of the result in muted text. Raw JSON never appears in a row; the side panel keeps the full parameters and output.
Geometry: the main tl-body left padding is 34px; the 24px tile occupies 0..24 (center 12) and a 1px rail sits at 11.5px, fading in and out over its first and last 12px. Child timelines indent 6px with 30px left padding, 20px tiles (6px corners, 11px glyphs) and the rail at 9.5px. Rows are at least 30px tall (26px nested).
Animation: expandable rows show a small chevron after the summary that rotates 90° when open. Child content and the child timeline share one 220ms cubic-bezier(.2,.7,.2,1) transition; the whole strip keeps its 180ms collapse. prefers-reduced-motion reduces every transition to 0.01ms.
Expandability is shown only by that chevron; add no arrow slot or “⋯ N steps” label. Keep only actions such as Copy and Switch on the right; action right edges do not depend on expandability. Underlined titles always open details. Auxiliary actions appear only on the hovered row, changing immediately on entry/exit without opacity transitions. Apply :hover to step-head rather than the entire step so child hover does not reveal ancestor actions. Keyboard interaction retains actions only through :focus-visible; pointer focus must not leave old-row actions visible. Icons, summaries, and whitespace only expand/collapse, never open details.
Nesting: when functions have child calls (Agent method execution trees or multi-level tool call trees), expansion showsan indented child timeline, recursively using identical row styles without a depth limit.
Implementation status:Production components use tone-tinted rounded tiles per tool, verb titles with argument chips, a conic sweep while running and a chevron on expandable rows. Every timeline row offers copy; step counts are removed.

3Manual function runs: one FunctionInvocation, two input modes

The function form and complete function expression are two editors for the same FunctionInvocation { name, kwargs } . Clicking Run in the form submits typed kwargs directly. Entire composer text that exactly matches a registered function with named arguments is converted to the same object before creating a user message. Both create only the ProgramRun below, without UserBubble or an extra Agent text turn. Function fragments with surrounding prose, backticks, or code fences remain ordinary messages to avoid executing code under discussion.
InputEntry classificationPersistence and display
gui_agent(task="…", surface="browser")Exact whole-input match, registered function, named literal arguments onlyPOST /api/function/gui_agent → single ProgramRun
gui_agent form in Favorite ProgramsForm schema generates the same typed kwargsPOST /api/function/gui_agent → single ProgramRun
Please explain gui_agent(...) or `gui_agent(...)`Not a complete function expressionOrdinary UserMessage → Agent
The recognizer executes neither JavaScript nor Python and supports no positional arguments, attribute access, variables, or arbitrary expressions. It parses only strings, numbers, booleans, null, and flat named arguments allowed by the function schema. Missing required or invalidly typed parameters retain the draft, open the same form, and display errors instead of falling back to chat. Runs keep a 28px ƒ type marker on the left and the unframed timeline on the right. The marker identifies a ProgramRun; it is not a speaker avatar. Expanded run summaries show only direct root children; their descendants stay collapsed until users expand each level.
weather_report {"city": "Beijing"} · 3.2s → Light rain becoming heavy rain today, 24–28°C…
web_search “Beijing weather today” → 12 results
LLM Summarize two sources · 1.1s
Conclusion

Completed 3 calls: weather data retrieved, checked, and summarized.

Entry contract:FunctionForm and exact function expressions share one kwargs converter and one HTTP submitter. Chat submission only performs precise recognition before them; it must not duplicate function placeholders, Project, channel, session navigation, or error rollback. Successful recognition must not call sendChatMessage or write pending user text. Attachments, queuing during a run, and system decisions do not implicitly become function parameters.
The top collapsed title, Conclusion, and footer share one content-column start, without separate left, padding-left, margin-left, or transform adjustments. Footer uses the same icon buttons, timestamp, and hover reveal as chat footers, with justify-content:flex-start. Conclusion or direct result-to-footer spacing reuses the assistant body’s 14px visual paragraph gap, without a separate compact gap. Version navigationreuses the chat .message-nav component (◀ N/M ▶), with no new style. Footer copy copiesthe root call’s actual result (error ?? output), not argument JSON.
Branch semantics: Retry creates a sibling branch with unchanged arguments; Edit prefills previous arguments into the form and submits asa sibling branch with editable arguments (fork_of_node anchors to the original call’s predecessor). Both replace the displayed run in place; old runs remain accessible via ◀ N/M ▶.Never append a new call at the conversation’s end. Running placeholder = one breathing-dot row; nested runs render no footer.
Implementation status:Production function footers no longer have extra left padding. Timestamp rows, collapsed titles, and Conclusion share exactly one left edge. Exact function expressions, FunctionForm, shared HTTP dispatcher, Advanced argument groups, and bound-session isolation are implemented;function-invocation.test.mjs covers direct calls, ordinary-text boundaries, typed kwargs, successful binding, navigation, and failure rollback. Corresponding Python regressions cover non-executable GUI results and metadata. Independent specification review passed.

4Current sub-agent live display: records retained, execution collapsed by default

Sub-agent rows update the execution tree live, while the outer execution area defaults to collapsed in streaming and terminal states. Data remains in the message’s attachCards or contextTree ; expansion reveals the same continuously updated timeline record.
T0 · parent RUNNING
Execution area defaults to collapsed
const [open, setOpen] = useState(false); streaming state does not determine its initial open value.
T1 · sub-agent starts
Record “Sub-agent · Running”
Timeline data updates while the summary stays collapsed. If the user has expanded it, the new row appears with normal animation.
T2 · sub-agent finishes
Update the same row in place
The row with stable card_id changes to “Completed/Error,” without duplication or deletion.
T3 · parent TERMINAL
Execution area stays collapsed
Terminal updates neither change the default nor override the user’s manual expansion.
T4 · user expands again
Still the same timeline row
The record has not become a card. Hovering or focusing the row still displays at its end Switch ↗.

Slow replay of current behavior

The demo is deliberately slow so running-state changes can be seen not to expand the execution area automatically. T4 simulates manual expansion and confirms that records updated while collapsed remain the same timeline rows.
Waiting to replay
Sub-agent: search recent papers Running…
State boundary:Whether the parent workflow is still running affects only summary activity indicator and step status, not outer expansion. Collapsing does not delete records; expansion shows the original record as a timeline row.
Call pathLive dataPage nodeShared visibility control
In chat, agent()sub_agent events update by stable card_id : attachCardsSubAgentStepExecutionStrip
const [open, setOpen] = useState(false)
auto_workflow internal selection agenttree_update : contextTreeTreeStep child node
Implementation contract:Completion events update the same sub-agent record only;ExecutionStrip uses false as the common default for streaming and historical messages, with manual choices taking precedence. Related entries:execution-strip.tsx, chat-stream.ts, runtime-block.tsx, check-spawn-cards.mjs.

5Sub-agent parent/child navigation: one entry, two return destinations

The two existing return interactions are shown here. The parent enters a child branch through the timeline’s Switch ↗ . The child’s top Return to main conversation returns to the parent’s latest position; before the first message, Created from / Return to call site returns to the message that started the sub-agent. Click the three sample buttons to inspect destinations.
A · callerId
Parent branch · original call site
Bidirectional paths between parent and child branchesSwitch enters the child branch to the right; return to call site goes left. Switch →
← Call site
B · attach.head_id
Sub-agent child branch
Child returns to the parent’s latest positionThe top return-to-main button navigates to the parent’s latest message. Return to main conversation →
C · backTarget
Parent branch · latest position
Interaction preview Parent branch · latest position
Returned to the parent’s latest position, with the originating sub-agent execution row expanded and highlighted.
Returned to the original call site; later parent-branch messages are not in the current transcript.
Find relevant papers from 2025–2026 and organize the research developments.
I’ll ask a sub-agent to independently search and verify papers.
Thinking ×1 · Sub-agent ×1›
Sub-agent: search recent papers Completed
Continued the parent conversation: deduplicated the returned papers and wrote the research overview to project files.
Top “Return to main conversation”Return to the parent’s latest position; after reload, locate the originating turn, expand execution, and highlight the sub-agent row.
“Return to call site” before the first messageReturn directly to callerId; later continuation in the parent branch is hidden.
Shared boundary:All three actions switch the active HEAD in one session and reload the transcript; they do not open a new tab. The backend prohibits HEAD movement during execution. Legacy cross-session AttachCard opens a target session only and is outside this same-session example.auto_workflow contains only TreeStep without an attach pointer still open right-panel details only and show none of these branch-navigation actions.

6Left-edge message navigation: lines mark messages; hover opens a list

Small horizontal marks along the chat column’s left edge each represent one user message; the currently visible one is brighter and longer. Hover the column to open a message list, then click to jump. Try hovering the marks on the left.
Hello
Hello! How can I help?
Can you start an agent to check today’s weather?
Beijing has light rain becoming heavy rain today, currently 25.8°C… Bring an umbrella.
What about tomorrow?
Tomorrow turns from cloudy to sunny, 26–31°C, no precipitation…
Hello
Can you start an agent to check today’s weather?
What about tomorrow?
Marks count only user messages; replies occupy no mark. The columnstays at the left edge beside the sidebar divider (left: calc((100% - 100cqw)/2 + 8px), with chat-area an inline-size container), not amid body text. The panel expands to the right of the marks with single-line truncated rows. Clicking smoothly scrolls to and briefly highlights a message. Marks disappear while the panel is visible. Highlight follows the current visible message during scrolling.

7File changes: exact attribution, scoped Review, safe Undo

This section defines only chat-card and Changes center-tab visuals. The sole authority for file attribution, Undo, historical Revert, multi-turn Restore, Branch, checkpoints, concurrency, and retention is File changes, Review, and history recovery. Chat cards report only exact changes from this turn. Review uses This turn, Current branch, and Workspace.
Background-process logic and tests updated.
3 files changed+27−6current file state does not match the recorded source
playbook.json+1−1
src/runtime/player.py+16−4
tests/test_player.py+10−1
Changes ×
This turnCurrent branchWorkspace+27 −6UnifiedSide-by-sideOpen file
Code
player.py
playbook.json
Tests
test_player.py
src/runtime/player.py+16−4
38 def start_process(config): 39 - process = launch(config.command) 39 + process = launch(config.command, cwd=config.cwd) 40 + process.wait_until_ready(timeout=config.timeout) 41 return process
File rows and overflow:Each row leads with the Files panel’s per-type icon (FileTypeIcon: the @pierre/trees built-in icon set rendered as inline SVG, 15px in a 16px box), tinted per file type with separate light and dark palettes, so .ts, .md, .json and .py rows are told apart at a glance; the header keeps the pen icon. The header count is the turn’s file_count, and the list never silently disagrees with it. Collapsed, the card shows three rows plus a “Show N more files” toggle for the other loaded rows (at most 20). Counted files the card does not list — more than 20, or a bounded summary whose full list has not loaded — end the list in one “and N more files — open Review” row that opens the Review tab exactly like the Review button. Persisted summaries keep up to 20 rows; a summary that counts more files than it lists makes the card request the full list through review_scope, which then reads the complete mutation journal instead of the bounded summary.
Display state:Mount a chat card only when persisted turn file records exist or the turn contains a direct file-writing tool. Ordinary text replies and turns confirmed to have no records display neither card nor Review. Legacy file-writing turns without embedded summaries query on demand when entering the viewport. During queries, keep only an invisible probe; hide empty results. Query failure shows only a compact “Unable to load file changes / Retry” inline status, without unsupported Review controls. This state implementation passed independent specification review, quality review, real component tests, and the complete Web gate.
Visual boundary:Retain the full outer border, rounded corners, file list, and footer. The entire card uses product --font-sans; titles, paths, statistics, status, and buttons do not switch to a separate monospace font, while numbers retain tabular alignment. Header is 40px high with 12px left padding. FeatherIcon-to-title and title-to-statistics gaps are both 12px. Use the user-specified 19px FeatherIcon on the left, with a horizontal divider between summary and files. Dark-theme header and footer use --bg-tertiary, and the file list uses --bg-primary. In light mode, the card, header, Review button, and footer all use --bg-primary, without shadows or dark top/bottom blocks; only outer borders and dividers establish boundaries. File rows are transparent by default in every theme, adding only --bg-hover on hover. Footer hover likewise adds only --bg-hover. FeatherIcon indicates direct file edits in this turn, not the site logo. Hover uses the component’s 1.6-second movement/rotation sequence, disabled for reduced motion. Header FeatherIcon, 14px title, statistics, 13px Undo/Redo, and Review button share one vertical centerline rather than baseline alignment. Direction icons use 1em at the same height as button text. Always show both addition and deletion totals, including +0 or −0. Statistics are 12px; the 2.5px two-color proportion bar below encodes actual additions/deletions only, without category chips or decorative numbers. The card is an inline-size container. At width ≤420px, shorten “3 files changed” to “3 files,” retain FeatherIcon/statistics/actions, and restore compact 6px / 5px gaps. Viewport media queries are compatibility-only. When history preflight fails, hide Undo/Revert/Redo/Reapply and show one 12px theme-accent status before Review. Truncate it to one line;title retains full text; narrow cards limit it to 92px. Do not append a separate status below the file list or footer. After successful Undo, show Redo; the inverse of historical Revert is Reapply. Both use the same safe reapply transaction with renewed preflight, without a separate linear redo stack. Review is a neutral outlined button. Undo/Revert use text with a backward icon, not accent color. Code / Tests / Docs / Large categories appear only in Review. File-list padding is 4px on all sides. Rows are 28px high with 6px horizontal padding, 13px product sans paths, and 12px product sans statistics in fixed-width columns. The whole row navigates; add no expansion arrow or hover action group. Only the Show more footer expands. Once expanded, the footer shows only 13px Collapse. Full Review always opens from the header button, without an added “Review all” footer.

Official references and verifiable boundaries

SystemThis turn / checkpointReviewUndo / rewind
CodexOpen-source TurnDiffTracker consumes only committed apply_patch exact deltas without rereading the workspace; inexact deltas clear this-turn diff./diff independently uses Git: tracked files use git diff, untracked files use git ls-files --others --exclude-standard.Open-source thread/revert explicitly restores conversation history only. Codex App file Undo client implementation is not confirmed in the current open-source repository; do not infer it.
Claude CodeCreate a checkpoint per user prompt, tracking only direct file-editing tools such as Write, Edit, and NotebookEdit. Retain the latest 100 checkpoints.Desktop/Web exposes session-level additions/deletions; opening shows file navigation on the left and a single-file diff on the right, supporting line comments and another Agent revision.Code and conversation can be restored separately. Bash, general background sub-agents, external concurrent changes, and symlink/hardlink recovery are not guaranteed. Without tracked edits, no code-restore option appears.
Current OpenProgram implementationBeyond direct-tool checkpoints, recursively scans up to 20,000 files around every Bash call and precopies candidate content up to 5 MiB.Chat cards request full diffs; historical messages request file lists individually; shadow Git is mixed with current-disk fallback.Large files without baselines are recorded as pre_existing=false; Undo may delete preexisting files, and partial failures may still persist reverted state.

Adopted, adapted, and rejected

DecisionDesign itemOpenProgram handling
AdoptCodex’s exact incremental attributionTurn statistics derive only from committed file-mutation events. Without exact before/after, omit from the turn card.
AdoptClaude/Codex’s independent review surfaceChat retains summaries only. Review has left file navigation and right single-file content, with line comments.
AdaptClaude session diff and Codex turn/repository diffPlace in three scopes within one Review tab: This turn, Current branch, Workspace. Switching scope does not alter terminology.
AdaptClaude checkpoint recoveryRetain quick turn-level Undo with complete conflict preflight first. Store recovery as a new immutable journal transaction.
RejectBash directory-scan attributionDelete cwd traversal and candidate precopy around Bash. Ordinary Bash, background processes, and external changes neither count toward this turn nor qualify for its Undo.
RejectApproximate turn diffs and silent partial recoveryUnproven provenance appears only in Workspace Review. Failed Undo preflight changes no files and lists conflict paths.
AdaptReference-product header action hierarchyAdopt “text Undo + neutral outlined Review.” FeatherIcon on the left identifies direct edits; dimensions, spacing, colors, and Review pages retain OpenProgram’s visual system.

Three Review scopes

ScopeAuthoritative data sourceContainsUndo
This turnExact mutation journal for the current turnPrimary change set; linked child change sets retain actual producers and group by origin without copying mutations.Only when every record is recoverable and current digest equals the recorded after digest.
Current branchMutation projection of the current active lineageAll completed turns and explicit revert/restore/reapply; excludes sibling branches, ordinary Bash, and external writes.Start from a specific turn/checkpoint; no unscoped one-click branch Undo.
WorkspaceGit tracked diff + ls-files --others --exclude-standardCurrent uncommitted tracked/untracked files; respects .gitignore. Non-Git workspaces show unavailable, without directory-scan substitution.None. Workspace changes may come from users, other sessions, or background processes.

Target mutation journal

1 · Before mutation

Before writing, direct-edit tools record canonical path, file type,before_digest , and a recoverable baseline. If a baseline cannot be saved safely, mark recoverability=unavailable, never “original file absent.”

2 · Commit mutation

After successful writes, record after_digest, operation, line counts, binary/large,turn_id and actor_id. Failures create no committed mutation.

3 · Persist summary

Turn finalize merges only this turn’s mutations, generating net file lists and addition/deletion statistics persisted with the transcript. Complexity depends only on changed paths, without cwd scans.

4 · Review on demand

Review reads summaries first, requesting one bounded diff only after file selection. Switching files cancels old requests. Chat DOM never mounts diffs.

Current logic that must be removed:agent_loop._snapshot_cwd, _walk_scan and _checkpoint_changed_files must no longer participate in file attribution. In particular, “over 5 MiB not staged → use nonexistent path → pre_existing=false” must disappear. Missing baselines mean unrecoverable, never newly created files.

Undo safety contract

Phase / stateRuleVisible result
EligibilityEvery file requires exact operation and before/after digests. Only regular files are recoverable; symlinks, hardlinks, and missing baselines are unavailable.If any file is unavailable, the card shows no executable Undo; Review explains why.
PreflightReread all current digests before recovery. Newly created files may be deleted only while equal to after digest; modifications, deletions, and renames likewise require no subsequent edits.Any conflict prevents all disk writes for this Undo; state becomes blocked-conflict, listing conflict paths.
ApplyAfter complete preflight, write restored contents to same-directory temporary files and atomically replace. Multi-file operations retain rollback staging.Unexpected I/O failures roll back applied items; partial success must not be labeled Reverted.
RecordSuccessful recovery appends a revert_of=<turn_id> transaction; original-turn state derives from the journal.Chat and Review show Reverted; historical file lists remain reviewable without changing original conversation content.

Performance and content limits

PathTarget limitFallback
Turn finalizeNever traverse workspace; merge only changed paths in the mutation journal.Exclude mutations missing exact information and record diagnostics; produce no approximate statistics.
TranscriptReturn summaries with messages; initially display at most three files and stop sending list_turn_files. for every historical message. Query legacy messages only on viewport entry or explicit Review.
File listServer pages contain 100 files; frontend retains adjacent pages only and supports keyboard navigation.Category statistics come from summaries without loading every row.
Selected diffMount only one diff at a time, with at most 200 lines or 256 KiB per batch, continuing by hunk/page.Binary or oversized single hunks show statistics, reasons, and Open file; never present incomplete content as a full diff.
Render budgetDiff computation has an explicit time budget. Exceeding it returns metadata; Review never blocks chat or Agent completion.Allow retry later or opening the original file; never fall back to cross-turn approximate “before versus current disk” diffs.

Review tab and interaction contract

Action or stateResultBoundary
Review / file rowOpen the same deterministic r:<sessionId>:<assistantMsgId> center tab; file rows also select their path.Do not load inline chat diffs; repeated clicks only focus or update selection.
LayoutWithout split view, reuse the existing group and place Review to the chat’s right; with a full split, use a normal active tab.Do not replace existing peers, add a third pane, or create a second tab store.
Scope switchingThis turn, Current branch, and Workspace share left navigation/right diff layout, updating title and source labels together.Workspace has no Undo; ignored files are absent; non-Git is explicitly unavailable.
Line commentsClick diff lines to comment; send multiple comments with structured file locations in the next prompt.Comments do not directly edit files or claim code review has been performed.
Large / binaryShow exact statistics, diff-unavailable reasons, and Open file.Do not render entire large files or automatically classify logs as Agent edits.
Undo conflictCard shows Review conflict; Review locates conflicting files.No Force undo shortcut that could overwrite later user or other-session edits.

Implementation boundaries and acceptance

ScopePreferred locationExplicit exclusions
Exact mutations and recoveryprograms/tools/files/, store/snapshot/checkpoint/, turn context/finalize, and agent/internals/_revert.py.Do not add Bash directory watchers, modify user Git, or infer this turn from current disk.
Review protocolws_actions/turn_files.py, message metadata, and existing Git helpers; add paginated summary/diff requests.No unbounded strings or approximate fallback.
Review center tabcenter-tab-ids.ts, center-tabs-store.ts, center-tab-groups.ts and a minimal Review pane.No new window, modal, third pane, or reference-product branding.
Chat cardturn-files-chips.tsx, user-provided FeatherIcon , and existing CSS.FeatherIcon identifies direct file edits only; no site logo, inline diff, file-row arrows, hover action groups, or reference-product branding.
Visual acceptance:Chat cards initially show at most three filenames and load no diff; expand to at most 20 paths, with the remainder reached through the final “and N more files — open Review” row. Changes mounts one bounded diff at a time. This turn / Current branch / Workspace source labels remain visible. Historical Restore uses a separate impact sheet rather than crowding Undo/Revert/Restore/Branch into card headers. Full data/recovery acceptance is in theauthoritative runtime design.
Implementation status:Production chat cards retain outer borders and header dividers, with 40px headers and 19px Feather file-edit icons. Undo/Redo/Revert/Reapply shows only the currently executable action; Review uses a neutral outlined button. Center Review implements This turn / Current branch / Workspace, bounded file/diff pagination, and actor/producer/linked impact. A branch checkout mismatch banner requires Keep current files or Restore branch code before file modification. Runtime and visual implementations passed Task A–E independent reviews. Fonts, three background levels, 8ch fixed statistics columns, Collapse-only footer, compact spacing, and light primary-background revisions passed independent review. Moving history-preflight hints into the header also passed this iteration’s independent specification and quality reviews.

8Message surfaces: no avatars, a raised user bubble, the edit box and the action row

The transcript has no avatars and no speaker-name line: a bubble on the right and flat text on the left already tell the speakers apart, and the text column lines up with the composer’s left and right edges. Two marks remain and neither is a speaker avatar: a reply from a named agent other than main keeps a compact 18px identity line above its text in multi-agent sessions, and a manual function run keeps its ƒ type marker (section 3). The user bubble, the input box and the file-change card share one raised surface: a --bg-input fill (white in light themes, per theme in dark ones) with --composer-shadow. The assistant reply sits flat on the page without a bubble. Hover the messages below to see their action rows; rest on a button to see its tip.
Switch the README install steps to uv, and check the Python version requirement while you’re there.
Switched to uv sync and set the minimum to Python 3.11, matching pyproject.toml.
User bubble: right-aligned and as wide as its content, up to 75% (88% in narrow layouts and split panes); 16px corners, 10px 16px padding, no border. The fill is --user-msg-bg, which equals var(--bg-input) in every theme; the shadow is a static --composer-shadow. A sent message is not a control, so hover does not deepen it. Code blocks and collapsed pastes inside the bubble use a --bg-primary inset to stand apart from the bubble surface. Queued rows above the composer are user messages not yet sent and use the same surface with 10px corners.
Edit state: Edit message turns the bubble into an input box that matches the main composer: a --bg-input fill, 23px corners, --composer-shadow at rest and a 180ms transition to --composer-shadow-focus on hover and while focus is inside; it spans the full column. Inside are the kept attachments (each removable), the new-attachment strip and the composer’s own input row (with @ file mentions). The bottom row holds the Solar paperclip in the official ghost round icon Button (icon-sm) with the tip “Add images or files” on the left, then Cancel (elevated sm) and Save & resend (default primary sm) on the right. Esc cancels and ⌘/Ctrl+Enter saves; files can be dropped onto the box. The action row is hidden while editing.
Action row: sits under every message, right-aligned under a user bubble and left-aligned 14px below assistant text. Order: timestamp, Quote, Copy, Retry, Edit (user only), Branch (assistant only), Rewind to here (user only), then ‹ N / M › when sibling versions exist. Every button is the official ghost round icon Button at icon-xs (24px, the official size closest to 26px) holding a 16px animated line icon; it rests in muted ink and turns primary ink with a round muted fill on hover. The whole button drives the icon’s animation on hover and keyboard focus. Labels show through HoverTip + TipBody after a 1.5s dwell, never a native title, and the same text is the aria-label. The row is transparent until the message is hovered or holds focus, and always visible on touch devices; its height stays 24px whether or not it shows. A busy button is disabled at 40% opacity; Copy flashes a green check for 1.2s.

Assistant rendering across lifecycle states

Streaming, completed, and restored assistant messages use the same ExecutionStrip, ThinkingStep, and FunctionStep components. When ordered blocks are absent, existing thinking and tool records are adapted for presentation in their stored order; this fallback does not reconstruct missing interleaving or invent tool records. Tool results, error state, identifiers, and full-output references remain intact. Completion never switches to the legacy boxed ToolsBlock table.

Implementation acceptance: complete a turn with tool calls, expand its execution summary, then reload the conversation. The same shared rows and detail interaction remain available.

Chat math delimiters

Chat markdown already stashes complete $$/$/\[/\( blocks before marked and restores them as escaped text after sanitize. The React bubble still has to run KaTeX: MarkdownText typesets after paint, and the chat-area pin path calls typesetMath on the observed message column. Unclosed streaming math is not a complete stash pair; the next paint with a closer typesets. Sanitize, code, links, and ordinary escapes stay as they were. No new math library.

Acceptance: the sample \(d_t\) / \(v_t = (d_t - d_{t+1}) / d_t\) produces KaTeX HTML on the assistant bubble; display \[/$$ and inline $ do too; prose without math and incomplete openers do not typeset; invalid math does not throw.

Assistant replies omit the token and service-tier usage footer. The current execution segment uses the original text shimmer and running dot. Reply footers add no loading mark.

Bounded transcript history

The transcript uses bidirectional, byte-budgeted pages. A connection owns a small LRU of immutable disk-backed snapshots with message-ID indexes. A page request reads only its selected display groups, preserving tool descendants and compaction metadata. Snapshot tokens are scoped to the connection, session and captured branch head. Replaced snapshots and disconnected connections release temporary files.

The browser targets four cached conversations, allowing displayed panes and requests in flight to temporarily exceed that limit, and releases page references when conversations are deleted. Each conversation retains at most six ordinary pages and an 8 MiB content budget per loaded transcript, with an indivisible page allowed when needed to preserve a single large turn. Eviction occurs opposite the reading position, never deleting persisted records; active updates are retained. Scrolling back automatically retrieves evicted pages in either direction. Measured rows outside the viewport unmount, preserving their height. Heights of evicted rows are removed.

Prefetch distance follows scroll direction and speed with a bounded maximum. Only one page request runs at a time; failures retry with exponential backoff. Invisible panes stop requesting and stale responses cannot overwrite a newer generation. Page insertion and eviction restore the visible message and its offset before paint. Restoration uses a message ID rather than a pixel offset in an obsolete window. Jump to latest retrieves the latest window directly.

Acceptance uses a 300,000-message synthetic source: repeated bidirectional paging keeps cached pages, rendered rows and measurements bounded; rapid scrolling under latency preserves a visible message; stale replies, reconnect, branch changes and live updates cannot corrupt the window. Backend page reads must not scan or parse the full snapshot after indexing. Cold snapshot construction remains a separate measured operation.

Protocol v2 defers the full graph until its view is visible; ordinary branch metadata requests omit graph bodies. Cold snapshot construction reads and enriches the authoritative branch once. Subsequent indexed pages avoid full hydration.

ModuleResponsibility
message-list.tsxMessage composition and measured row rendering
use-chat-area-stick / use-message-viewport / use-history-windowFollowing, viewport measurement, active history lifecycle
history-window / history-viewportBounded page storage and message anchor operations
session-history-loaderNetwork requests, generation checks and state coordination
session_history.pyDisk-backed snapshot index and bounded page reads

Interaction contract: the bottom of an intermediate history window is not the latest conversation position. Main and split panes share the same following, adaptive rendering and jump behavior. Jump remains available while newer history exists and after a failed request; repeated clicks share one pending intent. Wheel, pointer and keyboard interaction cancel a pending jump or saved-position restoration before it changes the visible window. Ordinary paging still compensates for inserted rows at the current reading position. Pane teardown saves the final reading anchor. Connectivity recovery resumes automatic loading without retaining an obsolete retry delay.

Interaction acceptance: browser tests exercise an intermediate-window bottom, failed and repeated jumps, interrupted delayed restoration, independent split panes and unmount/remount. These interaction refinements are implemented and pass independent specification and quality review. Browser acceptance also checks that the saved anchor survives queued animation frames after unmount.

Performance contract: bounded Chat loads skip graph construction when the captured messages have neither compaction coverage nor spawn/attach relationships requiring graph annotations. When those annotations are needed, the shared graph builder performs the same semantic filtering without viewport geometry; opening the DAG still requests its full layout. First-open persistence hydration remains linear in stored nodes. The Markdown cache uses a 256-entry LRU and an 8 MiB budget estimated from UTF-16 source and sanitized output lengths; an individual entry exceeding the budget is rendered but not retained. Cache policy never bypasses sanitization.

Performance acceptance uses persisted synthetic messages through the public load handler and production browser components. Timing, frame intervals and retained heap are recorded separately; stable regression checks assert skipped work, semantic equivalence, cache eviction and bounded retention rather than machine-specific timing. Performance refinements are implemented and pass independent specification and quality review. Public-handler regression verifies skipped graph work and compaction equivalence; browser regression verifies byte-budget eviction, LRU promotion and oversized-entry bypass. Timing and retained-heap comparisons are synthetic measurements, not guarantees for full-App workloads.

Implementation status: implemented. The 300,000-message synthetic-source browser test verifies bounded bidirectional paging, rapid reversals under delayed transport, saved-message restoration, live-row preservation and jumps during pending loads. Indexed backend reads, snapshot cleanup and the web regression suite pass. Cold construction still reads the authoritative branch once; synthetic measurements do not establish latency for large real-world attachments or tool output.

Conversation loading mark

Only the existing empty-reply thinking… placeholder replaces its loading mark with the approved 18px open double ring. The rings counter-rotate while exchanging radius and stroke width. Colors follow theme tokens; reduced motion freezes the mark and forced colors use system text color.

Execution summaries, thinking steps, function steps, nested calls, pane titles, retry notices and reply footers do not use this mark. No additional indicator position is selected by tool, thinking or text phase. The function timeline retains its original layout, text shimmer, running dot, disclosure structure and detail interactions.

Acceptance: an empty-reply placeholder displays one new mark; tool calls, thinking content and reply text add no double rings to the timeline or footer. Approval and terminal states suppress the placeholder. Independent history reconciliation, nested progress and stable-message rendering fixes remain intact. Implementation and acceptance evidence is recorded in the generation-motion-layout task report.

Transcript update ownership

Message IDs and ordered assistant blocks define the transcript. Execution trees supplement the owning reply; status trees never generate replacement chat rows. History snapshots reconcile with observed progress for the same message: running snapshots cannot remove received tool calls, shorten text, or reopen a settled reply. Terminal snapshots update lifecycle and metadata while retaining received trace when omitted. Branch and history-window replacement still removes rows absent from that window.

Each execution root keeps its persisted path. Repeated calls to the same function remain distinct, and a root is consumed once across all text-separated execution groups. A completed function remains in its existing execution summary. Direct function runs retain their standalone presentation. Acceptance exercises stream, partial history, completion, late tree updates, errors, cancellation, and reload through production reducers and rendered components.

Implementation status: verification pending.

Queue and current-turn input

Queued messages dispatch in order after the current turn. Consumed steering input appears between the preceding and subsequent reply content. Its user node remains independent; the reply stores an ordered boundary reference shared by streaming, cancellation and history loading. Older records without a boundary place steering before the owning reply using persisted predecessor edges, without guessing a text offset.

Receipt polling confirms the same command without changing the visible waiting label. Pending or uncertain delivery cannot be resent, edited or removed. Definitive rejection retains the ordinary queued draft; transport failure retains uncertain delivery and never resends it as another turn. Attachments use ordinary queue delivery. Session queues and editing order remain independent.

Streaming steer: an accepted text instruction interrupts the active ordinary text/reasoning response, preserves its emitted content, then applies the durable command at the completed provider boundary before the next model request. The request is closed before continuation. Tool-call argument streams, running tools and structured-output responses retain their existing safe-point delivery; no tool is replayed or cancelled by steer. Polling is bounded and works while the provider is silent.

Stop during pending steer preserves the partial reply and its active history position. A command rejected because of cancellation remains an unsent draft with an explicit Send action; cancellation never automatically converts that draft into a new turn. Cancellation projection may advance only the exact top-level turn from its own user node to its existing assistant node, using compare-and-set, and never changes a newer or explicitly selected branch.

Implementation status: streaming interruption and stop retention pass independent specification and quality review, affected tests and the full verification manifest. Installed-App acceptance is recorded separately in release evidence.

Failed replies

A failed reply retains its partial answer and ordered thinking/function trace. One terminal notice appears after that content, before message actions. A concise localized headline describes the failure; technical details expand on request and render as plain text. No automatic retry is added.

Partial reply blocks and failure detail have separate fields. The legacy node output retains the error text for model-context and non-Web consumers; the Web mapper restores the partial answer from ordered text blocks. The failed assistant node persists the detail, reason, retryability and retry delay before the final failure event. History restores the same notice. Legacy error output is recognized without rewriting history and does not repeat the answer inside the notice.

Scope: main chat failures, shared assistant bubble and history mapping. Existing cancellation, approval, nested function details, retry/branch semantics and Goal reports retain their behavior. The existing provider taxonomy design remains authoritative; this adds durable presentation without a competing error store or third-party UI dependency.

Acceptance: a public turn emits text and tools then fails; live rendering and cold history both show answer, trace and one final notice. Empty and legacy failed replies remain visible. Successful/cancelled replies gain no error notice. Details work in narrow and dark layouts.

Release regression coverage also retains browser permission inheritance and high-level agent delegation checks. Bound-page tests supply an explicit binding and expect the existing stop after three failed attempts on the same observation, including the concrete failure reason. They do not change the browser workflow or its permission policy.

Implementation status: implementation, independent specification and quality reviews, and the full declared verification manifest pass. The browser regression fixtures match the existing bound-page path and failure limit. Installed-App acceptance is recorded separately in release evidence.