S design consistency review · #99 #101

The Experiment Capture panel,
read against the token contract

The form layout landed correct in #105 — aligned grid, grouped sections. What's left is the half-styled read flagged on #101: the panel uses the tokens but doesn't yet speak them. Six gaps, each a small, in-system fix.

Form grid (#105) — correct 6 token gaps remain reviewed vs sprout-tokens.css

Before / after

same markup, same tokens — only the gaps closed
before

Experiment Capture experiment mode

writes an isolated experiments/<id>/
idle
  • pill wraps mid-word when the row tightens
  • Start and Stop are the same neutral pill — no primary
  • status is lonely mono text, not a state
after

Experiment Capture experiment mode

isolated experiments/
recording
  • pill holds on one line — white-space:nowrap
  • Start fills --leaf; Stop stays the neutral secondary
  • status is a dot + live glow on --leaf

The six gaps

each names the token it should already be honoring
1
The “proposed” pills wrap mid-word
logging mode / experiment mode break across two lines when .panel-h tightens. A pill is an atom — it never wraps. fix → white-space:nowrap on .proposed
2
Button labels wrap too
Start logging breaks to Start / logging in a tight row. fix → white-space:nowrap on .btn
3
No action hierarchy — every button is a neutral outline
Start (constructive) and Stop read identically, so the primary action doesn't lead. The contract names --leaf as the brand action. fix → .btn.primary fills --leaf; Stop stays the secondary outline
4
Status is text, not a state
idle / running / error are core states but render as muted mono text. The system makes state a color, and the tokens note a live glow: box-shadow:0 0 8px currentColor. fix → dot + label; idle=--muted, running=--leaf (glow), error=--st-fault
5
The Monitor row sits on three baselines
In .capctl the lone status, the floating-label port field, and the two buttons each land at a different height. fix → align-items:center; push status to its own end-slot (margin-left:auto)
6
The two modes don't read as a pair
Monitor (the always-on baseline) and Experiment (bounded, isolated) are near-identical stacked cards — nothing signals which is the safe living logger vs the throwaway test. Reuse the device already on the summary cards: a 5px left accent. fix → Monitor accent --leaf, Experiment accent --st-predicted
Also, beyond this panel — the chart series use SENSOR_COLORS = ['#1E40AF','#7C5CFF'…], generic blues/purples that aren't in the token ramp. Out of scope for #101's form, but flagging for #99: series color should derive from the band ramp or a sanctioned categorical set so the dashboard reads as one palette.

Lift this

paste into dashboard_template.html's <style> — pure token deltas, no new values
/* 1 + 2 — pills and buttons are atoms; never wrap */
.proposed, .btn { white-space: nowrap; }

/* 3 — primary action = the brand green; Stop stays the neutral outline */
.btn.primary { background: var(--leaf); border-color: var(--leaf); color: #fff; }
.btn.primary:hover { background: #2C9247; border-color: #2C9247; }
/* then give each Start: class="btn primary" */

/* 4 — status is a state: a dot + label, with the live glow from the tokens */
.capstatus { display: inline-flex; align-items: center; gap: 7px; margin-left: auto; }
.capstatus::before { content: ""; width: 8px; height: 8px; border-radius: var(--r-pill);
  background: currentColor; }
.capstatus.run { color: var(--leaf); font-weight: 600; }
.capstatus.run::before { box-shadow: 0 0 8px currentColor; }
.capstatus.err { color: var(--st-fault); }

/* 5 — one baseline for the control row */
.capctl { align-items: center; }

/* 6 — the two modes read as a pair (same accent device as .scard) */
#monitorCard, #captureCard { position: relative; overflow: hidden; }
#monitorCard::before, #captureCard::before { content: ""; position: absolute;
  left: 0; top: 0; bottom: 0; width: 5px; }
#monitorCard::before  { background: var(--leaf); }          /* the living baseline */
#captureCard::before  { background: var(--st-predicted); }  /* the bounded test */