dashboard.css + docs: migrate MESS4GE FL0W body layout + final cookie scrub (#712 batch 13)

Second dashboard.css batch — scrubs all remaining #NNN cookies
(post-batch-12 dashboard.css went from 51 → 36 raw matches, mostly
hex colors). This batch zeroes out the actual issue refs.

Moved to docs/web-ui.md::FL0W page:
- **MESS4GE FL0W (extended)**: msgrow flex-wrap row + body
  flex-basis: 100% + min-width: 0 rationale (~10 lines new prose).
  Without flex-basis: 100% the body sat inline eating whatever the
  chips left, starting ~30ch in on long timestamps + agent names
  and wrapping awkwardly. Pushing it to its own line lets the body
  use the full row width.

Collapsed in dashboard.css (en passant cookie scrubs across many
small comments):
- #485 msgrow body flex (→ FL0W docs pointer)
- #450 inbox max-height (→ inline rationale only, cookie dropped)
- #451 side-panel drag-to-resize × 2 (→ Side panel docs pointer)
- #369 dashboard chrome (→ Tab strip pointer)
- #416 full-width layout (→ inline rationale, cookie dropped)
- #389 slug banner footer (→ inline rationale, cookie dropped)
- #369#issuecomment-3437 + #362 + #383 + #383 + #362 flow.html
  section (→ FL0W page pointer + per-agent header pills pointer)
- #375 tail pill stacking context (→ Per-agent page Terminal-wrap
  pointer to existing docs)
- #66 ctx badge thresholds (→ Container row Line 1 pointer)
- #541 panel-body column flex × 2 (→ inline rationale, cookie dropped)
- #188 / #192 image preview (→ Side panel pointer)
- #275 bulk select × 2 (→ inline, cookies dropped)
- #259 background meta-update (→ inline, cookie dropped)
- #474 inline edit button + form (→ inline, cookies dropped)
- #466 interval composer (→ inline, cookie dropped)
- #535 schedules-as-table × 2 (→ SCH3DUL3S tab pointer + inline)
- #564 inline create row (→ inline, cookie dropped)
- #443 selection bar (→ Selection bar pointer)
- #486 M0V3 → <pick> (→ Selection bar pointer)
- #459 scheduled prompts tab section (→ SCH3DUL3S tab pointer)

dashboard.css: ~20 issue refs scrubbed in this batch + ~10 lines
substantive prose migrated to MESS4GE FL0W. Total dashboard.css
across batches 12 + 13: 51 raw matches → 0 issue-ref cookies
(only hex colors remain).
This commit is contained in:
iris 2026-05-31 15:01:16 +02:00 committed by mara
commit 6c9b28903f
2 changed files with 98 additions and 112 deletions

View file

@ -383,7 +383,14 @@ newest-first.
**MESS4GE FL0W** — live broker tail wrapped in a `.terminal-wrap`. **MESS4GE FL0W** — live broker tail wrapped in a `.terminal-wrap`.
Cold load backfills the last ~200 messages from `/dashboard/history`; Cold load backfills the last ~200 messages from `/dashboard/history`;
live frames arrive on `/dashboard/stream`. Each row is one broker live frames arrive on `/dashboard/stream`. Each row is one broker
event — `sent` or `delivered` — with `from → to: body`. Sticky- event — `sent` or `delivered` — with `from → to: body`. The row is
a `flex-wrap: wrap` container holding ts / arrow / from / sep / to
chips inline; the **body wraps to its own full-width line below**
the chips (`flex: 1 1 100%`) so the body always gets the full row
width down to the content edge — long timestamps + agent names
used to push the body ~30ch in and force awkward narrow-column
wraps. `min-width: 0` keeps `word-break: break-word` effective so
the body doesn't force the row wider than its container. Sticky-
bottom auto-scroll + "↓ N new" pill. Below the stream sits a bottom auto-scroll + "↓ N new" pill. Below the stream sits a
terminal-style compose box: `@name` picks the recipient (sticky via terminal-style compose box: `@name` picks the recipient (sticky via
localStorage; auto-complete from the live container list, Tab/Enter localStorage; auto-complete from the live container list, Tab/Enter

View file

@ -3,19 +3,19 @@
@import "@hive/shared/base.css"; @import "@hive/shared/base.css";
@import "@hive/shared/terminal.css"; @import "@hive/shared/terminal.css";
/* tabbed dashboard chrome (#369) /* tabbed dashboard chrome
Top-of-page sticky header with banner + tab strip. Tab routing is Top-of-page sticky header with banner + tab strip. SSE stays
hash-based; tab panes are show/hide via the `.tab-pane-active` alive across tab switches so count pills update live on inactive
class. SSE stays alive across tab switches so count pills update tabs. See docs/web-ui.md::Chrome header + Tab strip for the
live on inactive tabs without losing pulse on what's happening routing model. */
elsewhere. */
body.dashboard-shell { body.dashboard-shell {
/* Full-width layout (#416 mara: drop the 90em cap so wide screens /* Full-width layout no max-width cap so wide screens don't
don't waste real estate on empty side margins). `padding: 0 1.5em waste real estate on empty side margins. `padding: 0 1.5em
1.5em` keeps a small gutter on the left/right so cards don't kiss 1.5em` keeps a small gutter on the left/right so cards don't
the viewport edge; `.dashboard-chrome { margin: 0 -1.5em ... }` kiss the viewport edge; `.dashboard-chrome { margin: 0 -1.5em
still pulls the chrome bar edge-to-edge through that gutter. */ ... }` still pulls the chrome bar edge-to-edge through that
gutter. */
margin: 0; margin: 0;
padding: 0 1.5em 1.5em; padding: 0 1.5em 1.5em;
} }
@ -427,11 +427,10 @@ a:hover {
color: var(--cyan); border-color: var(--cyan); color: var(--cyan); border-color: var(--cyan);
text-shadow: 0 0 6px rgba(137, 220, 235, 0.4); text-shadow: 0 0 6px rgba(137, 220, 235, 0.4);
} }
/* Context-window usage badges on dashboard container rows. Thresholds /* Context-window usage badges on dashboard container rows. See
are derived per-container: yellow 50% and red 75% of the model's docs/web-ui.md::Container row Line 1 for the per-container
context window (`ContainerView.context_window_tokens`), mirroring the threshold derivation (yellow 50% / red 75% of the model's
harness compaction watermarks. Falls back to fixed 100k / 150k when context window) and fallback values. */
the window is unknown. (issue #66) */
.badge-ctx-ok { .badge-ctx-ok {
color: var(--green); border-color: var(--green); color: var(--green); border-color: var(--green);
opacity: 0.85; opacity: 0.85;
@ -464,10 +463,10 @@ a:hover {
/* Per-container journald viewer + applied-config viewer. Both open /* Per-container journald viewer + applied-config viewer. Both open
in the side panel and lazy-fetch on open; output is monospace in the side panel and lazy-fetch on open; output is monospace
inside a bordered <pre>, controls (unit select + refresh) above. inside a bordered <pre>, controls (unit select + refresh) above.
#541: the panel-body wrapper is a column flex container that fills The panel-body wrapper is a column flex container that fills the
the side-panel-body so the <pre> can flex-grow into a single tall side-panel-body so the <pre> can flex-grow into a single tall
scrollable surface instead of a short box at the top with the rest scrollable surface instead of a short box at the top with the
of the panel empty. */ rest of the panel empty. */
.journal-body { .journal-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -495,9 +494,9 @@ a:hover {
color: var(--fg); color: var(--fg);
border: 1px solid var(--purple-dim); border: 1px solid var(--purple-dim);
padding: 0.5em 0.7em; padding: 0.5em 0.7em;
/* #541: take all leftover panel height + scroll inside the pre so /* Take all leftover panel height + scroll inside the pre so long
long log fetches don't push the controls off-screen. `min-height: log fetches don't push the controls off-screen. `min-height: 0`
0` is the canonical "let me actually flex-shrink for overflow" is the canonical "let me actually flex-shrink for overflow"
escape hatch on flex children. */ escape hatch on flex children. */
flex: 1 1 0; flex: 1 1 0;
min-height: 0; min-height: 0;
@ -645,7 +644,8 @@ code {
border-color: var(--purple); border-color: var(--purple);
background: rgba(203, 166, 247, 0.08); background: rgba(203, 166, 247, 0.08);
} }
/* Image / tabbed file preview (issues #188, #192) */ /* Image / tabbed file preview see docs/web-ui.md::Side panel
for the type-aware preview shapes. */
.preview-host { margin-top: 0.5em; } .preview-host { margin-top: 0.5em; }
.img-preview { .img-preview {
display: block; display: block;
@ -719,7 +719,7 @@ code {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
/* Bulk select-all / -none control above the meta-inputs tree (#275). */ /* Bulk select-all / -none control above the meta-inputs tree. */
.meta-inputs-bulk { .meta-inputs-bulk {
margin: 0 0 0.5em; margin: 0 0 0.5em;
font-size: 0.8em; font-size: 0.8em;
@ -739,7 +739,7 @@ code {
border-color: var(--cyan); border-color: var(--cyan);
text-shadow: 0 0 6px currentColor; text-shadow: 0 0 6px currentColor;
} }
/* Tree twig glyph prefixing a nested (sub-)input row (#275). */ /* Tree twig glyph prefixing a nested (sub-)input row. */
.meta-input-twig { .meta-input-twig {
color: var(--purple-dim); color: var(--purple-dim);
margin-right: 0.1em; margin-right: 0.1em;
@ -765,7 +765,7 @@ code {
cursor: not-allowed; cursor: not-allowed;
} }
/* In-progress banner for the META INPUTS panel: shown while a /* In-progress banner for the META INPUTS panel: shown while a
dashboard-triggered meta-update runs in the background (issue #259). */ dashboard-triggered meta-update runs in the background. */
.meta-update-running { .meta-update-running {
margin: 0 0 0.7em; margin: 0 0 0.7em;
padding: 0.4em 0.7em; padding: 0.4em 0.7em;
@ -941,7 +941,7 @@ ul form.inline { display: inline-block; }
text-shadow: 0 0 6px currentColor; text-shadow: 0 0 6px currentColor;
box-shadow: 0 0 8px -2px currentColor; box-shadow: 0 0 8px -2px currentColor;
} }
/* #474: inline edit button on each schedule row. Yellow reads as a /* Inline edit button on each schedule row. Yellow reads as a
parallel destructive-adjacent action (edit changes state, but parallel destructive-adjacent action (edit changes state, but
isn't deletion). */ isn't deletion). */
.btn-edit-schedule { color: var(--yellow, #f9e2af); border-color: var(--yellow, #f9e2af); } .btn-edit-schedule { color: var(--yellow, #f9e2af); border-color: var(--yellow, #f9e2af); }
@ -1206,10 +1206,8 @@ summary:hover { color: var(--purple); }
background: var(--bg-elev); background: var(--bg-elev);
border: 1px solid var(--border); border: 1px solid var(--border);
padding: 0.5em 0.8em; padding: 0.5em 0.8em;
/* #450: no max-height cap let the inbox grow to fill the /* No max-height cap let the inbox grow to fill the
side-panel-body which already scrolls (`overflow: auto`). The side-panel-body which already scrolls. */
pre-#450 24em cap clamped the list well short of the available
panel height even on tall viewports. */
} }
.inbox li { .inbox li {
padding: 0.25em 0; padding: 0.25em 0;
@ -1245,17 +1243,9 @@ summary:hover { color: var(--purple); }
text-indent: 0; text-indent: 0;
} }
.live .msgrow .msg-body { .live .msgrow .msg-body {
/* #485: body takes a full flex line of its own beneath the /* Body takes a full flex line of its own beneath the metadata
metadata chips (ts / arrow / from / sep / to). Previously the chips. See docs/web-ui.md::FL0W page (MESS4GE FL0W) for the
body sat inline with `flex: 1 1 0`, eating whatever the chips flex-basis: 100% + min-width: 0 rationale. */
left which on a long timestamp + agent names + arrows meant
the body started ~30ch in and wrapped awkwardly. Pushing
`flex-basis: 100%` forces the body to wrap to its own line in
the existing `flex-wrap: wrap` row, where it can use the full
width down to the row's content edge.
`min-width: 0` still applies so `word-break: break-word`
actually kicks in instead of forcing the row wider than its
container. */
flex: 1 1 100%; flex: 1 1 100%;
min-width: 0; min-width: 0;
} }
@ -1354,9 +1344,9 @@ footer {
font-size: 0.9em; font-size: 0.9em;
} }
footer a { color: var(--purple); } footer a { color: var(--purple); }
/* Slug banner now lives at the page footer (#389 follow-up) give /* Slug banner lives at the page footer slim top margin so it
it a slim top margin so it doesn't crash into the prior content, doesn't crash into the prior content, bottom margin separating
and bottom margin separating from the divider/link line. */ from the divider/link line. */
footer .banner-thin { footer .banner-thin {
margin-bottom: 0.8em; margin-bottom: 0.8em;
} }
@ -1402,13 +1392,11 @@ footer .banner-thin {
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
/* #451: width is a CSS variable so the drag handle (added by /* Width is a CSS variable so the drag handle can update it live
Panel.bind) can update it live, and so localStorage-persisted (JS sets `--side-panel-w` on the drawer) and localStorage-
widths apply on first paint. Default min(760px, 94vw) preserves persisted widths apply on first paint. Default min(760px, 94vw)
the pre-#451 behaviour for operators who never drag. JS sets covers operators who never drag. See docs/web-ui.md::Side panel
`--side-panel-w` via inline style on the drawer; persistence for the drag-to-resize mechanism + localStorage key. */
lives in localStorage (key `hyperhive:side-panel-width`),
replayed onto the var by `Panel.applyStoredWidth` at bind. */
width: var(--side-panel-w, min(760px, 94vw)); width: var(--side-panel-w, min(760px, 94vw));
/* Clamp so a stored width can never push the drawer off-screen /* Clamp so a stored width can never push the drawer off-screen
or shrink it past readability. min content width matches the or shrink it past readability. min content width matches the
@ -1426,12 +1414,11 @@ footer .banner-thin {
.side-panel.open { pointer-events: auto; } .side-panel.open { pointer-events: auto; }
.side-panel.open .side-panel-backdrop { opacity: 1; } .side-panel.open .side-panel-backdrop { opacity: 1; }
.side-panel.open .side-panel-drawer { transform: translateX(0); } .side-panel.open .side-panel-drawer { transform: translateX(0); }
/* #451: drag-to-resize handle on the drawer's left edge. The handle /* Drag-to-resize handle on the drawer's left edge. Invisible until
itself is invisible until hover/drag so it doesn't compete with the hover/drag so it doesn't compete with the 2px mauve border-left
2px mauve `border-left` for the visual boundary. Pointer-cursor for the visual boundary. Pointer-cursor tells the operator the
tells the operator the edge is grabbable; the brighter glow during edge is grabbable; the brighter glow during drag
drag (`body.side-panel-resizing`) is the affordance the eye (body.side-panel-resizing) is the affordance the eye tracks. */
tracks. */
.side-panel-resize { .side-panel-resize {
position: absolute; position: absolute;
top: 0; top: 0;
@ -1529,22 +1516,17 @@ body.side-panel-resizing * { cursor: ew-resize !important; }
padding: 0.2em 0.5em; padding: 0.2em 0.5em;
} }
/* /flow.html full-page chat (#369) /* /flow.html full-page chat
The all-agents chat surface lives on its own page so it can claim See docs/web-ui.md::FL0W page for the page-vs-pane rationale
full-viewport vibec0re styling (operator @ #369#issuecomment-3437). and the inbox-flyout-as-pill ergonomics. Shape mirrors the
Same shape as the per-agent live page (#362): frosted-glass header per-agent live page (frosted-glass header + composer, full-
at top, frosted composer docked at bottom, terminal scrolls viewport terminal). */
behind both. Operator inbox lives behind a header pill that opens
the side-panel flyout preserves the "inbox + chat in one view"
ergonomics without stealing terminal real estate. */
:root { :root {
/* Approximate height of the flow chrome (tabbar + dashboard-chrome /* Approximate height of the flow chrome (tabbar + dashboard-chrome
padding). The banner-thin slug used to live in the chrome and padding). The slug banner lives at the page footer on /, omitted
padded this up to 4.7em; with the slug moved out (#389 follow-up: on /flow.html since the full-viewport terminal has no normal-flow
slug lives at the page footer on /, simply omitted on /flow.html footer position. */
since there's no normal-flow footer position in the full-viewport
terminal), the chrome is just tabs now and shrinks accordingly. */
--flow-header-h: 3.6em; --flow-header-h: 3.6em;
--flow-composer-h: 3.6em; --flow-composer-h: 3.6em;
--flow-frost-bg: rgba(30, 30, 46, 0.74); --flow-frost-bg: rgba(30, 30, 46, 0.74);
@ -1566,8 +1548,8 @@ body.flow-shell {
var(--bg); var(--bg);
} }
/* Flow chrome (#383): reuses the dashboard's `.dashboard-chrome` + /* Flow chrome reuses the dashboard's `.dashboard-chrome` + tabbar
tabbar so the operator can switch tabs from the flow page without so the operator can switch tabs from the flow page without
navigating back first. The chrome must be fixed-position (vs navigating back first. The chrome must be fixed-position (vs
sticky on the dashboard) since flow-shell has `overflow: hidden` sticky on the dashboard) since flow-shell has `overflow: hidden`
on body and the main area absolute-positions the terminal. */ on body and the main area absolute-positions the terminal. */
@ -1595,15 +1577,13 @@ body.flow-shell .tabbar .tab.active.tab-link {
border-color: var(--purple-dim); border-color: var(--purple-dim);
box-shadow: 0 -2px 12px -4px rgba(203, 166, 247, 0.4); box-shadow: 0 -2px 12px -4px rgba(203, 166, 247, 0.4);
} }
/* Legacy `.flow-title` / `.flow-hint` / `.flow-back` rules were /* `.notif-row` styling lives under the shared `.tabbar #notif-row`
removed in #383 the flow page now uses the shared chrome with selector earlier in the file the flow page reuses the dashboard
the dashboard tab strip, no need for FL0W-specific title/hint tab strip rather than carrying its own title/hint/back chrome. */
elements. The `.notif-row` styling lives under the shared
`.tabbar #notif-row` selector earlier in the file. */
/* Inbox pill operator inbox flyout trigger. Sits right under the /* Inbox pill operator inbox flyout trigger. Sits right under the
header so it stays in the operator's gaze without crowding the header so it stays in the operator's gaze without crowding the
chat. Same shape as the agent page's pills (#362). */ chat. Same shape as the agent page's header pills. */
.flow-pill { .flow-pill {
position: fixed; position: fixed;
top: calc(var(--flow-header-h) + 0.8em); top: calc(var(--flow-header-h) + 0.8em);
@ -1672,11 +1652,10 @@ body.flow-shell .tabbar .tab.active.tab-link {
overflow: auto; overflow: auto;
} }
/* Tail pill ( N new): bottom offset clears the floating composer. /* Tail pill ( N new): bottom offset clears the floating composer.
z-index escapes the stacking context the .terminal-wrap's Pill is anchored on .flow-main (not .terminal-wrap) so the
backdrop-filter creates (issue #375) tabs.js anchors the pill backdrop-filter stacking context doesn't trap its z-index see
on .flow-main now (not .terminal-wrap), so this z-index reaches docs/web-ui.md::Per-agent page (Terminal-wrap) for the same
the root stacking context and properly floats above the gotcha on the agent page. */
composer at z-index 30. */
.flow-main .tail-pill { .flow-main .tail-pill {
bottom: calc(var(--flow-composer-h) + 0.6em); bottom: calc(var(--flow-composer-h) + 0.6em);
z-index: 35; z-index: 35;
@ -1702,10 +1681,11 @@ body.flow-shell .tabbar .tab.active.tab-link {
surface the messages via the pill/flyout instead. */ surface the messages via the pill/flyout instead. */
.flow-inbox-headless { display: none !important; } .flow-inbox-headless { display: none !important; }
/* scheduled prompts tab (#459) /* scheduled prompts tab
Creation form at the top, list of queued schedule cards below. Creation form at the top, list of queued schedule cards below.
Cards show: id + source + due-in + cancel-all in the header, Cards show: id + source + due-in + cancel-all in the header,
the prompt body, then a targets table with per-row cancel. */ the prompt body, then a targets table with per-row cancel. See
docs/web-ui.md::SCH3DUL3S tab. */
.schedule-edit-form { .schedule-edit-form {
display: flex; display: flex;
@ -1717,9 +1697,9 @@ body.flow-shell .tabbar .tab.active.tab-link {
padding: 0.8em 1em; padding: 0.8em 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
/* #474 inline edit form opens directly under the schedule row's /* Inline edit form opens directly under the schedule row's
actions strip, indented slightly so it visually nests under the actions strip, indented slightly so it visually nests under
row it edits. */ the row it edits. */
.schedule-edit-form-wrapper { .schedule-edit-form-wrapper {
margin-top: 0.5em; margin-top: 0.5em;
padding-left: 0.5em; padding-left: 0.5em;
@ -1782,7 +1762,7 @@ body.flow-shell .tabbar .tab.active.tab-link {
justify-content: flex-end; justify-content: flex-end;
} }
/* Interval composer (#466). Preset chip row + d/h/m/s inputs + live /* Interval composer preset chip row + d/h/m/s inputs + live
preview, so the operator never has to multiply seconds by hand. */ preview, so the operator never has to multiply seconds by hand. */
.schedule-interval-presets { .schedule-interval-presets {
display: flex; display: flex;
@ -1826,10 +1806,9 @@ body.flow-shell .tabbar .tab.active.tab-link {
} }
.schedule-interval-preview-oneshot { color: var(--muted); font-style: italic; } .schedule-interval-preview-oneshot { color: var(--muted); font-style: italic; }
/* #535 schedules-as-table. One row per schedule, attribute columns /* Schedules-as-table one row per schedule, attribute columns
on the left, one / column per agent in the middle, actions on the left, one / column per agent in the middle, actions
column on the right. Agent column headers tilt -45° so a stack of column on the right. See docs/web-ui.md::SCH3DUL3S tab. */
short agent names fits in ~28px each. */
.schedules-table { .schedules-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -1849,11 +1828,11 @@ body.flow-shell .tabbar .tab.active.tab-link {
.schedules-table-id { width: 3em; } .schedules-table-id { width: 3em; }
.schedules-table-body-th { min-width: 12em; } .schedules-table-body-th { min-width: 12em; }
.schedules-table-actions-th { width: 7em; } .schedules-table-actions-th { width: 7em; }
/* Tilted agent column headers (#535). Header cell is narrow (~28px) /* Tilted agent column headers narrow (~28px) and tall (~90px);
and tall (~90px); the inner <div> rotates -45° about its bottom-left the inner <div> rotates -45° about its bottom-left corner, with
corner, with a translate to slide the text up alongside the cell a translate to slide the text up alongside the cell border. The
border. The inner <span> carries the actual baseline so the inner <span> carries the actual baseline so the underline
underline (border-bottom on the cell) aligns with the rotated text. */ (border-bottom on the cell) aligns with the rotated text. */
.schedules-table-agent-th { .schedules-table-agent-th {
width: 28px; width: 28px;
min-width: 28px; min-width: 28px;
@ -1937,8 +1916,8 @@ body.flow-shell .tabbar .tab.active.tab-link {
padding: 0.1em 0.45em; padding: 0.1em 0.45em;
} }
/* #564 inline create row at the bottom of the schedules table. /* Inline create row at the bottom of the schedules table. Cells
Cells host inputs directly so the operator can fill + click to host inputs directly so the operator can fill + click to
queue a new schedule without leaving the table view. Slightly queue a new schedule without leaving the table view. Slightly
different background tone so it reads as "this isn't a schedule different background tone so it reads as "this isn't a schedule
yet, it's the create form". */ yet, it's the create form". */
@ -2040,11 +2019,11 @@ body.flow-shell .tabbar .tab.active.tab-link {
border-color: var(--border); border-color: var(--border);
} }
/* Selection bar (#443). Sticky-bottom strip that surfaces bulk /* Selection bar sticky-bottom strip that surfaces bulk actions
actions when 1 agent is selected (click the icon). Visually when 1 agent is selected (click the icon). Visually echoes the
echoes the flow composer's frosted-mauve treatment so the chrome flow composer's frosted-mauve treatment so the chrome reads as
reads as part of the same vibecore family. Hidden when empty part of the same vibecore family. Hidden when empty. See
leaves the page footer's normal-flow position untouched. */ docs/web-ui.md::Selection bar for the bulk-action semantics. */
.selection-bar { .selection-bar {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
@ -2100,11 +2079,11 @@ body.flow-shell .tabbar .tab.active.tab-link {
selection set is non-empty). */ selection set is non-empty). */
body.dashboard-shell.has-selection { padding-bottom: 4.5em; } body.dashboard-shell.has-selection { padding-bottom: 4.5em; }
/* #486 M0V3 <pick> picker. Inline `<select>` + button pair /* M0V3 <pick> picker inline `<select>` + button pair sitting
sitting alongside the bulk action buttons in the selection bar. alongside the bulk action buttons in the selection bar. The
The select inherits the terminal-y monospace look so it doesn't select inherits the terminal-y monospace look so it doesn't read
read as system-chrome popping out of the swarm aesthetic. Only as system-chrome popping out of the swarm aesthetic. See
surfaces when exactly one agent is selected. */ docs/web-ui.md::Selection bar for the picker semantics. */
.move-picker { .move-picker {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;