Skip to content

bob viz Design Checklist

Every region on /bob follows these rules. A new pane copies this list into its requirement and ticks each box before it merges. The reasons behind the rules are in bob viz: Design (#2074). tests/test-bob-viz-page.js checks every box marked (tested).

The same list is what the briefing page (/briefings, #2244) is held to by tests/test-briefings-page.js — the boxes below plus one column at phone width with every long line wrapping — and what a pane on the per-project monitor (/warp) cites. /warp is one column of regions — Needs you, Run, Where it is, Acceptance · Time by phase · Activity, the context tabs (its PANES registry, #1734) and the Sources line — drawn by scripts/dashboard/warp-regions.js from the build spec warp-viz-spec.md; see warp viz. A /warp region is held to the boxes below with one difference, the type scale (decision #4350, in the Type and spacing box), and its assertions live in tests/test-warp-regions.js.

  • No border around a tile, a row or a list entry. Whitespace separates them (tested)
  • A hairline rule (--hairline, --border) sits only under a section head or a table head (tested)
  • No background fill except the page ground, a control and a hovered link (tested)
  • No gridline, axis, frame or legend box on a word-sized chart
  • Every figure in the pane is a value from its document. The pane renders; it does not compute
  • A region that shows a project shows every project the same way: the same rows, in the same order, at the same width
  • Machines follow machines.json order. Projects within a machine follow the viewer’s sort key (the grid’s default is recent, #2477), with name as the tiebreak (tested)
  • Stale or unpublished data is dimmed, and still listed
  • No row label takes more than two fifths of a tile (#3582). A repo tile labels each checkout’s row by its machine, so a long label is cut with an ellipsis and carried whole in its title. The todo row sits under its label at the tile’s full width: priority, wait, then the words, which stop at four lines and are whole in the line’s title and in Notices (tested in a browser)
  • Only --state-active (a run in flight) and --state-bad (what is wrong) colour anything, on .active, .bad and .feed-error (tested)
  • The normal case (ready, live, a link) reads in plain ink (tested for ready)
  • Every coloured value is also a word, so colour is never the only channel
  • No theme overrides the state tokens, and the pair passes the dataviz palette validator on every theme, dark and light (tested). Run the validator with DATAVIZ_VALIDATOR=<skill>/scripts/validate_palette.js node tests/test-bob-viz-page.js
  • A new state colour is added as a --state-* token and re-validated with the others. It never borrows an accent or an extended-palette hue
  • The tile’s state mark is the one non-text use of a state token (#2477): a dot before a running or attention tile’s name, coloured only through .active / .bad, titled with the feed’s reasons[]. An idle tile has no mark, and no tile has a background fill (tested). A new --state-* token the mark ever needs is validated with the pair, in both themes
  • The needs-you mark (#3038) is a hollow ring (○) and a word — todo, 2 todos, 2 todos · 1 p1 — after the name of a tile an open todo waits on. It is plain ink, never a state token, no border and no fill, so it stays distinct from the running and attention dots in both themes; the card’s todo row gives the oldest todo and its exact command (tested)
  • One series, stroked in --viz-line with no fill; the last known point is a dot in --viz-point (tested)
  • Word-sized: 64×16 in a table, 40×12 on a tile. A 1-unit line and a dot of radius 1.5
  • Scaled from 0 (or the lowest value, when negative) to the highest, so a flat line is never exaggerated
  • The value beside it is the label. The tooltip names the range, the point count and the maximum
  • A missing point is a gap, never a zero. An unread series says not measured with its reason, never an empty chart
  • A page uses one type scale from design-tokens.js: /bob uses dense (--text-dense-*), /warp uses large (--text-*), read from across the room (decision #4350). A page-local multiplier is allowed when it multiplies tokens and never adds a px literal — /warp’s --warp-scale, stepped by its A− / A+ control. Spacing comes from --space-*, --hairline and --radius-*. No px literal in the stylesheet (tested)
  • Figures and labels in a pane use --font-mono; headings and prose use --font-body
  • A long value is truncated with an ellipsis and keeps its full text in a title
  • The page paints its own ground, ink and face (--bg, --text, --font-body), because the server substitutes only the tokens (tested)
  • State colours and sparkline ink clear 3:1 on the page ground in every theme, dark and light (tested)
  • The pane looks right in light and in dark. Regenerate the screenshots with node scripts/dashboard/bob-viz-shots.js and view both

Built once in scripts/dashboard/bob-controls.js (#2922); a region reuses them, never its own. The reasons are in bob viz: Page controls.

  • The region is a native <details>/<summary> rendered by renderSection, open unless the viewer collapsed it, operable from the keyboard (tested)
  • The summary gives the count in words (4 need you, 1 blocked), so a collapsed region still says what it holds (tested)
  • The count is coloured with a state token only when it holds something running or something wrong, and never reads “nothing needs you” while a source could not be read (tested)
  • Each list declares its sort keys in SORTS, the default first, and the select names the active key in words (tested)
  • The sort is stable, in codepoint order, and not measured sorts last in either direction, never as zero (tested)
  • A list longer than FOLD_FIRST (6) folds after the sort, reveals FOLD_STEP (10) more per click, then offers show fewer; the footer counts the rows still hidden (tested)
  • What needs Paul is pinned past the fold (in Notices, a blocked run and a p1 notice) (tested)
  • A filter never lies: when it hides any row, the head says N of M; a problem count (blocked) is over every row; “nothing needs you” only when the unfiltered count is zero; each active filter has a chip that clears it; a filter is session-only, never stored or in the URL (#2477) (tested)
  • Identity is carried by linked selection, never by a hue: a tile’s name filters Notices to its project, and a notice’s machine / project reaches its tile with a brief --border outline and no fill (#2477) (tested)
  • The view persists per viewer under bob-viz-view/1 with every storage call in try/catch, and the sort keys are mirrored in the URL fragment, which wins over storage (tested)
  • A push or a re-poll repaints with the viewer’s open state, sort and revealed count (tested)
  • At 390 wide the heads wrap and every control is touch-sized (min-height: var(--space-2xl)) (tested)

Held by the card on /bob (#2925), and by any region that opens a row into detail. The rendering is in scripts/dashboard/bob-card.js, and the reasons are in bob viz: Expanded card.

  • The compact rows stay on the card. The expanded view opens them up underneath, in place, never on a separate page (tested)
  • The disclosure control is a <button> with aria-expanded and aria-controls, operable from the keyboard (tested)
  • The expanded view shows loading until its document lands, never an empty card (tested)
  • A push re-renders an open card from the new row and never collapses it. The cap never hides it (tested)
  • Every section the document could not fill says not measured or unavailable with its reason, and every null field carries its reason as a tooltip (tested)
  • Every link opens its source, and a link with no URL is left out, never shown dead (tested)
  • No fill, no border and no rule inside the card. Whitespace separates the sections (tested)
  • It spans two grid columns wide and the full row on a narrow screen, and it is legible in both themes at 390 wide. Regenerate bob-viz-card-*.png with node scripts/dashboard/bob-viz-shots.js and view them
  • not measured is never a zero, a blank or an idle state
  • Every source that was not fully read is named under the pane, with its reason
  • Every value from another document is HTML-escaped

Light --accent is 4.22:1 on the light ground. It clears the 3:1 mark rule but not the 4.5:1 WCAG small-text rule. /bob colours its active state with --state-active instead, which has the same value. Raising both is a shared token change for every dashboard, and is not part of #2074.