Skip to content

WI-066: A web view that matches the app, and a way in

WI-066: A web view that matches the app, and a way in

Problem

The web view looked clunky and inconsistent next to the app, and neither half of that was an accident.

Inconsistent, because nothing ever checked. check-design-tokens.mjs scans apps/mobile only, so apps/web sat outside it and accumulated ten hand-written hex values in Base.astro — the pre-WI-046 cold blue-greys that WI-046 rejected for reading “like software”:

web beforeapp since WI-046
ink#1f2933#2a2521
canvas#f5f7fa#f7f4ef
line#e4e7eb#e8e1d7
links#1f6feb#2f6b4f

It was half-migrated — theme-color and two values had been copied by hand — and had no line heights beyond a blanket 1.5, which WI-046 called “the most common reason an interface reads as unfinished”.

Clunky, because the site was a stub. Three routes, a home page of marketing copy, and a nav whose second link was a hardcoded /games/catan labelled “Browse a game”. searchGames() had been written since WI-061 and was reached by no page: there was no way to find a game on the site.

In scope

  • Generate the app’s tokens into the web as CSS custom properties (ADR-0025), and fail the gate on drift or on a raw value under apps/web/src.
  • One deliberate divergence: a larger display size above 48rem.
  • Restyle the layout, the home page, the game page and the 404 against them.
  • /games — search, server-rendered, no island — and a search box in the nav.

Out of scope

  • Anything signed-in. The site is public reads only; personal views on the web are a separate decision, and a large one.
  • Caching catalogue reads. Every page view is currently an API call, which is a real question now the site has more than one route — but it is a decision with a cost, not a tidy-up.
  • apps/mobile, untouched.

Definition of done

  • tokens.css and tokens.ts are generated from theme.ts and committed.
  • The gate fails when they drift, and when a raw colour or font size appears under apps/web/src.
  • Every page uses the tokens; no hand-written colour or size remains.
  • The desktop display step exists, and is the only difference between the two scales.
  • /games searches the catalogue, server-rendered, and every result links to its page.
  • The hardcoded “Browse a game” link is gone.
  • astro dev still works.

Verification

Terminal window
pnpm tokens && git diff --exit-code # generated output is committed
node scripts/web-tokens.mjs # drift + raw values
pnpm --filter @tabletop/web test # 25 tests
pnpm --filter @tabletop/web dev # then / , /games?q=catan , /games/catan

The check earns its place immediately: run against Base.astro as it was, it reports all sixteen raw values, with file and line.

Notes

astro dev broke on this and the cause was not obvious. It does resolve cloudflare:workers, and the service binding it provides points at a Worker that is not running — it answers 503, so every catalogue page 500’d, while wrangler dev was fine. Reaching for the binding is now gated on import.meta.env.PROD, with a regression test.

ADR-0024 had claimed the opposite, as measured. It was not measured — the wrangler dev case was, and the conclusion was generalised to astro dev without checking. The ADR now carries a dated correction rather than a quiet edit.

Falling back to the URL when the binding errors was considered and rejected: a real 503 from the real API would then be retried against the public URL, which is the kind of fallback that hides an outage.