# DS audit — DATA-VIZ & TABLE scope (plo-gto-hud-redesign v2/v1)

Read-only audit of `apps/artifacts/plo-gto-hud-redesign/{v2,v1}.html` vs `libs/ui/**` + the shipped page `apps/gto-wizard-web/src/pages/gto-hud/**`.

Two facts that invalidate several mock values up front:
- Radius mapping (`libs/ui/src/theme/tailwind-preset.ts:46-55`): `xxs`=2 `xs`=4 **`sm`=6** **`md`=12** `lg`=16 **`xl`=24**.
- Root font-size is responsive — **14px at ≤1920px**, 13px ≤1000px (`libs/styles/src/media_queries.scss:44-52`, loaded via `apps/gto-wizard-web/src/app.tsx:3`). DS `rem` classes are not 16px-based.

| # | Mock element (selector/line) | Mock value | DS truth (file:line) | Verdict | Exact fix |
|---|---|---|---|---|---|
| 1 | matrix grid template, v2:823 | inline `gridTemplateColumns='56px repeat(n,66px)'` (CSS comment v2:145 says 68px, spec §4 v2:483 says `4.25rem`) | `vs-matrix-panel.tsx:26-32` static `grid-cols-[3.5rem_repeat(n,4.25rem)]` → **49px / 59.5px** at 14px root | MISMATCH | Pick px and put it in the static class map: `grid-cols-[56px_repeat(n,68px)]`. Keep it a class — `vs-matrix-panel.tsx:20-24` says computed inline styles are counted by `tools/scripts/ds-coverage.mjs`. |
| 2 | `.vc-plot/.vc-col/.vc-bar`, v2:362-370,782-790 | hand-rolled column chart (div heights, `transition:height .25s`) | `composed-chart.tsx:1-13` imports only `Area, CartesianGrid, Line, ReferenceLine, Tooltip, XAxis, YAxis` — **no recharts `Bar`**; `types.ts:8-23` has no chart-type field | CANNOT-EXPRESS (custom justified) | Build a real component (`ColumnChart` in libs/ui, or page-local `PositionColumns`); don't label it "ComposedChart stand-in" (v2:172). Note it deletes `FamilySeriesCards` (`family-series-cards.tsx:24-31,118-150`). |
| 3 | `sparkline()`+`wireHover`, v1:700-772 | hand-rolled SVG area+line+crosshair+in-SVG readout | `ComposedChart`, already used for this exact card in `family-series-cards.tsx:118-150` (`showDataPoints`, `xAxisTicks:[0,5]`, `yAxisWidth={0}`, `tooltipTitle`) | HAND-ROLLED | `<ComposedChart data={...} showLegend={false} yAxisWidth={0} axisConfig={{xAxisTicks:[0,5],xAxisDomain:[-.5,5.5],yAxisDomain:[0,domain],yAxisTicks:[]}} metrics={[{key:'value',colorHex,areaFill:{type:'single',color},showDataPoints:true,renderValue}]}/>`. |
| 4 | sparkline strokes/dots/area/axes, v1:713-724 | line 1.6px; dots r2.6 no stroke, **per-position fill**; area `.26→0`; baseline `rgba(255,255,255,.08)`; axis text 9.5px `rgba(255,255,255,.34)`; cursor white line `.35`+white dot r3.6 | `composed-chart.tsx:110-135` `strokeWidth={1}`, dot `{r:3,stroke:paper.elevation3,strokeWidth:1,fill:metric.colorHex}`; `metric-area-gradient.tsx:18` `MAX_OPACITY=0.3` (0.3→0@50%→0.3); baseline = `ReferenceLine text.primary @0.4` + `CartesianGrid colors.divider`; `custom-x-axis-tick.tsx:22-33` tick `divider`, text 14px `text.secondary`; `chart-cursor.tsx:29-63` 1px rect, `text.secondary` gradient | MISMATCH | Delete the SVG (#3). Per-position dot colours are **not expressible** (one `colorHex` per metric) — drop them or add a `dot` render prop to the DS chart first. |
| 5 | `POS_COLOR`, v2:766 + claim v2:763-765 & spec v2:434 | UTG #E5B466 HJ #59BEB9 CO #3761BE BTN #6964EC SB #A05BCD BB #EF99EB, "the same palette the app's by-position legend uses" | Hexes are real (`colors.ts:290-303` `colorDots.{lightOrange,teal,blue,indigo,purple,pink}`), but **no position→colour mapping exists in the repo**. `colorDots` consumers: `notes-badges-tags-panel.tsx:53`, affiliates `coupon-tag-color.ts`, and `gto-hud-meta.ts:32` for one *stat*. gto-hud colours by **stat family** (`gto-hud-meta.ts:30-38`). Vue `getLabelColors` (`helpers/constants.ts:537-552`) is the user-label palette = `colors.ts:275-288 tagColors`, whose teal is **#5ABEB9 ≠ #59BEB9** | MISMATCH (claim false; palette is new) | Keep the hexes, but present it as a new convention: export `POSITION_COLORS: Record<GtoHudPosition,string>` from `gto-hud-meta.ts` off `colors.colorDots.*`, and get signoff. Don't cite `getLabelColors`/`tagColors`. |
| 6 | `.vc-pos`, v2:371,789 | 10px text painted in `POS_COLOR` | colorDots are swatch colours ("tuned for white text on the swatch", `colors.ts:275-278`); DS uses them as dots; `vs-action-panel.tsx:150-153` = `size-1.5 rounded-full` swatch + `text.secondary` label | MISMATCH (a11y) | On `paper.elevation2`: CO **3.11:1**, BTN **3.99:1** (AA small text = 4.5). Label stays `text.secondary`; hue lives in the 6px dot and the bar. |
| 7 | `.panel.e4` on matrix + vs panels, v2:118-119,816,849 | `paper4`, `border-radius:6px`, `p:16px` | `vs-matrix-panel.tsx:64-68`, `vs-action-panel.tsx:118-122`, reports `panel-n-bet-vs.tsx:95-98`: `<Paper elevation="3" className="rounded-md p-4">` | MISMATCH | `border-radius:12px` (`rounded-md`) + `paper.elevation3` #1B1B1D — or declare the elevation-4 change intentional (spec §3 v2:469 contradicts the code). |
| 8 | `.mat .cell`, v2:153 | `height:32px; radius 4px` | `vs-matrix-panel.tsx:170` `h-8 … rounded-sm` = 32px / **6px** | MISMATCH | `border-radius:6px`. (Reports' matrix cell is `StatTile` `h-[39px]` in `Table.Cell`, `stat-tile.tsx:86-90`; gto-hud's denser 32px grid is an intentional exception — say so in the spec.) |
| 9 | dark-text flip, v2:835 | `color:'#111'` | `vs-matrix-panel.tsx:176` `text-grey-900` → `colors.ts:76-84 grey.900 #212121` | NOT-A-TOKEN | `#212121`. |
| 10 | ramp legend `.ramp`/`.ramp .bar`, v2:154-156,818-820 | `gap:6px`; bar 40×8 `radius 2px`; no shrink | `vs-matrix-panel.tsx:78-97` `flex shrink-0 items-center gap-1` + `h-2 w-10 rounded-sm` | MISMATCH | `gap:4px`, radius `6px`, `flex-shrink:0`. |
| 11 | `.ph` on matrix panel, v2:120 | `margin-bottom:8px` | `vs-matrix-panel.tsx:70` `mb-3` (12px); vs-action is `mb-2`/8px (`vs-action-panel.tsx:124`) | MISMATCH | 12px bottom margin for the matrix header only. |
| 12 | no-spot bar, v2:857 | `style="background:var(--hatch)"` — shorthand kills the base fill; `--hatch` (v2:35) has no colour | `no-spot-track.tsx:15-19` `backgroundColor: alpha(text.primary,.02)` **plus** the `-45deg 3px/6px alpha(text.primary,.05)` image; consumed as `<NoSpotTrack className="h-3 rounded-xl"/>` (`vs-action-panel.tsx:104-106`) | MISMATCH | `background-color:rgba(255,255,255,.02); background-image:var(--hatch)`. The comment "gradients.hatch equivalent" is wrong — `gradients.ts:69` hatch is 8/9px @2%. |
| 13 | `.vc-none`, v2:369-370 | hatch + `border:1px rgba(255,255,255,.05)` + radius 2px | `no-spot-track.tsx:21-27` no border, default `rounded-sm` 6px | NOT-A-TOKEN | drop the border, radius 6px, add the 2% fill — i.e. render `<NoSpotTrack/>`. |
| 14 | column height floor, v2:783 | `Math.max(2,(v/dom)*100)` → a true **0.0** (LIMP UTG–BTN) still draws a bar | `lollipop-row.tsx:44-49,80-96`: null → hatch, 0 → on-axis dot; "null can never read as zero" | MISMATCH (data honesty) | `h = v === 0 ? 0 : (v/dom)*100`; keep the printed `0.0`. |
| 15 | selected-card tint, v2:358-359 (and `.card.on` v2:177) | `linear-gradient(180deg,rgba(170,251,178,.08),rgba(170,251,178,0) 62%)` | `family-series-cards.tsx:90` `bg-gradient-primary-vertical` = `gradients.ts:29-33` `180deg, .08 0%, .01 100%` (`tailwind-preset.ts:372`) | HAND-ROLLED | use `bg-gradient-primary-vertical`. |
| 16 | `.mbar`, v2:208-209 | `h 4px`, radius full, `bg paper5`, no transition | `bar.tsx:34-104` `Bar` has `color` (outer), `barColor` (inner), `config[{percentage,color,label}]`, segments already `transition-[width] duration-300 ease-sharp` | HAND-ROLLED | `<Bar className="h-1" barColor={colors.paper.elevation5} config={[{percentage,color,label}]}/>`. |
| 17 | `.ds-bar` label, v2:346 | `border-radius:12px` commented "rounded-xl" | `bar.tsx:96-104` outer `h-4 rounded-xl bg-grey-800`, inner `size-full overflow-hidden rounded-xl bg-grey-900`; `rounded-xl` = **24px**; `h-3` comes from the call site | MISMATCH (label only) | Geometry/colour/transition are correct; fix the annotation (24px; pill either way at 12px tall). Tooltips: `config[].label` via `Tooltip followCursor`, or `sharedTooltipLabel` — the mock's `title=` is a stand-in. |
| 18 | matrix row header `.mat .rlab`, v2:152 | no height | `vs-matrix-panel.tsx:157-163` `flex h-8 items-center` | MISMATCH | `height:32px`. |
| 19 | matrix/cards semantics, v2:821-838,352-359 | no `role`/`aria-label` | `vs-matrix-panel.tsx:100-171` `role=table/columnheader/rowheader/cell` + `aria-label`; same in `vs-action-panel.tsx:130-168` | MISMATCH | keep the ARIA table roles when porting the markup. |
| 20 | `.vcards`/`.vcard` metrics, v2:352-366 | `gap:10px`, `minmax(200px,240px)`, plot 96px, values `body4`/`text.secondary` | `family-series-cards.tsx:67,88` `flex flex-wrap gap-3` (12px), `min-w-60` (240px), `h-36` (144px); printed values are `body3`/`text.primary` (`lollipop-row.tsx:98-113`) | MISMATCH (cosmetic) | `gap:12px`, one documented plot height, `body3` for values. |
| 21 | spec §4 "Series cards", v2:484 | "252px wide, chart 230×104, `repeat(auto-fill,252px)`" — v1 leftover | v2 renders 200–240px / 96px (v2:352-370) | MISMATCH (stale spec) | delete that line or the implementer builds v1 geometry. |
| 22 | `D.groups[].color`, `GC`, v2:533,535 | `var(--amber)` — never declared | `--range` = `betting.range` #FF8F00 is what `FAM` uses (v2:549) | NOT-A-TOKEN (dead) | delete or use `var(--range)`. |
| 23 | `barsCard` value format, v2:785 | `v.toFixed(1)` | mock `fmt` v2:537 / `lollipop-row.tsx:34-41`: 100 prints `100` | MISMATCH (cosmetic) | reuse `fmt`. |

**Verified MATCH:** heat ramp `.12 + .68·v/max` on `alpha(primary.main,…)` (`vs-matrix-panel.tsx:14-19`) · ramp gradient endpoints · F/C/R `#3D7CB8/#5AB966/#F03C3C` = `betting.fold/call/raise.default` (`colors.ts:241-247`) + 6×6 swatch = `size-1.5 rounded-full` · segment `.3s cubic-bezier(.4,0,.6,1)` = `duration-300 ease-sharp` (`transitions.ts:24`) · grey.800/900 hexes · `.mat{gap:1px}` = `gap-0.25` · empty cell = `div.h-8` · `.vs-row` 38/8/8px, label `basis-24`, value `w-10` (`row-layout.ts:8-10`).

## (a) DS components/props the mock should use but doesn't
- `Bar` (`libs/ui/src/components/bar.tsx`) for readout micro-bars — `config[{percentage,color,label}]`, `color`, `barColor`, `includeTooltip`, `sharedTooltipLabel`, `tooltipDelay`.
- `NoSpotTrack` (`gto-hud/components/no-spot-track.tsx`) for **both** no-spot marks instead of two local hatches.
- `bg-gradient-primary-vertical` (selected card) and `animatedGradientClassName()` (`animated-gradient-background-styles.ts:63-72`) for selected rows — `.row.on` is a static copy of `gradients.primary.horizontal()`.
- `ComposedChart` + `ComposedChartLoader` + `lazy()` for any line/area (v1), exactly as `family-series-cards.tsx:24-31`.
- `Paper elevation` + `rounded-md p-4` chrome; `Typography variant="body3|body4" color="text.secondary"` instead of `.t-*` classes.
- If the matrix should follow reports: `Table.Container/Root/Head/Body/Row` + `ReportsTableCell` + `StatTile` (`table.styles.ts:6-33`, `reports-table-cell.tsx:15`, `stat-tile.tsx:86-90`, `panel-n-bet-vs.tsx:109-140`) — the only DS table primitive; gto-hud's CSS grid is a deliberate exception and the spec should state it.

## (b) Mock values not traceable to a token
`66px` matrix track · `#111` cell text · `.ramp gap:6px` + 2px ramp radius · `.vc-bar max-width:22px` + the `Math.max(2,…)` floor · `.vc-none` 1px `rgba(255,255,255,.05)` border · `.vcards gap:10px` · v1 `stroke-width 1.6`, `r 2.6`, gradient `.26`, baseline `rgba(255,255,255,.08)`, axis `9.5px`/`.34`/`.62`, cursor `.35` + `#fff` dot, `230×104` / `96px` plots · `--amber` (undefined) · `rounded-[8px]` rows (arbitrary, but pre-existing in the app).

## (c) What the DS genuinely cannot express (needs custom)
1. **Vertical column charts** — `ComposedChart` renders only Area/Line/Grid/ReferenceLine and no bar-chart component exists in `libs/ui`. The v2 cards need a new component (per-column colour, printed value, hatched no-spot column, honest 0).
2. **Per-datapoint dot colours** in a series — one `colorHex` per metric; `dot`/`activeDot` hard-coded (`composed-chart.tsx:110-135`). Needs a `dot` render prop before v1's coloured dots are buildable.
3. **The alpha heat matrix** — no DS heat-cell/scale primitive; the ramp is page-local (`vs-matrix-panel.tsx:14-19`) and reports use flat-tint `StatTile` instead. Fine to keep page-local; not a DS component.
4. **The no-spot hatch** — `gradients.hatch` is a different pattern (8/9px @2%); `NoSpotTrack` is page-local by design (`no-spot-track.tsx:8-11`).
