deepseek-harness/.agents/notes/implemented/feature/2026-08-14-web-turn-process-folding.md

9.9 KiB
Raw Permalink Blame History

Agent Note: Web Turn process folding

Status: implemented

English | 中文

Problem

A model Turn can expose System prompt, Context injection, reasoning, several Assistant replies, Tool calls, and Retry rows before its final answer. Keeping that whole trajectory at full height obscures the answer, while moving independent Chat Nodes under a parent disclosure would remount stateful Tool renderers and disturb chronological evidence. The compact view must hide completed process work without hiding the only evidence available while a Turn is still thinking, using a Tool, retrying, or ending without an answer.

Decision

The Host-backed ui-chat.transcriptView preference selects normal or compact and defaults to compact. Normal leaves every process row visible and renders no Turn-process control. Compact applies the disclosure rules below. Switching modes changes wrapper visibility without reparenting or unmounting Chat Node renderers, and the preference remains outside the Session log.

In Compact mode, a Turn remains fully expanded while it is open. At turn/end, its latest Step becomes a final-answer boundary only when it contains user-facing Assistant reply content—non-blank text, an image, or an unknown visible block—and contains no Tool-call block. The answer remains visible. Context injection, reasoning, earlier Assistant material, Tool rows, and Retry rows before that boundary form one process disclosure. System prompt, User, and steering rows remain independent and never join the group. Completed, aborted, interrupted, failed, and max-token Turns use the same terminal projection; error, max-token, and turn-tail rows remain outside the group. A closed Turn with no final answer keeps all process evidence visible.

The Turn-scoped turn-process Definition derives the first model or Tool evidence, the latest Step's finalized answer boundary, reply-bearing durable Assistant-message count before that answer, and Tool-call counts from log events plus Step Location data. Shipped subagent delegation names (subagent and subagent_*) increment the subagent count instead of the ordinary Tool-call count, so the categories never overlap. Context injection remains process evidence without incrementing a summary count; System prompt is independent, stays visible, and remains before the opening User. The Definition publishes one immutable TurnProcessSpec directly to both Turn Location data and its stable control Chat Node; continuing open-stream updates reuse both values while their fields remain unchanged. The Chat target positions opening User or steering input before process candidates from their first projection, then inserts the synthetic control between that input and the process rows. Without opening human input, the control stays before the earliest process candidate from its first appearance. Answer finalization, Retry, later Steps, completion, and manual expansion therefore change visibility without changing existing nodes' relative order, as specified by stable Turn-process ordering.

ChatTurnProcessProjector owns the cross-Node presentation facts. It scans only an affected Turn when Node structure, TurnProcessSpec, or Turn status changes, retains an equal presentation by reference, and publishes a stable process source only to Seats in a changed Turn. Each Seat classifies its own membership and answer role from that shared presentation without reading the global Chat snapshot, scanning the Turn, or encoding and decoding a signature. Turn status and loaded-window completeness gate foldability: an open Turn never folds, and a partial history exposes neither the control nor hidden members. The control Node exists from the first process evidence onward but its Seat remains hidden until the closed Turn has a final answer and complete history. Once visible, it omits every zero-valued segment, uses Thought for a while when all three counts are zero, and places a full-width divider below the summary.

The Chat target binds the durable transcript preference through the shared settings scope and keeps per-Turn interaction state in its session-scoped Chat store. ChatView renders each business Node through one stable keyed ChatNodeSeat; adding the process control does not reorder existing keys, and Compact mode changes the Seat wrapper's hidden attribute without reparenting or unmounting a Tool, Assistant, Context, or Retry renderer. The Seat passes the same process state through ChatNodeOwnerProps, so the final Assistant renderer hides reasoning blocks from its own Step while leaving reply blocks visible; wrapper visibility and inline reasoning therefore share one UI-state source.

Closed process members use hidden="until-found". A beforematch event on any member opens the shared group in supporting browsers. The Chat column applies spacing only between visible siblings because hidden-until-found members retain searchable zero-height boxes; the control's divider spans the content width, and a closed process control uses an 8px answer gap only when no independent input intervenes, while expansion restores the ordinary 16px row spacing. A collapsed Think row follows the latest streamed line through CSS, has one font-axis-adjusted fixed row height, and applies size and layout containment; expansion removes that containment and restores natural prose height. In Compact mode, the non-persisted session store contains only manually expanded Turn-and-answer-Step generations; absence means collapsed, and a different answer generation starts collapsed. Every eligible closed Turn therefore uses the same default regardless of whether it completed live, appeared after Load earlier, or closed while the reader was away from the tail. This can reflow content above the reader when a Turn closes or history becomes complete. An automatic collapse that would hide a focused process descendant opens the shared group instead, leaving keyboard focus in place; a manual close focuses the process control before hiding its members. If Load earlier is present, every process remains expanded and its control stays hidden; once history is complete, eligible groups immediately use the collapsed default. A fresh page load restores the durable Normal or Compact preference; per-Turn manual expansion survives only view remounts within the same page lifetime. Switching to Normal reveals every process row, while switching back to Compact reapplies the page-lifetime manual overrides over the collapsed default.

This presentation composes with Conversation Node assembly: Definitions own deterministic process facts, the Seat owns shared interaction state, and keyed renderers remain independent. The log-ordered human transcript remains complete because folding changes no session event or model input.

Alternatives considered

Fold only earlier Assistant replies. Rejected because a common Think → Tool → answer Turn has only one reply-bearing Step and would expose no compact control, leaving the user's requested process content at full height.

Keep Context injection outside the process. Rejected because injected runtime context is part of the pre-answer trajectory rather than a new human instruction. Its own disclosure and label remain intact when the process is expanded. System prompt is kept outside because moving or hiding the request-wide instruction changes the visible frame around the opening User.

Reparent the whole Turn under one summary row. Rejected because eligibility changes while the Turn runs, and moving existing Chat Nodes across React parents remounts stateful Tool views. Stable Seats provide one disclosure without moving their children.

Store manual expansion in Turn Location data. Rejected because Location data is a deterministic projection of session events and has no browser-action write path. UI gestures belong to a declared, non-persisted store.

Reuse DisclosureRow and unmount closed members. Rejected because browser find could not discover their text and reopening would reconstruct stateful renderer subtrees.

Fold a live answer candidate before turn/end. Rejected because streamed text can still be followed by a Tool call, Retry, or later thinking Step. Waiting for the terminal boundary prevents automatic collapse–expand–collapse cycles and the resulting layout jumps.

Defer a newly eligible collapse while the reader is away from the tail. Rejected because it requires transient completion tracking and deferred state, and makes identical closed Turns start in different states depending on how they entered the viewport. Closed Turns use one deterministic default; scroll anchoring preserves position, while the focus guard preserves an active interaction.

Consequences

Compact mode keeps the final answer prominent even when the Turn contains only injected Context, reasoning, or Tools before it, while expansion restores every process row in original order. Normal mode preserves the complete transcript without Turn-level controls. Hidden wrappers and Markdown subtrees remain mounted, trading browser memory for stable Tool state, manual expansion across view remounts, and browser-find recovery. Ordinary cross-message selection excludes closed members only in Compact mode; users expand the group before selecting them or choose Normal. Browsers without hidden="until-found" and beforematch retain manual disclosure but cannot reveal closed process text through page search. Unit coverage pins finalized answer boundaries, Retry and interruption, shared cross-kind expansion, final-Step reasoning, mode switching, manual expansion, immediate history-completion folding, off-tail folding, focus preservation, and content-only final-page revisions; assembled browser snapshots pin running, aborted, completed, paged-history, and persisted-setting trajectories.