dashboard: split queued vs running rebuild visual (#769)

Container row's pending highlight fired for both queued + running
rebuild-queue entries, so a long queue painted half the SW4RM tab
amber. Mara on #769: don't highlight while queued, show running as
spinner on agent icon.

- tabs.js: derive opRunning (transient OR op.state === 'running')
  separately from pending; add a pending-running row class.
  Operator-initiated transients still count as running so the
  rebuild-now-clicked → backend-picks-it-up window stays visually
  consistent.
- dashboard.css: move the amber border + tint from .pending to
  .pending-running (queued rows now keep their normal look, with
  the badge text still saying "rebuild queued"). Add a 1s linear
  rotating amber arc as .pending-running > .container-icon::after,
  with overflow:visible so the ring sits just outside the icon and
  composes with the mauve selected ring.
- docs/web-ui.md::Container row: describe the queued vs running
  split in prose. Drop the orphaned (#398) cookie while I'm at it
  — small #712 freebie.
This commit is contained in:
iris 2026-05-31 14:06:30 +02:00 committed by mara
commit 272d8af620
3 changed files with 45 additions and 3 deletions

View file

@ -433,8 +433,14 @@ body sits to the right with three stacked lines
entry for this agent is `queued` or `running` but no entry for this agent is `queued` or `running` but no
operator-initiated transient is set, the card surfaces a operator-initiated transient is set, the card surfaces a
`building…` / `meta-updating…` badge sourced from `building…` / `meta-updating…` badge sourced from
`rebuildQueueState` (#398) — so the SW4RM tab shows the same `rebuildQueueState` — so the SW4RM tab shows the same
rebuild progress visible on the SYST3M tab's R3BU1LD QU3U3. rebuild progress visible on the SYST3M tab's R3BU1LD QU3U3.
The row visual splits queued vs running: a **queued** entry shows
only the pending-state pill (no row tint, so a long queue doesn't
paint half the tab amber); a **running** entry keeps the amber
row tint AND draws a **rotating amber ring** around the agent
icon, so it's obvious at a glance which container is actually
moving.
Container name + port, and a `ctx · Nk` chip showing the Container name + port, and a `ctx · Nk` chip showing the
agent's last-turn context size (from `ContainerView.ctx_tokens`, agent's last-turn context size (from `ContainerView.ctx_tokens`,
read from the turn-stats sqlite on each `build_all` sweep; read from the turn-stats sqlite on each `build_all` sweep;

View file

@ -341,11 +341,38 @@ a:hover {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
.container-row.pending { /* 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. */
.container-row.pending .actions { opacity: 0.4; pointer-events: none; }
.container-row.pending-running {
border-color: var(--amber); border-color: var(--amber);
background: rgba(250, 179, 135, 0.05); background: rgba(250, 179, 135, 0.05);
} }
.container-row.pending .actions { opacity: 0.4; pointer-events: none; } /* The spinner is a transparent border ring with two adjacent sides
coloured amber, rotated by a CSS animation looks like an
orbiting arc around the icon. Override the icon's overflow:hidden
so the ring can sit just outside the square; it composes naturally
with the mauve selected-ring (selected + running shows both). */
@keyframes container-icon-spin {
to { transform: rotate(360deg); }
}
.container-row.pending-running > .container-icon {
overflow: visible;
}
.container-row.pending-running > .container-icon::after {
content: '';
position: absolute;
inset: -4px;
border-radius: 10px;
border: 2px solid transparent;
border-top-color: var(--amber);
border-right-color: var(--amber);
animation: container-icon-spin 1s linear infinite;
pointer-events: none;
}
.container-row .head { .container-row .head {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -497,10 +497,19 @@ window.marked = marked;
: (op.kind === 'meta_update' ? 'meta-update queued' : (op.kind === 'meta_update' ? 'meta-update queued'
: op.kind === 'destroy' ? 'destroy queued' : op.kind === 'destroy' ? 'destroy queued'
: 'rebuild queued'))); : 'rebuild queued')));
// Split the visual into queued vs running (#769): queued ops get
// no row highlight — the badge text alone says "wait, queued" —
// while running ops keep the amber row tint + add a rotating
// amber ring on the agent icon. Operator-initiated transients
// always count as running (the op is already in flight even if
// the queue worker hasn't picked it up yet).
const opRunning = transientKind != null
|| (op != null && op.state === 'running');
const selected = selectionState.has(c.name); const selected = selectionState.has(c.name);
const li = el('li', { const li = el('li', {
class: 'container-row' class: 'container-row'
+ (pending ? ' pending' : '') + (pending ? ' pending' : '')
+ (opRunning ? ' pending-running' : '')
+ (selected ? ' selected' : ''), + (selected ? ' selected' : ''),
}); });
// Topology: depth contributes left-padding; the glyph string in // Topology: depth contributes left-padding; the glyph string in