Design system
Docs
Surfaces

Transcript and tool calls

Turns, user messages, reasoning, work groups, tool calls, prose, and code: how a conversation is drawn

HostImplementationNote
Primitive@nyte-ai/ui/collapsible, /button, /dialog
Desktopconversation/turn-view.tsxTurnView › UserMessageView | ReasoningBlock | WorkGroupView › ToolCallView; Prose › CodeBlock
Terminaltranscript.tsTranscriptView (later pass)

Parts

ComponentFilePure?Purpose
TurnViewconversation/turn-view.tsxmostly"One transcript item. User prompts are the only ordinary contained message region. Assistant prose stays flat, while reasoning, tools, and system history use compact rows."
UserMessageViewturn-view.tsxyesThe prompt bubble with edit.
ReasoningBlockturn-view.tsxyesCollapsible thinking; open while streaming, aria-busy.
WorkGroupViewconversation/tool-group.tsxyes"Intermediate narration, reasoning, and tool calls form one work episode."
ToolCallViewconversation/tool-call.tsxyes"One tool call: a quiet verb line that expands into its evidence."
Proseconversation/prose.tsxlink clicks onlyStreamdown Markdown with Nyte components.
CodeBlockconversation/code-block.tsxyesEscaped text immediately, worker highlight when near the viewport.
UserMessageTextconversation/message-content.tsxyesMessage text with inline reference chips.
ImagePreviewconversation/image-preview.tsxyesThumbnail opening a Dialog at natural size.

TurnView

PropTypeDescription
turnTurnturn / checkpoint / summary / config / note.
liveToolsReadonlyMap<string, LiveToolProgress>
liveLiveSnapshot"Only the trailing group carries the run."
cwdstring | undefinedPath shortening.
onEditUser(part, content, choice) => Promise<void>Enables editing.
branchModelBranchModelPickerCatalog and model state for the edit footer picker.
onOpenChanges(target: TurnChangesTarget) => voidReview / open file.
runningboolean

"Parts keep core's stable identity so settled and streaming content exchange in place." Two constraints from the file: "A completion's continuation turn draws nothing until its response lands; an empty completed turn must not leave a blank row behind." and "an earlier [group] is settled history, and the run's indicator belongs below the prose that follows it."

User message editing

UserMessageView is a button until edited. Click or F2 begins an edit, unless a text selection exists (so selecting to copy never opens the editor). In edit mode it mounts a ComposerEditor and a ModelPicker in the footer:

KeyEffect
EscapeCancel
Enter, ⌘EnterSave

The Send button is labelled "Send edited message". Sticky behavior is data-attribute driven (data-sticky-user-message, data-sticky-active, data-sticky-visible) and mutated by syncStickyUserMessage in the thread screen, which "avoids cloning the prompt or rerendering the transcript on every scroll tick."

Tool calls: the locked law

From tool-call.tsx:

Its locked law carries over: no status icons on tool calls. The running state is the shimmer, failure is the red line, and the chevron is a control, not a status. Neither shimmer nor red line survives a screen reader or a colour-blind eye, so the same two states also ship as visually hidden text.

PropTypeDefault
partToolTurnPart
progressToolProgress | undefined
cwdstring | undefined
density"compact" | "detailed""compact"

Anatomy: Collapsible.Root with render callbacks ("Open state lives in Base UI, not here: a controlled open would re-render this component (and re-parse the patch) on every toggle."), data-tool-status={state}, srOnly "Running" / "Failed", a Collapsible.Panel role="region" aria-label="{verb} output" with data-nyte-scrollport, and for edits a keepMounted panel with DiffView variant="inline". Closed output shows an OutputPreview that is a second trigger labelled "Show full command output".

Work groups

WorkGroupView props: parts, live, liveTools, cwd, durationMs, running, density. Summary verbs: Edited / Ran / Read / Listed / Worked / "Work failed"; while running: Working / Thinking / "Preparing next move" / "This is taking a bit longer" after 15s. Compact mode keeps the episode in a clipped window that follows new output; scrolling away pauses following for 10s; click opens. Uses Spinner, AnimatedNumber, srOnly, focus.ring.

Prose and code

Prose({ markdown, streaming }) runs Streamdown with animated={false}, controls={false}, skipHtml, link safety off, and maps preCodeBlock, table → a scrollable wrapper (data-nyte-scrollport, data-prose-table), img → alt text, apreventDefault + nyte.host.openExternal.

CodeBlock({ code, lang }): "Fenced code paints as escaped plain text immediately. Known grammars upgrade in a bundled worker, keeping grammars and syntax work off the UI thread." Highlight is requested once within 300px of the viewport (IntersectionObserver); LRU of 64; worker replies are validated with typebox at the boundary. Copy button labels flip "Copy code" → "Code copied".

Images

ImagePreview({ src, name, compact }): a 80px (64px compact) thumbnail that is a Dialog.Trigger; the popup shows the image at natural size "capped only by the window", on t.imageBg so transparency does not pick up the workspace tint. Close is labelled "Close image preview".