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 before | app 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.cssandtokens.tsare generated fromtheme.tsand 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.
-
/gamessearches the catalogue, server-rendered, and every result links to its page. - The hardcoded “Browse a game” link is gone.
-
astro devstill works.
Verification
pnpm tokens && git diff --exit-code # generated output is committednode scripts/web-tokens.mjs # drift + raw valuespnpm --filter @tabletop/web test # 25 testspnpm --filter @tabletop/web dev # then / , /games?q=catan , /games/catanThe 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.