todo: drop landed entries (terminal coherence pass, get_state_file defense-in-depth, self-management of loose ends, persist+cold-load ctx-badge). claude.md: - scratchpad: new just-landed entries for ctx+cost badge split, terminal coherence pass, loose_ends rename + cancel_loose_end, whoami, reminder failure persistence, path linkify, tombstones+ meta_inputs events, agent open-threads section + container pending- reminder chip + task event rendering. drops the meta-flake "just landed" — structural facts live in the file map + approvals.md, the narrative was no longer load-bearing. - file map: hive-fr0nt now lists MARKED_JS + marked.min.js + the unified prefix-column terminal.css update. - reading paths: terminal-rendering.md description matches as-built. docs/terminal-rendering.md: rewritten as as-built reference. layout contract documents the padding-left + negative text-indent prefix column + how details inherits it. row taxonomy reflects current state (notes split into .note / .note.stderr / .note.op; .sys is amber; recv tool_results default-open with markdown body via tool_use_id correlation; rich send/ask/answer renderers). new sections for renderer dispatch flow, markdown integration, fmtArgsGeneric extra-MCP fallback, dashboard msgrow text-indent reset.
5.8 KiB
Per-agent terminal: row taxonomy (as built)
Snapshot of how the per-agent web UI's live pane renders each
event kind today. Source of truth lives in
hive-ag3nt/assets/app.js (renderStream, fmtToolUse,
renderRichToolUse, renderToolResult, renderTaskEvent,
mdNode, detailsOpenMd, fmtArgsGeneric) +
hive-fr0nt/assets/terminal.css (the shared .live .<class>
styling) + hive-fr0nt/assets/marked.min.js (markdown).
Layout contract
Every row — flat <div class="row …"> and expandable
<details class="row …"> alike — shares one prefix column.
The mechanism is padding-left + negative text-indent on
.live .row: the row's first character (the prefix glyph)
gets pulled back into the column at ~0.5em, and wrapped
continuation lines hang under the body, not under the glyph.
<details> summaries inherit those metrics. The disclosure
marker (▸ / ▾) is supplied by CSS summary::before so it
lands in the same column as flat-row glyphs. To make that
work the JS-side summary text does not include a
directional → / ← — the row's colour (cyan = outbound,
muted = inbound) carries the direction, and the prefix
column never has to fit two glyphs side-by-side.
Child blocks inside a row (the .md markdown wrapper, an
inner <details>) get text-indent: 0 so their content
lays out from the body column instead of inheriting the
parent's negative pull.
Row taxonomy
| CSS class | Prefix glyph | Color | Triggered by | Source |
|---|---|---|---|---|
.turn-start |
◆ TURN ← <from> |
amber, left rule | LiveEvent::TurnStart |
harness wake |
.turn-body |
(child div under turn-start) | fg | same | the wake-prompt body |
.turn-end-ok |
✓ turn ok |
green, left rule | LiveEvent::TurnEnd { ok: true } |
harness |
.turn-end-fail |
✗ turn fail — note |
red, left rule | LiveEvent::TurnEnd { ok: false } |
harness |
.text |
(no prefix; markdown body) | fg | claude assistant.content[].text |
stream-json |
.thinking |
· thinking … |
muted, italic | claude assistant.content[].thinking |
stream-json |
.tool-use (flat) |
→ Name args… |
cyan | tool_use w/o rich renderer | stream-json |
.tool-use <details> |
Write/Edit <path> · +N (no →) |
cyan, body is +/- diff | renderRichToolUse Write/Edit |
stream-json |
.tool-use <details open> |
send → to · NL, ask → to, answer #id |
cyan, body is markdown | rich renderer for send / ask / answer | stream-json |
.tool-result (flat) |
← <txt> |
muted | short tool_result (≤120c, non-recv) |
stream-json |
.tool-result-block <details> |
Nl · headline |
muted, body is text | long generic tool_result |
stream-json |
.tool-result-block <details open> |
recv ← <txt> |
muted, body is markdown | tool_result correlated to a prior recv tool_use via id |
stream-json |
.tool-use |
⌁ task <id> started · <desc> [type] |
cyan | claude Task-tool subagent start | renderTaskEvent |
.turn-end-ok / .turn-end-fail / .tool-result |
⌁ task <id> ✓/✗/◌ <status> · <desc> · → <output_file> |
green / red / muted | claude Task-tool result | renderTaskEvent |
.note |
· <text> |
muted | harness chatter | LiveEvent::Note |
.note.stderr |
! stderr: <line> |
amber/orange | stderr lines off claude | LiveEvent::Note (text starts stderr:) |
.note.op |
· operator: <text> |
mauve italic | operator-initiated notes (/cancel, /compact, /model, new-session) | LiveEvent::Note (text starts operator:) |
.sys |
! {json…} |
amber/orange | catch-all for stream shapes renderStream didn't classify |
catch-all |
| Banner shimmer | mauve | turn in flight (ref-counted) | — | setBannerActive |
Renderer dispatch
renderStream(v, api) walks each stream-json line:
- Drops
system/init,rate_limit_event,result(noise / handled elsewhere —resultpowers thecostbadge). subtype == "task_started" | "task_notification"→renderTaskEvent(subagent activity gets the⌁glyph).type == "assistant"→ walkmessage.content[]:text→.textrow with a markdown body viamdNode.thinking→.thinkingrow.tool_use→ recordid → nameintoolNameById, tryrenderRichToolUse(Write/Edit/send/ask/answer get custom renderings); on miss fall through to a flat.tool-userow withfmtToolUse → fmtArgsGeneric.
type == "user"→ walkmessage.content[]fortool_result;renderToolResultcorrelates viatool_use_id → toolNameByIdto default-openrecvresults with a markdown body, else short = flat / long = collapsed details.- Unrecognised shape →
.sysrow (amber,!glyph).
Markdown
mdNode(text) wraps window.marked.parse(text) (vendored
v4.0.2 UMD via hive-fr0nt::MARKED_JS) in a <div class="md">. CSS in terminal.css scopes paragraph / code /
list / blockquote / link styling under .live .row .md so
the markdown body doesn't bleed into the row's own
text-indent. Falls back to plain text if marked didn't
load. Applied to text rows and to send / ask / answer /
recv message bodies.
Extra-MCP tools
fmtArgsGeneric(name, input) is the fallback when a tool
isn't in the built-in fmtToolUse switch:
- single string field →
name k: "v" - single number/bool field →
name k: v - multi-field → first 4 pairs trimmed to
k: "v"/k: [N]/k: {…}with a…+Noverflow
This keeps mcp__matrix__send_message and similar from
dumping raw JSON.
Dashboard side (not covered here)
The main dashboard's message-flow pane is a different
shape: broker messages render as .msgrow grid lines (ts /
arrow / from / → / to / body) with their own styling.
.live .msgrow explicitly resets text-indent: 0 so the
per-agent terminal's hanging-indent metrics don't leak into
the flex-grid broker rows.