hyperhive/docs/terminal-rendering.md
müde 69a3ca7469 docs: prune landed todos + refresh scratchpad + as-built terminal-rendering
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.
2026-05-18 19:25:50 +02:00

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:

  1. Drops system/init, rate_limit_event, result (noise / handled elsewhere — result powers the cost badge).
  2. subtype == "task_started" | "task_notification"renderTaskEvent (subagent activity gets the glyph).
  3. type == "assistant" → walk message.content[]:
    • text.text row with a markdown body via mdNode.
    • thinking.thinking row.
    • tool_use → record id → name in toolNameById, try renderRichToolUse (Write/Edit/send/ask/answer get custom renderings); on miss fall through to a flat .tool-use row with fmtToolUse → fmtArgsGeneric.
  4. type == "user" → walk message.content[] for tool_result; renderToolResult correlates via tool_use_id → toolNameById to default-open recv results with a markdown body, else short = flat / long = collapsed details.
  5. Unrecognised shape → .sys row (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 …+N overflow

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.