Files
whynot-design/.claude/rules/stack-and-commands.md
tegwick 7cf524137f feat(refresh): make designbook-refresh orchestrator + drift-triage runbook (WHYNOT-WP-0002 T09)
scripts/designbook-refresh.mjs chains the automatable steps
(check->pull->sync->ir->adapt-lit->parity) and stops for the human drift-triage
step, propagating the adapter-contract exit codes (3=stop for triage, 4=parity
fail). Gating steps call the node scripts directly so make doesn't collapse the
3/4 codes to 2. Best-effort steps (check/pull/sync) warn and continue; --no-pull
/--no-check/--no-parity flags. Documented the loop in stack-and-commands.md and a
step-6 drift-resolution runbook in designbook/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:19:44 +02:00

6.4 KiB
Raw Blame History

Stack

  • Language: ES modules (no build step, no transpile — source ships as-is).
  • Key deps: lit ^3 (web components, the one runtime dep); @playwright/test (the one dev dep, visual regression).
  • Components: shadow-DOM Lit elements that adopt a shared stylesheet so the Layer 1 .wn-* utility classes work inside the shadow root. src/elements/_styles.js is auto-generated from src/styles/components.css by scripts/sync-shared-styles.mjs — never hand-edit it.

Dev Commands

pnpm install
pnpm showcase                 # serve :4321 → /examples/showcase/  (every component)
pnpm example                  # serve :4322 → examples/whynot-control
pnpm test:visual              # Playwright visual-regression run (auto-starts a static server)
pnpm test:visual:update       # regenerate screenshot baselines after an intentional change
pnpm check                    # check-changelog: fails if CHANGELOG.md gained no entry vs main
npx playwright test -g "inbox"  # run a single visual test by name

make                          # list make targets (help is the default goal)
make designbook-sync          # after a /design-sync pull, record changes + last-sync time → RecentChanges.md
make designbook-check         # ask Claude Design (via llm-connect) if the cloud is newer; warn if mirror is stale
make ir                       # extract the technology-neutral IR (ir/) from designbook/  (React → IR)
make adapt-lit                # project IR onto Lit: regen tokens + scaffold stubs + drift reports (exit 3 on drift)
make parity-lit               # render every <wn-*> (Playwright) + assert contract/visual parity (exit 4 on fail)
make designbook-refresh       # the refresh loop: check→pull→sync→ir→adapt-lit→(drift triage)→parity. ARGS="--no-pull" etc.
make recent-changes           # regenerate RecentChanges.md (alias; ARGS="--range main..HEAD" supported)
make sync-styles              # = node scripts/sync-shared-styles.mjs

Keeping Lit current with the designbook — the refresh loop (WHYNOT-WP-0002)

make designbook-refresh is the single routine that re-propagates a cloud designbook change down to the Lit stack. It runs the automatable steps and stops for you when drift needs a human decision:

1 designbook-check  → has the cloud moved?            (best-effort: needs llm-connect)
2 designbook-pull   → pull React designbook→designbook/ (best-effort: needs `claude` binary)
3 designbook-sync   → record the diff → RecentChanges.md
4 ir                → re-extract ir/ (review the git diff — the blueprint change)
5 adapt-lit         → regen tokens, scaffold new stubs, emit drift reports
6 you resolve drift  ← STOP if step 5 exits 3 (adapters/lit/drift/*.md)
7 parity-lit        → confirm contract + visual parity

Exit codes propagate the adapter contract: 3 = stop for drift triage (step 6), 4 = parity failure. Steps 13 are best-effort (a network/claude/llm-connect gap warns and continues; the IR just re-extracts from the current mirror). After resolving drift, re-run make designbook-refresh --no-pull (via ARGS=) to re-check and reach parity. Drift resolution itself is governed by .claude/rules/designbook-propagation.md (fix the stack, or change the language in Claude Design and re-propagate — never a stack→React back-edit).

There is no unit-test suite — correctness is verified by full-page Playwright screenshot diffs of the two examples/ pages (tests/visual/ui-kit.spec.mjs, maxDiffPixelRatio: 0.005). Any visual change needs pnpm test:visual:update + baseline review.

Integrating the designbook

The designbook is the upstream atelier — the Claude Design project (cloud, claude.ai/design), source of truth for the language. Its local mirror lives in-repo at designbook/ (see designbook/README.md). Sync is agent-driven and incremental (one component at a time, never a wholesale replace):

  1. Pull — run make designbook-pull (scripts/designbook_pull.py). It drives the local claude binary headless (claude --print --permission-mode acceptEdits), which has the DesignSync tool over the claude.ai login, to fetch the React designbook and write it into designbook/; the file contents stay in that subprocess, so the pull is cheap regardless of size. Selection is governed by designbook/.design-pull.json (it excludes _whynot-design-seed/**, the cloud project's copy of this repo). The script stamps freshness itself on success. Note: the bundled /design-sync skill goes the other way — it pushes a repo up to Claude Design — so it does not populate designbook/; use make designbook-pull for the pull (see designbook-propagation.md).
  2. Recordmake designbook-sync runs scripts/designbook-sync.mjs, writing RecentChanges.md: a snapshot (not a log) of what changed across designbook/ + the derived surfaces (tokens/, src/styles/, src/elements/, examples/), grouped by layer.

Freshness is tracked in designbook/.design-sync.json (lastSyncAt, remoteUpdatedAt, projectId, projectName). Every report states "Last /design-sync: " so it's clear whether the snapshot reflects the latest design. The cloud-ahead check is backed by llm-connect (make designbook-checkscripts/check_designbook_staleness.py): it uses the claude-code adapter to ask the local claude binary for the project's current updatedAt via DesignSync.list_projects, then records it with node scripts/designbook-sync.mjs --remote-updated <iso>. Only the claude-code adapter can reach the user's Claude Design project (Gemini/OpenRouter/OpenAI cannot), and no secret goes in the prompt — DesignSync uses the claude.ai login (see credential-routing.md). The check needs llm_connect importable; the Makefile auto-selects ~/llm-connect/.venv/bin/python. Use --remote-updated <iso> to run the comparison offline/manually, or --fail-if-stale (exit 3) in automation. When remoteUpdatedAt is newer than lastSyncAt, the report and stdout warn that the local mirror is OUTDATED until the next /design-sync. If no sync was ever recorded, it warns that /design-sync has not run. The reporter is deterministic (built from git status/git diff), only writes the working tree, never commits, and never edits designbook/ content. Fold notable entries into CHANGELOG.md under ## [Unreleased] before releasing — RecentChanges.md is overwritten every run and is not the CI-enforced artifact.