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>
63 lines
6.4 KiB
Markdown
63 lines
6.4 KiB
Markdown
## 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 1–3 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.
|