Dashboard primitives

StatTile and SoftChip drawn at the shared baseline's own token values. A decision artifact for DS-5 / PRIM-1, not an approved spec. Each component already exists twice — hram as a file, and a second build inline inside a page in fitness-monitor (StatTile) and cockpit (SoftChip). The promotion trigger is met on evidence, and the seven divergences between the two builds were decided on 2026-08-12 — everything drawn here is the promoted spec.

Every colour, size and radius below is read from ui-core-micha/src/theme/tokens.js. The chip tints are derived the way the component derives them — alpha(main, .12) fill, alpha(main, .30) border, darken(main, .3) text and dot — so nothing here is a hand-picked hex. Contrast ratios are computed in the page against the blended background, because a ratio taken against the raw rgba() would be meaningless.

StatTile — the KPI tile

A small bordered card: muted label, one bold tabular number, optional caption. Two call sites in hram today — the Calibration KPI row and the Allocation rail.

1280 px — Calibration KPI row (three tiles)
theta*
0.8421
Recovered
0.8397
bias −0.0024
Interval covers theta*
Yes
94.2 % of draws

Allocation rail (two tiles, with a child slot)

Coverage at budget
71.4 %
of maximum realizable
Deaths averted
63.8 %
children slot
375 px
theta*
0.8421
Recovered
0.8397
bias −0.0024
Interval covers theta*
Yes
94.2 % of draws

At 375 px the tiles fill the width and wrap once they hit the 120 px floor — the decided flex: 1 plus minWidth rule, which is why two fit here where hram's original minWidth-only version dropped to one per line.

StatTile passes THEME-4 as written. It references tokens throughout — variant="caption", variant="h5", text.secondary, divider — and its only raw numbers are theme-relative multipliers (borderRadius: 1, the spacing steps), not literals. What it lacks is a test: neither build has one, in either app, and tests are part of promotion.

SoftChip — the annotation chip

Soft tinted chips that read as annotation rather than alarm. Two variants, four tones, a 6 px leading dot. Replaced loud saturated fills — a bright-orange pill and a full-width green banner.

caveat — radius 3 px, 12/500

Provisional ranges Derived value Outside calibrated range Within tolerance

status — pill radius, 11/600

passed degraded failed queued

In place — a card header, where the chip actually sits

Sensitivity analysis
Tornado over 14 parameters
Provisional ranges

Computed contrast — text on the blended fill, not on the raw rgba

Tonemaintext = darken(main,.3) effective fill over paperratioWCAG AA (4.5)

The fill is alpha(main, .12) over background.paper, so the real background is a blend. getContrastRatio ignores alpha and would return the ratio against white — which is close here but is the wrong number for the wrong reason. These are computed from the blend.

The typography problem, and how it was resolved

SoftChip set typography as raw numbers, which is exactly what THEME-4's shadowing check forbids in a kit component. The four cells below are the options as they stood; the status variant now uses overline, and caveat is deliberately left unresolved (see the end of this page).

status, as hram authored it
passed
raw fontSize: 11, fontWeight: 600 — no variant
overline — chosen
passed
11/600 — identical metrics, plus uppercase + .4 px tracking
caveat, as authored
Provisional ranges
raw fontSize: 12, fontWeight: 500
baseline caption
Provisional ranges
12/400 — the nearest variant, one weight step lighter
Two clean ways out, and they are not equivalent. Use overline for status and accept uppercase — which changes how every status chip reads, from “passed” to “PASSED”. Or add one variant to the baseline for chip text and let both variants reference it — which is a baseline change, so an app-level decision that does not belong inside a component promotion. caveat has the same shape of problem at 12/500 against caption's 12/400: visually a hair bolder, but there is no variant for it.

Both components exist twice already — and the differences are the decision

The second build of each is an inline component inside a page, not a file, which is why a filename search misses it. The promotion rule is met: two independent builds each, three apps consuming.

StatTile — hram vs fitness-monitor

hram — ResearchComponents/StatTile.jsx
Recovered
0.8397
bias −0.0024
value h6 16/600 · tabular-nums · padding 12/8 fixed · minWidth 120 · caption sub-line · children slot
fitness-monitor — BodyHistoryPage.jsx:554
Body fat
18.4 %
Lean mass
62.1 kg
value h5 18/600 · no tabular-nums · padding responsive {xs:1, sm:1.5} · flex: 1 · accent = 2 px primary.main border (right-hand tile)

Five decisions fall out of this, and none is arbitrary: h6 or h5 for the value; tabular-nums (hram has it, fm does not — for a KPI row it is what makes digits line up between tiles, so this one has a right answer); whether accent survives as a variant; fixed or responsive padding; and flex: 1 vs minWidth, which is the 375 px wrap behaviour above expressed as an API choice.

SoftChip — hram vs cockpit

hram — SoftChip.jsx, status variant
passed
11/600 · lowercase · 6 px dot · 1 px border at alpha(main,.30) · fill alpha(main,.12) · auto height
cockpit — BoardView.jsx:65, lane pill
recurring 3
11/700 · uppercase + .4 px tracking · leading icon, not a dot · no border · fixed height 22 · tabular-nums
cockpit's build resolves the typography question the section above left open. Its pill is already 11-with-uppercase-and-.4 px-tracking — which is the baseline's overline in all but the weight step. So overline is not an imposition on the shared component; it is what the second consumer independently chose, and hram's lowercase is the outlier. That turned “changes how every status chip reads” from a cost into a one-app cost — which is how it was decided.

Decided — the promoted spec

Operator decisions, 2026-08-12. Everything rendered above the divergence section already reflects them; the two comparison panels keep the original values as the record of what changed.

DivergenceDecisionWhich app changes
StatTile value size h5 — 18/600 hram (16 → 18)
StatTile digit alignment tabular-nums, always — the reason a KPI row reads as a row fitness-monitor
StatTile row behaviour flex: 1 with a minWidth floor — covers both existing behaviours with one rule, no prop hram (tiles now share the row)
StatTile accent kept — 2 px primary.main; without it fitness-monitor cannot adopt neither
StatTile padding responsive {xs: 1, sm: 1.5} hram (was fixed)
SoftChip status typography overline — 11/600, uppercase, .4 px tracking. No new baseline variant, so THEME-4 is satisfied without a theme change hram (passed → PASSED); cockpit drops 700 → 600
SoftChip leading element + border dot by default, any icon via prop; border optional neither — both adopt unchanged

The promoted components, as decided

1280 px
Coverage at budget
71.4 %
of maximum realizable
Deaths averted
63.8 %
of max avertable
Lean mass
62.1 kg
accent variant
passed degraded failed Derived value
third chip borderless · fourth is the caveat variant, unchanged at 12/500
375 px
Coverage at budget
71.4 %
Lean mass
62.1 kg
passed degraded
Still open, and not part of the promotion: the caveat variant keeps its raw 12/500, which has no baseline variant (caption is 12/400). It is left as-is deliberately — resolving it needs either a baseline addition or a visible weight change, and neither belongs in the same change as the promotion. It will surface as a THEME-4 finding on the promoted component and should be exempted with that reason, not silently flattened to 400.

Not shown, because it cannot be: motion, focus and hover states, and how the chips read on a real dense results screen rather than a specimen sheet.