Skip to content

feat(showcase): consume the shared design tokens in the preview generator - #291

Open
Spagero763 wants to merge 3 commits into
salazarsebas:mainfrom
Spagero763:feat/showcase-consumes-design-tokens
Open

feat(showcase): consume the shared design tokens in the preview generator#291
Spagero763 wants to merge 3 commits into
salazarsebas:mainfrom
Spagero763:feat/showcase-consumes-design-tokens

Conversation

@Spagero763

Copy link
Copy Markdown
Contributor

Closes #260

Split out of #289 at review request. That PR ships the tokens package; this one makes something actually consume it, which is #260's second definition-of-done item:

At least one real site/build consumes it and visibly reflects a token change when the package is updated.

Stacked on #289 — it will show only its own diff once that merges. Reviewing #289 first is the intended order.

Why the showcase generator

#260 names two consumers, "the documentation site and the showcase". The docs-site scaffold (#249) hasn't landed, so the showcase preview generator is the only real consumer that exists today. It is also the one with the problem #260 describes: its four renderers each declared their own slate palette, hand-copied between files, sharing nothing with docs/BRAND.md.

// tools/preview-gen/renderers/checkers.js, before
const COLORS = {
  bg: '#0f172a',
  p1_piece: '#f43f5e',      // rose-500
  p2_piece: '#38bdf8',      // sky-400
  ...

Four files, four copies, none of them the brand. That is the drift the package exists to stop.

What changed

Renderers now resolve colors, radii, and the font stack through a new tools/preview-gen/theme.js, which reads the built token values. The per-game accent hexes in generate.js are gone, so adding a fallback game no longer means picking a hex. tools/preview-gen/README.md's hardcoded palette table is replaced by the token exports.

The regenerated previews for tic_tac_toe, checkers, and battleship are included. They now read as Cougr rather than as default Tailwind slate.

The proof #260 asks for

Change one value, rebuild, regenerate:

$ sed -i 's/"dark": "#14100D"/"dark": "#000080"/' packages/tokens/tokens.json
$ cd packages/tokens && node build.js
$ cd tools/preview-gen && node generate.js tic_tac_toe

$ git diff examples/tic_tac_toe/preview.svg
-  <rect width="400" height="480" fill="#14100D" rx="12"/>
+  <rect width="400" height="480" fill="#000080" rx="12"/>

Reverted, rebuilt, regenerated, and the previews came back byte-identical.

Handling the un-committed dist/

#289 stopped committing packages/tokens/dist/, so a fresh clone has no token values for the renderers to import. generate.js now builds them on first run:

$ rm -rf packages/tokens/dist
$ node generate.js tic_tac_toe
Design tokens not built yet, building them first...
✓ Written dist/tokens.css
✓ Written dist/tokens.js
✓ Written preview: examples/tic_tac_toe/preview.svg

node generate.js <game> therefore still works exactly as the README documents, with no setup step to remember and no npm install. A second run skips the build.

This is why renderers are registered by path in RENDERERS and imported on demand rather than statically: they read BRAND at module load, so none of them can be imported before the tokens exist. Documented in the README so the next person adding a renderer doesn't reintroduce a static import.

One deliberate limit

Battleship's hit colors stay local to theme.js and are commented as such. docs/BRAND.md deliberately defines a small fixed vocabulary and has no damage hue; widening the brand palette for a case only the previews have would work against that intent. If a status palette is ever added to the brand, they move into the package.

Everything structural — background, surface, text, dividers, player accents, status badges, radii, type — comes from the tokens, which is what makes the showcase and the docs site share a palette by construction.

Validation

  • npm run gen:all, plus the no-argument and unknown-game paths, which still print usage and exit 1.
  • Cold-start path verified with packages/tokens/dist deleted, and the warm path verified not to rebuild.
  • All three regenerated previews parse as well-formed XML.
  • Token-change propagation verified end to end and reverted byte-identically, as shown above.
  • No Rust source touched. Changing examples/*/preview.svg does fire the Checkers and Tic Tac Toe example workflows, which passed on the combined branch in docs(patterns): on-chain / off-chain boundary guide + feat(design-system): shared versioned tokens package #289's first round.

Adds docs/ONCHAIN_OFFCHAIN_BOUNDARY.md, the guide recommended in
docs/strategy/04-onchain-gaming-research.md problem salazarsebas#1 and listed as the
second-priority Learn-section gap in the documentation architecture.

The guide gives a five-question framework for placing a single piece of game
state or logic, then works it through three shipped examples: battleship
(commitments on-chain, placement and Merkle tree off-chain), snake (a full
on-chain simulation that is turn-based rather than real-time, stated honestly),
and blind_auction (a proof standing in for data, with the Experimental maturity
of Groth16 verification named). Every claim is drawn from the current source of
the example it describes, including the shortcuts: battleship does not validate
fleet legality, and snake has no authorization and derives food placement from
the tick counter.

It closes with an explicit list of what Soroban throughput cannot support, so
the guide does not imply on-chain real-time gameplay is achievable. Resource
cost and privacy tiers are cross-linked to PERFORMANCE.md and PRIVACY_MODEL.md
rather than re-derived, and the Phase 2 GameHarness resource-reporting work is
referenced as planned rather than available.

Linked from the README documentation list, the PATTERNS.md problem table, and
a Related section in PERFORMANCE.md.
Adds packages/tokens (cougr-tokens), a versioned artifact encoding every value
defined in docs/BRAND.md so the documentation site and the showcase consume one
source instead of hand-copying values.

Format decision: tokens.json is the single hand-edited source, and a
zero-dependency build script emits both dist/tokens.css and dist/tokens.js. CSS
custom properties alone are not sufficient because some consumers need literal
values at generation time: anything producing a standalone artifact is consumed
outside a document, so custom properties declared by a host page never reach it.
Both outputs come from the same build, so they cannot disagree.

dist/ is generated, not committed. It is built by npm run build, and by the
prepare script on install and before pack/publish, so an installing consumer
gets built output without running the build and nothing in git can fall out of
step with the source.

Because there is no committed dist/ to diff against, --check no longer compares
build output. It now validates the source and a dry-run build, and fails when
tokens.json has drifted from docs/BRAND.md. That moves the guarantee to where
the risk actually is: BRAND.md is the declared source of truth and nothing
otherwise stops the two being edited apart.

Light and dark sets are complete for every themed token. Light is the default on
:root, dark applies under prefers-color-scheme unless the document opts out, and
an explicit data-theme attribute on the root element always wins.

Adds a path-filtered Design Tokens workflow that runs the drift check, builds,
loads the built module to confirm both modes resolve, and asserts dist/ is not
tracked.
…ator

The four preview renderers each declared their own slate palette, hand-copied
between files, which is exactly the drift packages/tokens exists to prevent.
They now resolve colors, radii, and the font stack through a new theme.js that
reads the built token values, and the per-game accent hexes in generate.js are
gone.

This is the end-to-end proof for the tokens package: changing color-bg in
tokens.json, rebuilding, and regenerating moves every preview background. The
regenerated previews are included.

packages/tokens/dist is generated rather than committed, so generate.js builds
it on first run and imports renderers on demand afterwards, keeping
`node generate.js <game>` working from a fresh clone with no setup step.
Renderers are registered by path rather than by static import, because they read
brand values at module load and so cannot be imported before the build.

Hit colors stay local to theme.js and are marked as such: docs/BRAND.md
deliberately defines a small fixed vocabulary with no damage hue, and widening
the brand palette for a case only the previews have would work against that.
Copilot AI review requested due to automatic review settings July 30, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Spagero763

Copy link
Copy Markdown
Contributor Author

@salazarsebas kindly review and merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(design-system): build a shared, versioned tokens package consumed by the docs site and showcase

2 participants