diff --git a/docs/web-ui.md b/docs/web-ui.md index 1a1c2a35..7a6cd6d7 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -538,6 +538,26 @@ needed). The joint at the row's own depth column is `├` (more siblings below) or `└` (last sibling at this depth — vertical stops at the row's icon midline). +**Indent + lane geometry.** Each depth level shifts the row right +by `1.8em` (the lane width). The per-depth ladders are hardcoded +for six levels — enough for any plausible hive topology, and the +typed `attr()` function from CSS Values 5 that would collapse +this to one rule is still partial-support (Chromium-only as of +2026). The `.tree-prefix` span sits absolutely positioned with +`left: -*1.8em` so its right edge meets the row content +(the icon) and its leftmost lane lines up with top-level rows' +icons at `x = 0`. Each `.tree-lane` is `flex: 0 0 1.8em` so all +lanes have equal width. Continuation bars are drawn at lane +center (`left: 0.6em`, `border-left: 1px solid currentColor`, +`top: 0; bottom: 0`) and extend through `.containers { gap: 0.4em }` +into the next sibling's prefix (`bottom: -0.4em` on the prefix +span itself) so adjacent ancestor lines visually merge into one +unbroken vertical line. The horizontal stub at a row's own joint +lands at the icon midline so the L/T meets the icon edge cleanly. +When every container has `parent = null` (pre-topology state) the +`[data-depth]` attribute is absent on every row and these rules +are no-ops — the layout reads exactly like the legacy flat list. + ### Selection bar Per-card action buttons (`R3ST4RT` / `ST0P` / `ST4RT` / `R3BU1LD` / diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index c0a92dba..f08bb5d8 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -189,16 +189,9 @@ a:hover { background: rgba(24, 24, 37, 0.55); transition: opacity 200ms ease, border-color 200ms ease; } -/* Topology indent (#363). Each depth level shifts the row right by one - step; the .tree-prefix span (drawn by tabs.js::treePrefix) carries - the ├─ / └─ glyph and any continuation lines that thread through - ancestor columns. When every container has parent=null (pre-#361 - state) `[data-depth]` is absent on every row and these rules are - no-ops — the layout reads exactly like the legacy flat list. - Per-depth indent: hardcoded steps for 6 levels (sufficient for any - plausible hive topology) — the typed `attr()` function from CSS - Values 5 would collapse this to one rule, but browser support is - still partial (Chromium-only as of 2026). */ +/* Topology indent ladder. See docs/web-ui.md::Topology tree (Indent + + lane geometry paragraph) for the 1.8em-per-depth-level + rationale + CSS-attr()-not-yet-portable caveat. */ .container-row[data-depth] { position: relative; } .container-row[data-depth="1"] { margin-left: 1.8em; } .container-row[data-depth="2"] { margin-left: 3.6em; } @@ -206,16 +199,9 @@ a:hover { .container-row[data-depth="4"] { margin-left: 7.2em; } .container-row[data-depth="5"] { margin-left: 9em; } .container-row[data-depth="6"] { margin-left: 10.8em; } -/* Tree prefix sits in the left margin and paints the connecting - ├ / └ / │ lanes as CSS rules rather than text glyphs (#388). Each - ancestor depth gets its own `.tree-lane` so we can paint a - full-row-height vertical bar that extends through the - .containers row gap into the next sibling — text box-drawing - glyphs only fill one text line, which left visible breaks - between rows once cards grew taller than one line of text (5em - square icons + multi-line body). The horizontal stub at the row's - own joint lands at the icon midline so the L/T meets the icon - edge cleanly. */ +/* Tree prefix lanes — DOM-painted, not text-glyph-painted. See + docs/web-ui.md::Topology tree for the full-row-height vertical + bar rationale + icon-midline joint alignment. */ .container-row .tree-prefix { position: absolute; /* Extend into the `.containers { gap: 0.4em }` below so vertical @@ -228,12 +214,10 @@ a:hover { user-select: none; color: var(--purple-dim); } -/* Each depth step is one 1.8em lane wide — same step as the row's - own margin-left ladder above, so the rightmost lane (the joint) - sits flush against the row content (the icon). The prefix's left - edge is depth*1.8em LEFT of the row's left edge, so its right - edge meets the icon, and the leftmost lane lines up with the - top-level rows' icons at x=0. */ +/* Prefix-left-edge ladder. Each depth step is 1.8em (matches the + row indent ladder above) so the prefix's right edge meets the + icon and its leftmost lane lines up with top-level rows' icons + at x = 0. */ .container-row[data-depth="1"] .tree-prefix { left: -1.8em; } .container-row[data-depth="2"] .tree-prefix { left: -3.6em; } .container-row[data-depth="3"] .tree-prefix { left: -5.4em; } @@ -277,14 +261,12 @@ a:hover { width: 2em; border-top: 1px solid currentColor; } -/* Live cards get the icon-left / body-right split; tombstone rows keep - the plain stacked block layout. The icon is a background-image div - with no intrinsic size, so its load state can never reflow the row - (issue #177). It used to `align-self: stretch` to fill the body - height, but with state badges / rate-limit pills / etc. wrapping the - head row, the body grew taller and the square icon grew with it — - so two cards with different content showed different-sized icons - (issue #344). Fixed at 5em now; height follows from aspect-ratio. */ +/* Live cards get the icon-left / body-right split; tombstone rows + keep the plain stacked block layout. The icon's fixed 5em width + + aspect-ratio-derived height avoid the align-self: stretch + feedback loop that used to make different-content cards show + different-sized icons. See docs/web-ui.md::Container row (Icon + layout + load strategy) for the load-state reflow rationale. */ .container-row:not(.tombstone) { display: flex; align-items: flex-start; @@ -298,9 +280,10 @@ a:hover { aspect-ratio: 1; border-radius: 6px; background-color: rgba(17, 17, 27, 0.6); - /* #443 — icon is the selection toggle. Cursor + hover ring make - that affordable without a chrome change. The :focus-visible ring - covers keyboard activation (Enter / Space). */ + /* Icon doubles as the selection toggle — cursor + hover ring + make that affordable without a chrome change. The + :focus-visible ring covers keyboard activation (Enter / Space). + See docs/web-ui.md::Selection bar for the toggle semantics. */ cursor: pointer; transition: box-shadow 120ms ease, transform 120ms ease; } @@ -321,9 +304,9 @@ a:hover { .container-row.selected > .container-icon { box-shadow: 0 0 0 2px var(--purple), 0 0 12px -4px var(--purple); } -/* The icon image fills the square wrapper and is taken out of flow - (absolute) so its load state — pending, loaded, broken — can never - contribute intrinsic size or reflow the row. (issue #177) */ +/* Icon image — absolutely positioned, fills the square wrapper. + See docs/web-ui.md::Container row (Icon layout + load strategy) + for the load-state-can't-reflow-row rationale. */ .container-row:not(.tombstone) > .container-icon > .container-icon-img { position: absolute; inset: 0; @@ -331,8 +314,9 @@ a:hover { height: 100%; object-fit: contain; } -/* When the fails to load it falls back to the dimmed hyperhive - mark, standing in for the unreachable agent icon (issues #195, #202). */ +/* When the fails to load it falls back to the dimmed + hyperhive mark — see docs/web-ui.md::Container row for the + fire-and-forget load + favicon fallback chain. */ .container-row:not(.tombstone) > .container-icon.icon-unreachable { filter: grayscale(1); opacity: 0.4; @@ -341,11 +325,11 @@ a:hover { flex: 1; min-width: 0; } -/* Pending state splits queued vs running (#769): queued ops show - only the pending-state badge — the row sits unhighlighted so a - long queue doesn't paint half the SW4RM tab amber. Running ops - keep the amber row tint AND get a rotating amber ring on the - agent icon so it's obvious which container is actually moving. */ +/* Pending state splits queued vs running — queued ops show only + the pending-state badge (no row tint), running ops keep the + amber row tint AND get a rotating amber ring on the icon. See + docs/web-ui.md::Container row (Pending-state derivation) for + the priority order. */ .container-row.pending .actions { opacity: 0.4; pointer-events: none; } .container-row.pending-running { border-color: var(--amber); @@ -385,11 +369,13 @@ a:hover { font-weight: bold; } .container-row .head .meta { margin-left: auto; } -/* Icon-only nav strip in the head row — the per-container backend- - supplied link list (issue #262). Inline-flex + gap so a longer list - (e.g. with `dashboardLinks` extras) doesn't cram (issue #333). Each - link gets a comfortable hit target with a subtle hover so the - icons read as interactive rather than decorative. */ +/* Icon-only nav strip in the head row — backend-supplied per- + container link list. Inline-flex + gap so a longer list (e.g. + with `dashboardLinks` extras) doesn't cram. Each link gets a + comfortable hit target with a subtle hover so the icons read + as interactive rather than decorative. See + docs/web-ui.md::Container row Line 1 for the link-list source + of truth. */ .container-row .head .nav-strip { display: inline-flex; align-items: center; @@ -605,9 +591,8 @@ code { flex-wrap: wrap; gap: 0.3em; } -/* When the approval was requested — right-aligned in the head row; - goes amber once it has been pending ≥ 1h so a stale request stands - out at a glance (issue #272). */ +/* Approval requested-at chip — right-aligned, goes amber after + 1h. See docs/web-ui.md::Approval card. */ .approval-ts { margin-left: auto; color: var(--muted); @@ -836,11 +821,10 @@ code { .rqe-source-approval { color: var(--green); border-color: var(--green); } .rqe-when { color: var(--muted); font-size: 0.85em; } .rqe-reason { color: var(--muted); font-size: 0.85em; flex: 1 1 auto; } -/* #437: in-flight step indicator on running queue entries — sub-line - below the main row, indented under the state glyph + kind. Cyan - keeps it visually grouped with the running spinner instead of - blending into the muted reason/timing chips. flex-basis: 100% so - it always wraps to its own line. */ +/* In-flight step indicator on running queue entries — cyan sub- + line below the main row, wraps to its own line via flex-basis: + 100%. See docs/web-ui.md::R3BU1LD QU3U3 for the step-annotation + semantics. */ .rqe-step { flex-basis: 100%; margin: 0.1em 0 0 1.8em; @@ -858,12 +842,11 @@ code { white-space: pre-wrap; } -/* #575: cancel-X for queued rebuild_queue entries. Quiet by default - (sits at the far right via margin-left: auto), lights red on hover. - Renders only when `state === 'queued'` per renderQueueEntry's - guard — Running / terminal rows don't get the affordance. Mirrors - the side-panel-close glyph shape (✗) without the full B6N-style - `btn-deny` width so it stays unobtrusive in a list row. */ +/* Cancel-X for queued rebuild_queue entries — sits at far right + via margin-left: auto, lights red on hover. Mirrors the side- + panel-close glyph shape (✗) so it stays unobtrusive in a list + row. See docs/web-ui.md::R3BU1LD QU3U3 for the queued-only + gating. */ .rqe-cancel { margin-left: auto; } @@ -942,10 +925,9 @@ ul form.inline { display: inline-block; } .btn-restart { color: var(--cyan); border-color: var(--cyan); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } .btn-stop { color: var(--pink); border-color: var(--pink); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } .btn-start { color: var(--green); border-color: var(--green); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } -/* #486 — M0V3 affordance (selection bar). Mauve picks up the same - accent the question-override / mid-status surfaces use; reads as - "structural change" rather than the destructive red / amber chrome - of destroy / rebuild. */ +/* M0V3 affordance (selection bar) — mauve reads as "structural + change" rather than the destructive red / amber chrome of + destroy / rebuild. See docs/web-ui.md::Selection bar. */ .btn-move { color: var(--mauve); border-color: var(--mauve); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } .btn-talk { color: var(--cyan); border-color: var(--cyan); } .btn-spawn { color: var(--amber); border-color: var(--amber); }