Per mara's screenshot report (agent-terminal icons not aligned in the
first column) — and her explicit follow-up steer on the first version of
this fix: 'dont do it by offsets at all, it should be part of the layout
that they align.'
Root cause traced first, not guessed: .row-glyph relied on inheriting
text-indent: -1.4em from .live .row to paint its glyph in the reserved
prefix slot; details.row > summary separately reset text-indent: 0 for
its own hanging-caret needs, which also zeroed the inherited value for
the icon nested inside it (indent inherits by computed value, not by
rule). Two independently-computed offsets that had to agree by hand,
and silently didn't.
Replaced the whole offset scheme with a real structural fix: every row
is a 2-column CSS grid (icon column, width from one shared
--row-icon-col custom property, then content column), and a details
row grids its own <summary> with the exact same grid-template-columns
value instead of griding itself (so its <pre> body still stacks full
width below, not squeezed into column 2). Icon and content are placed
by explicit grid-column, not auto-placement inference, so an icon-less
row's lone child still lands in the content column. .row-content is a
new wrapper class (Row.tsx, terminal.js's row()/mutableRow()/
placeholder()) giving that content an element the grid can target by
class - a DocumentFragment (what linkify() returns) doesn't persist as
a node once appended, so without an explicit wrapper there was nothing
for the grid to place.
Caught and fixed a second real bug while building this for real (not
just reasoning about the CSS): the first draft's details.row { display:
block } had lower specificity than .live .row's display: grid and never
actually applied, squeezing <summary> into the row's own 1.4em icon
column and wrapping its text one character per line. Needed
.live details.row to out-specify it.
Verified with a headless-chromium render of all 6 row shapes (flat
icon / flat icon-less / flat markdown-body / details icon / details
icon-less / a long-wrapping flat row) - all align and wrap correctly.
Also verified via the earlier Range.getBoundingClientRect() measurement
(glyph paint position, not just the element's own box): diff 0.00 for
both this and the previous fix, but only this one is structural rather
than two numbers that happen to still agree today.
61 lines
2.9 KiB
Markdown
61 lines
2.9 KiB
Markdown
# Per-agent terminal: row taxonomy (as built)
|
|
|
|
The per-agent web UI's live pane renders one row per `TermMsg`
|
|
(`hive-agent/src/term_msg.rs`): `{icon?, level: debug|info|warn|error,
|
|
summary, body?, body_format?: markdown|diff, coalesce_key?}`. Classification
|
|
(icon, summary text, whether a tool call gets an expandable body) happens
|
|
server-side, once — `term_msg.rs` + `stream_enrich.rs` — and both
|
|
`GET /api/events/history` and `GET /api/events/stream` serve it identically
|
|
as `TermEnvelope { ts, seq?, msgs: TermMsg[] }` frames
|
|
(`hive-agent/src/web_ui/stream.rs`).
|
|
|
|
The frontend renders a `TermMsg` close to as-is
|
|
(`frontend/packages/agent/src/components/Row.tsx`): `level` picks the
|
|
CSS colour (`terminal.css`'s `.live .level-*`), an empty `summary` +
|
|
markdown `body` renders as a flat row with just the body (assistant
|
|
text), and any other bodied row is an expandable `<details>`, opened by
|
|
default according to the operator's expand-tool-output preference
|
|
(`getExpandDetailsPref()`) — uniformly, no per-tool override. There's no
|
|
separate client-side row model or classification step.
|
|
|
|
## Layout
|
|
|
|
Every row is its own 2-column grid — icon column (`.row-glyph`, width
|
|
from the `--row-icon-col` custom property) then content column
|
|
(`.row-content`, or a markdown body's own `.md` class) — so an icon's
|
|
left edge lands in the same place regardless of row kind. A `<details>`
|
|
row grids its `<summary>` instead of itself (so the `<pre>` body below
|
|
can stack full-width), using the exact same `grid-template-columns` value,
|
|
with the disclosure caret leading the summary text rather than the icon.
|
|
One shared column definition, not two independently-computed offsets
|
|
kept in sync by hand — see `terminal.css`'s own comment on the incident
|
|
that motivated the change.
|
|
|
|
## Levels
|
|
|
|
| Level | Colour | Roughly |
|
|
|---|---|---|
|
|
| `debug` | muted | thinking, coalesced ticks, ambient harness chatter |
|
|
| `info` | default fg | turn start/ok, assistant text, tool calls + results |
|
|
| `warn` | amber, left rule | stderr, an unrecognised event shape, API retries |
|
|
| `error` | red, left rule | turn failed, a tool result with `is_error: true` |
|
|
|
|
The settings menu's "hide debug output" toggle (`getHideDebugPref()`,
|
|
`@hive/shared/prefs.js`) skips `debug`-level rows entirely rather than
|
|
muting them further — client-side only, same live-read-no-reload shape
|
|
as the expand-tool-output preference above.
|
|
|
|
Per-tool icon + summary formatting (what a `Read`/`Edit`/`send` call's row
|
|
actually says) lives in `stream_enrich.rs`'s `fmt_tool_use()` family —
|
|
read that when you need the specifics, this doc doesn't duplicate it.
|
|
|
|
## Markdown
|
|
|
|
`frontend/packages/agent/src/lib/markdown.ts`'s `renderMarkdown()` runs
|
|
`marked.parse()` through DOMPurify into a `<div class="md">`. Applied to
|
|
any `TermMsg` with `body_format: "markdown"`.
|
|
|
|
## Dashboard side (not covered here)
|
|
|
|
The main dashboard's message-flow pane is a different shape: broker
|
|
messages render as `.msgrow` grid lines, not agent-terminal rows.
|