Files
whynot-design/workplans/WHYNOT-WP-0002-designbook-stack-adapters.md
tegwick 92877c4a65 chore(visual): gitignore snapshot baselines; sync workplan task IDs
Visual baselines are large binary test artifacts; keep them out of the repo.
They stay on disk locally (regenerate with `pnpm test:visual:update`); the
infra fixes that make rendering deterministic remain committed. Also folds in
the state_hub_task_id writeback from `make fix-consistency`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:19:29 +02:00

310 lines
12 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.
---
id: WHYNOT-WP-0002
type: workplan
title: "Technology-neutral designbook with stack adapters (Lit reference)"
domain: infotech
repo: whynot-design
status: proposed
owner: claude
topic_slug: custodian
created: "2026-06-22"
updated: "2026-06-22"
state_hub_workstream_id: "0a3511c1-1771-438b-9364-104d8f0de2f8"
---
# Technology-neutral designbook with stack adapters (Lit reference)
## Problem
Claude Design's designbook is **React-bound** (its `/design-sync` converter generates
React components and React-rendered previews; a non-React DS "has nothing for the design
agent to build with"). That defeats the idea of a designbook as a *technology-neutral UI
blueprint*. coulomb needs to stay open to different UI stacks (Lit today, others later)
while still using one Claude designbook for a consistent appearance.
## Approach
Keep React as the **interaction surface** with Claude Design (use the recommended React
designbook — it is what the tool supports), but introduce a **technology-neutral
intermediate representation (IR)** as the actual blueprint, and **per-stack adapters** that
project the IR onto each stack. Lit is the first/reference adapter. Adapters are
**scaffold + drift-detect**, not full behavioral codegen: tokens are fully generated,
new components get stubs, changed components get a drift report (never an overwrite), and
visual parity is checked against the designbook's own exemplars. Behavior stays
hand-authored per stack.
```
Claude Design (React designbook — canonical authoring surface)
│ /design-sync [exists]
designbook/ (local React mirror: components/<group>/<Name>/{.d.ts,.prompt.md,.html},
│ tokens/, styles.css, _ds_bundle.js, _ds_manifest.json)
│ make ir → ir/extract (the pivot)
ir/ (technology-neutral blueprint, committed + diffable)
│ tokens.json (W3C Design Tokens format)
│ components/<Name>.json (contract: props/attrs, variants, slots, events, docs, exemplar)
│ exemplars/<Name>.{png,html} (reference render from the designbook preview)
│ make adapt-lit → adapters/lit
adapters/lit/ (reference) tokens → CSS custom props (full gen)
new component → <wn-*> stub + contract scaffold
changed component → DRIFT REPORT (no overwrite)
make parity-lit → visual + contract parity vs exemplar
```
**Directionality is one-way: React → IR → stacks.** Lit-side changes do **not** flow back
automatically; a change to the shared language must be made in Claude Design (React) and
re-propagated. The Lit components remain hand-authored; the adapter keeps their *contract
and appearance* aligned, it does not own their behavior.
## Builds on (already in this repo)
- `designbook/` — local mirror of the Claude Design project + `designbook/README.md`.
- `make designbook-sync` / `RecentChanges.md` — record what a sync changed.
- `make designbook-check` + `scripts/check_designbook_staleness.py` — llm-connect backend
that detects when the cloud designbook moved ahead (the refresh trigger).
- `tokens/*.json`, `src/styles/*.css`, `src/elements/*.js` — the existing Lit DS, which
becomes the first adapter's target/baseline.
- Playwright visual harness (`tests/visual/`) — extended for parity in Phase 4.
---
## Phase 0 — Contracts & governance
Establish the durable interfaces before any code, so future stacks slot in cleanly.
## Define the IR schema
```task
id: WHYNOT-WP-0002-T01
status: done
priority: high
state_hub_task_id: "66187d76-3755-4204-ad71-d9fae8ed38ac"
```
Specify the technology-neutral blueprint. Tokens: adopt the **W3C Design Tokens Community
Group** JSON format (`$value`/`$type`) so the token layer is a standard, not a bespoke
shape — `tokens/*.json` already holds the values. Component contract (`ir/components/<Name>.json`):
name, group, description, props (name/type/enum/default/required), **prop→attribute mapping**
(React camelCase prop → Lit attribute/property), slots, events, variant axes, `docsRef`,
`exemplarRef`. Write `ir/SCHEMA.md` + a JSON Schema for validation. Decide and document that
`ir/` is committed (diffable blueprint).
## Define the adapter interface
```task
id: WHYNOT-WP-0002-T02
status: done
priority: high
state_hub_task_id: "6fe8481c-02b3-407d-9b7b-c47f161c0dcd"
```
Document the contract every stack adapter implements so Vue/Angular/plain-CSS adapters are
drop-in: inputs (`ir/`), outputs (generated artifacts into the stack's source tree, a
machine-readable **drift report**, a **parity result**), idempotency rules (regenerate tokens
+ stubs; never overwrite hand-authored behavior), and exit codes for CI. Write
`adapters/ADAPTER_CONTRACT.md`.
## Governance & propagation doc
```task
id: WHYNOT-WP-0002-T03
status: done
priority: medium
state_hub_task_id: "97aadf8a-4d56-47d0-b841-d664d0676a53"
```
Document directionality (React canonical, one-way to stacks, Lit changes round-trip through
Claude Design), the drift-resolution workflow (who fills behavior, how a drift report is
triaged/closed), and how this extends the existing atelier→repo pipeline. Update
`DesignSystemIntroduction.md` (§5 propagation) and add a rule under `.claude/rules/`.
---
## Phase 1 — Canonical React designbook source
## Establish the React designbook as canonical
```task
id: WHYNOT-WP-0002-T04
status: done
priority: high
state_hub_task_id: "ed4dd1d4-f649-40f0-83f5-9cbd88622a7b"
```
The pivot needs a real React source in Claude Design (the current project holds the
hand-authored web-component experiment, not a React component bundle). Decide how the
canonical React designbook is established and maintained: author a React expression of the
`wn-*` component set in Claude Design (native, `/design-sync`-compatible), tokens-first then
grow components, or adopt an existing React kit. Define how it is pulled into `designbook/`.
**Risk/dependency**: this is the precondition for IR extraction; scope it deliberately —
a minimal token-plus-core-components React designbook is enough to prove the pipeline.
---
## Phase 2 — IR extraction (the pivot)
## Build the IR extractor
```task
id: WHYNOT-WP-0002-T05
status: done
priority: high
state_hub_task_id: "dcdc0b01-756f-4253-9599-e5d5dfbe1083"
```
Build `scripts/ir-extract.mjs`: read the `designbook/` React mirror — `.d.ts` (props),
`.prompt.md` (docs), `_ds_manifest.json` (groups), `tokens/` (values), preview `.html`
(exemplar render) — and emit `ir/tokens.json` (W3C format), `ir/components/<Name>.json`
(per T01 schema), and `ir/exemplars/<Name>.*`. Validate output against the JSON Schema from
T01. Add `make ir`. `ir/` is committed so a re-extract surfaces blueprint changes as a git diff.
---
## Phase 3 — Lit reference adapter
## Token generation (full gen)
```task
id: WHYNOT-WP-0002-T06
status: done
priority: high
state_hub_task_id: "a106c673-e849-4d06-91c9-3f7f63fec2ea"
```
`adapters/lit/`: generate CSS custom properties from `ir/tokens.json` into
`src/styles/colors_and_type.css` (the existing token layer). Fully generated + deterministic;
re-running is a no-op when nothing changed. Add `make adapt-lit` (tokens portion first).
## Component scaffold + drift report
```task
id: WHYNOT-WP-0002-T07
status: todo
priority: high
state_hub_task_id: "00ed1aff-7724-4e90-9a51-fd58699480ca"
```
Extend `adapters/lit/`: for an IR component with no `wn-*` counterpart, generate a Lit stub
(`<wn-*>` skeleton + reactive properties from the contract's prop→attribute map + a TODO for
behavior). For an existing component, **emit a drift report** (`adapters/lit/drift/<Name>.md`):
prop/attribute mismatches, missing/extra variants, removed props — **never overwrite the
hand-authored element**. Map React prop types → Lit property declarations. Wire into
`make adapt-lit`.
---
## Phase 4 — Parity verification
## Contract + visual parity
```task
id: WHYNOT-WP-0002-T08
status: todo
priority: medium
state_hub_task_id: "1f52ca1f-64a6-4643-992f-f0b4812461a0"
```
`make parity-lit`: (a) **contract parity** — assert each `wn-*` element's observed
attributes/properties match its IR contract (fail on drift); (b) **visual parity** — render
the Lit component and diff against `ir/exemplars/<Name>` using the existing Playwright harness,
emit a parity diff. Produce a single parity result per the adapter contract (T02). This is the
gate that confirms Lit actually matches the designbook appearance.
## Fix showcase page render hang (visual-baseline gate)
```task
id: WHYNOT-WP-0002-T11
status: todo
priority: medium
state_hub_task_id: "7435338d-702a-43d7-9c86-49531fe0d8e4"
```
Discovered 2026-06-26 while regenerating visual baselines after the T06 token
regen. The `examples/showcase/index.html` "every component" page wedges the
renderer main thread when its module executes — the page never reaches `load`
and no `showcase.png` baseline can be captured (it has never existed). Isolated:
the components + the vendored lit bundle render fine in a minimal page with a
mounted `<wn-button>`, so the loop is triggered by a *specific demo composition*
on the showcase page, not by lit or the element classes. The four
`examples/whynot-control` baselines are unaffected and pass deterministically.
The showcase test is marked `test.fixme` in `tests/visual/ui-kit.spec.mjs` until
this is fixed — remove `.fixme` and regenerate the baseline once the looping
component/usage is found (bisect the showcase demos).
Related fixes landed alongside this discovery (same commit): `serve.json`
(`cleanUrls:false` — serve was 301-redirecting `index.html` and breaking every
relative asset); corrected the whynot-control token stylesheet link
(`../../colors_and_type.css``../../src/styles/colors_and_type.css`); vendored
lit as `examples/vendor/lit.js`; and aborted the unused Google-Fonts CDN in the
visual tests for determinism.
---
## Phase 5 — Keep-up-to-date instruction set
## Refresh loop + runbook
```task
id: WHYNOT-WP-0002-T09
status: todo
priority: high
state_hub_task_id: "07e60a34-0c62-4f8b-848b-d3b8d4292a18"
```
Wire and document the end-to-end refresh sequence so keeping Lit current against the React
designbook is one routine:
1. `make designbook-check` — detect the cloud designbook moved (existing llm-connect backend).
2. `/design-sync` — pull the latest React designbook into `designbook/`.
3. `make designbook-sync` — record the diff (`RecentChanges.md`).
4. `make ir` — re-extract the IR; review the `ir/` git diff (the blueprint change).
5. `make adapt-lit` — regenerate tokens, scaffold new components, emit drift reports.
6. Resolve drift — fill/adjust Lit behavior guided by `adapters/lit/drift/*.md`.
7. `make parity-lit` — confirm appearance + contract parity.
Add a `make designbook-refresh` orchestrator for the automatable steps (15, 7) and a
human-in-loop runbook for step 6. Document in `.claude/rules/stack-and-commands.md` and
`designbook/README.md`.
---
## Phase 6 — Generalization hook (prove extractability)
## Second-adapter smoke (validate the boundary)
```task
id: WHYNOT-WP-0002-T10
status: todo
priority: low
state_hub_task_id: "483de131-f580-4031-85df-72cf70a45679"
```
Sketch a minimal second adapter (plain-CSS utility classes or a Vue stub) that consumes the
same `ir/` and implements the T02 contract — just enough to prove the IR/adapter boundary
holds and the architecture can be lifted into a coulomb-level tool later. Do not finish the
second stack; the deliverable is confidence in the seam.
---
## Open questions / risks
- **React designbook bootstrap (T04)** is the critical-path dependency — without a real React
source, the IR has nothing to extract. Keep its initial scope minimal.
- **Prop→attribute fidelity**: React props (objects, render props, callbacks) don't all map to
Lit attributes; the contract must mark non-portable props and the adapter must surface them
as drift, not silently drop them.
- **Exemplar parity tolerance**: web-component vs React rendering will differ at the pixel
level; parity needs a sensible diff threshold (reuse `maxDiffPixelRatio` discipline).
- **One-way constraint**: ensure the tooling never tempts a Lit→React back-edit that bypasses
Claude Design; governance doc (T03) must make the round-trip explicit.
## Registering this workplan
After review, register the workstream from `~/state-hub`:
```bash
make fix-consistency REPO=whynot-design
```