dashboard.css + docs: migrate topology indent + icon/pending prose (#712 batch 12)

dashboard.css carried many CSS WHY-blocks duplicating prose
already in docs/web-ui.md (after PRs #722/#724/#728/#776).
Substantive new migration: topology indent geometry. Remaining
cookies collapse to docs pointers.

Moved to docs/web-ui.md::Topology tree:
- Indent + lane geometry paragraph (~18 lines new prose):
  per-depth 1.8em ladder rationale + hardcoded six levels +
  CSS Values 5 attr() partial-support caveat (Chromium-only as
  of 2026) + lane center continuation bars + bottom: -0.4em
  prefix extension to merge sibling vertical lines + horizontal
  stub at icon midline.

Collapsed in dashboard.css:
- Topology indent block (#363/#361) → 3-line pointer
- Tree prefix lanes block (#388) → 3-line pointer
- Tree prefix depth-step ladder (~6 lines) → 4-line tighter
  paraphrase (lane-meets-icon rationale stays inline as it's
  immediate context for the values below)
- Live cards icon-left split (#177/#344) → 6-line tighter
  paraphrase + docs pointer
- Icon selection-toggle hover/focus (#443) → docs pointer
- Icon img absolute positioning (#177) → docs pointer
- Icon favicon fallback (#195/#202) → docs pointer
- Pending state queued vs running (#769) → docs pointer
- Icon-only nav strip (#262/#333) → 7-line tighter + docs pointer
- Approval requested-at chip (#272) → 2-line pointer
- In-flight step indicator (#437) → 4-line pointer
- M0V3 affordance (#486) → 3-line pointer
- Cancel-X queued (#575) → 5-line pointer

dashboard.css: 51 → 36 cookies (counting hex-color false positives;
~15 actual issue refs scrubbed). Net ~50 lines of CSS WHY-prose
collapsed to brief docs pointers; ~18 lines of new substantive
prose migrated to docs/web-ui.md::Topology tree.
This commit is contained in:
iris 2026-05-31 14:47:04 +02:00 committed by mara
commit 9c72fd369a
2 changed files with 72 additions and 70 deletions

View file

@ -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: -<depth>*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` /

View file

@ -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 <img> fails to load it falls back to the dimmed hyperhive
mark, standing in for the unreachable agent icon (issues #195, #202). */
/* When the <img> 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); }