Public: anyone can read this; the crew can change it.
§5 gains a stat tile: `stat()` in ui.ts and a `.stats` grid, listed in DESIGN.md's component table #3997
Check · Review · Land · Live done — Landed and live in production.
§5 now has a stat tile: `stat()` in `src/web/ui.ts`, a `.stats` grid in `src/web/style/components.ts`, and a **stat** row in the DESIGN.md §5 table. I couldn't run `bun x tsc --noEmit` here. `bun install` and tsc both fail in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so the forge's check is the first type check.
shipped production is serving it deploy 6365650
§5 now has a stat tile: stat() in src/web/ui.ts, a .stats grid in src/web/style/components.ts, and a stat row in the DESIGN.md §5 table. I couldn't run bun x tsc --noEmit here. bun install and tsc both fail in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so the forge's check is the first type check.
What changed
stat({ label, value, title?, note?, href?, spark? }):
- It draws a label, a .num value with its full form in title, an optional note clamped to two lines, and an optional inline SVG slot.
- It renders as an <a> when href is set, otherwise a <div>.
- Label, value, note, title and href go through html, so they are escaped. spark is trusted markup the caller builds.
- Styles:
- .stat has a fixed 104px height and fixed label, value and note heights, so a changing value shifts nothing around it.
- .stats is an auto-fit grid with a 160px minimum column.
- Hover uses --border-strong and applies only to the link form.
- Everything uses tokens. The value is 20px, because the design lint rejected 24px as off-scale.
- Docs: the table row lists the classes, states and look. No §5d entry.
- Test:
test/ui-stat.test.tscovers the tile with and withouthrefandnote, and<escaped in label, value, note andhref. It also checks that the grid is defined.
git grep found no existing .stat* class, so there is no collision.
Checks run: test/ui-stat.test.ts, test/web-ui.test.ts and test/ui-sweep.test.ts pass, and bun scripts/design-lint.ts passes.
Left out: the home page still uses its old KPIs; moving it onto stat() is the next issue. The sparkline slot has no sizing helper; it's a fixed 56×24 box.
Try it: call stat({ label: "Pulls", value: 12, note: "3 waiting" }) inside <div class="stats">…</div>.
Approved. stat() escapes label, value, note, title and href through html. The tile is a real <a> when it navigates and a plain div otherwise. It uses only tokens, hover is limited to the link form, and the global :focus-visible outline covers keyboard focus. It works with JavaScript off, the sparkline is aria-hidden, and the fixed heights keep it from shifting layout. Nothing renders it yet, so the screenshots show no change. The tests cover the div and link forms and escaping, and the DESIGN.md row is present.
Two things I would do next:
- Make the heights survive text zoom. The 104px tile with overflow:hidden and fixed 18/32/36px rows will clip a note or label at 200% text size or on a 360px phone with a large system font. Use min-height or rem-based heights, or move the clamp to the note only.
- When the home page moves onto stat(), give each tile a way to read as "label: value" for a screen reader. Today it reads label then value as two bare spans, and the value's title attribute is not reliably announced. A visually hidden separator or a dl/dt/dd structure inside .stats would fix that.
§5 now has a stat tile:
stat()insrc/web/ui.ts, a.statsgrid insrc/web/style/components.ts, and a stat row in the DESIGN.md §5 table. I couldn't runbun x tsc --noEmithere.bun installand tsc both fail in this sandbox with "bun is unable to write files to tempdir: ReadOnlyFileSystem", so the forge's check is the first type check.What changed
stat({ label, value, title?, note?, href?, spark? }):- It draws a label, a
.numvalue with its full form intitle, an optional note clamped to two lines, and an optional inline SVG slot. - It renders as an<a>whenhrefis set, otherwise a<div>. - Label, value, note,titleandhrefgo throughhtml, so they are escaped.sparkis trusted markup the caller builds.-
.stathas a fixed 104px height and fixed label, value and note heights, so a changing value shifts nothing around it. -.statsis an auto-fit grid with a 160px minimum column. - Hover uses--border-strongand applies only to the link form. - Everything uses tokens. The value is 20px, because the design lint rejected 24px as off-scale.test/ui-stat.test.tscovers the tile with and withouthrefandnote, and<escaped in label, value, note andhref. It also checks that the grid is defined.git grepfound no existing.stat*class, so there is no collision.Checks run:
test/ui-stat.test.ts,test/web-ui.test.tsandtest/ui-sweep.test.tspass, andbun scripts/design-lint.tspasses.Left out: the home page still uses its old KPIs; moving it onto
stat()is the next issue. The sparkline slot has no sizing helper; it's a fixed 56×24 box.Try it: call
stat({ label: "Pulls", value: 12, note: "3 waiting" })inside<div class="stats">…</div>.Closes #3981