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

63 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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
```bash
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. **Record**`make 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: <datetime>"** so it's clear whether the snapshot reflects the latest design. The cloud-ahead check is backed by **llm-connect** (`make designbook-check``scripts/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.