feat(consumer): versioned IR manifest + drift-check (WHYNOT-WP-0003 T03-T07,T09)
Make ir/ the unit of versioned downstream consumption so consuming repos can pin a version, inspect it, and follow changes at their own pace. - T03 ir/manifest.json: per-version inventory + diff anchor with deterministic sha256-over-canonicalised-JSON hashes; no-churn generatedAt; manifest schema. - T07 ir/INDEX.md: human-readable catalog generated by make ir. - T04 .whynot-design.lock sync-point format + lock schema. - T05 npx @whynot/design drift: consumer drift-check (bin entry), exit 0/2/3, --json/--update/--manifest/--version/--lock. - T06 CONSUMING.md guide + examples/consumer-fixture/ runnable demo; README + MultiFrameworkSupport cross-links; fix README version pin (@0.3.0 not @v0.3.0). - T09 CONSUMER_CONTRACT_PARITY.md design-only note (live-UI parity deferred). T02 (publish) and T08 (showcase, blocked on WP-0002 T11) remain wait. Repo stays in dev mode; no outward publish performed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
150
CONSUMING.md
Normal file
150
CONSUMING.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Consuming whynot-design from another repo
|
||||
|
||||
whynot-design is the **upstream visual reference** for other repos. It is a
|
||||
development reference and demo platform — it does not run as a production
|
||||
workload and handles no critical data. Consuming repos build their production
|
||||
UIs *from* it, and follow up on changes **at their own pace** — they are never
|
||||
force-synced.
|
||||
|
||||
A consumer tracks the **IR** (`ir/`), not the Lit internals. The IR is the
|
||||
technology-neutral contract: per-component contracts (`ir/components/*.json`),
|
||||
W3C-DTCG tokens (`ir/tokens.json`), exemplars, and the version anchor
|
||||
`ir/manifest.json`. Three moves make this work:
|
||||
|
||||
1. **Pin** a version — the package + your lockfile.
|
||||
2. **Inspect** it — `ir/INDEX.md` (browsable) + `ir/manifest.json` (machine).
|
||||
3. **Get a grip on changes** — `npx @whynot/design drift`.
|
||||
|
||||
This is the inverse of whynot-design's own upstream machinery
|
||||
(`Claude Design → designbook/ → ir/`), now pointed downstream
|
||||
(`ir/ → your repo`). It is **one-way**: you read the IR; you never write back.
|
||||
|
||||
---
|
||||
|
||||
## 1. Pin a version
|
||||
|
||||
`@whynot/design` is published to the coulomb Gitea npm registry. Pin an exact
|
||||
tagged version; your lockfile becomes the real pin.
|
||||
|
||||
```bash
|
||||
# .npmrc in your repo (see PUBLISHING.md for the read-token routing)
|
||||
# @whynot:registry=https://gitea.coulomb.social/api/packages/coulomb/npm/
|
||||
|
||||
npm i @whynot/design@0.3.0 lit
|
||||
```
|
||||
|
||||
`lit` is a **peer dependency** — install it alongside so your bundler dedupes to
|
||||
a single `lit` instance.
|
||||
|
||||
## 2. Inspect what you pinned
|
||||
|
||||
No clone, no build needed:
|
||||
|
||||
- **`node_modules/@whynot/design/ir/INDEX.md`** — human-readable catalog: every
|
||||
component, its tag, props/variants/slots/events, and a link to its exemplar.
|
||||
- **`node_modules/@whynot/design/ir/manifest.json`** — the machine inventory:
|
||||
`designVersion`, a `tokensHash`, and a content `hash` per component.
|
||||
|
||||
## 3. Adopt a sync-point
|
||||
|
||||
Record which IR state your repo has reconciled against. Run once, in your repo root:
|
||||
|
||||
```bash
|
||||
npx @whynot/design drift --update
|
||||
```
|
||||
|
||||
This writes **`.whynot-design.lock`** — commit it. It is the consumer-side mirror
|
||||
of whynot-design's own `designbook/.design-sync.json`.
|
||||
|
||||
### `.whynot-design.lock` format
|
||||
|
||||
```json
|
||||
{
|
||||
"designVersion": "0.3.0",
|
||||
"adoptedAt": "2026-06-27T17:31:08.640Z",
|
||||
"manifestSchemaVersion": "1.0.0",
|
||||
"manifestHashes": {
|
||||
"tokens": "sha256:426f565a9ce6c36f",
|
||||
"components": {
|
||||
"Button": "sha256:4a32713049e433dd",
|
||||
"TopNav": "sha256:32ebc6e46db38f93"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| field | meaning |
|
||||
| --- | --- |
|
||||
| `designVersion` | the `@whynot/design` version you adopted |
|
||||
| `adoptedAt` | when you adopted it (first adopt, or last `drift --update`) |
|
||||
| `manifestSchemaVersion` | the manifest's shape version; a mismatch warns that hashes may not be directly comparable |
|
||||
| `manifestHashes.tokens` | adopted value of the manifest `tokensHash` |
|
||||
| `manifestHashes.components` | adopted content hash per component |
|
||||
|
||||
**Lifecycle:** created on first `drift --update`, advanced only by a later
|
||||
`drift --update`. Nothing else writes it. Schema: `ir/schema/lock.schema.json`.
|
||||
|
||||
## 4. Follow up at your own pace
|
||||
|
||||
When you bump `@whynot/design` (or just want to know what moved), run:
|
||||
|
||||
```bash
|
||||
npx @whynot/design drift
|
||||
```
|
||||
|
||||
It compares your adopted `.whynot-design.lock` against the installed package's
|
||||
`ir/manifest.json` and reports **added / changed / removed** components plus
|
||||
whether **tokens** changed:
|
||||
|
||||
```
|
||||
whynot-design drift
|
||||
adopted: 0.3.0 (2026-06-27T17:31:08.640Z)
|
||||
target: 0.4.0 (2026-07-10T09:02:11.400Z)
|
||||
|
||||
Tokens: changed
|
||||
Components: +1 added · ~1 changed · -0 removed · 11 total
|
||||
+ Banner
|
||||
~ Button
|
||||
|
||||
Drift detected vs your adopted sync-point.
|
||||
Adopt this version: npx @whynot/design drift --update
|
||||
```
|
||||
|
||||
Then, when *you* are ready, review the changed contracts in `ir/INDEX.md`, update
|
||||
your UI, and adopt the new sync-point:
|
||||
|
||||
```bash
|
||||
npx @whynot/design drift --update
|
||||
```
|
||||
|
||||
### Exit codes (CI-friendly)
|
||||
|
||||
Mirrors the adapter contract (`adapters/ADAPTER_CONTRACT.md`):
|
||||
|
||||
| code | meaning |
|
||||
| --- | --- |
|
||||
| `0` | in sync — your lock matches the target |
|
||||
| `2` | usage / config error (bad flag, missing/invalid manifest or lock) |
|
||||
| `3` | **drift detected** — something changed since your sync-point |
|
||||
|
||||
Add `--json` for automation. Useful flags: `--manifest <path>` (diff against an
|
||||
explicit manifest, e.g. a fetched newer version on disk), `--version <x.y.z>`
|
||||
(assert the resolved manifest is that version — guards against the wrong install),
|
||||
`--lock <path>` (non-default lock location).
|
||||
|
||||
> **No network, no writes to the package.** `drift` reads only the
|
||||
> already-installed package + your lock, and the only file it ever writes is your
|
||||
> repo's `.whynot-design.lock`.
|
||||
|
||||
---
|
||||
|
||||
## Try the full loop now
|
||||
|
||||
A copy-pasteable fixture lives at
|
||||
[`examples/consumer-fixture/`](./examples/consumer-fixture/) — it exercises
|
||||
pin → inspect → drift → update against a fixed version without needing a real
|
||||
install. See its `README.md`.
|
||||
|
||||
See also: [`README.md`](./README.md) *Tracking whynot-design* ·
|
||||
[`MultiFrameworkSupport.md`](./MultiFrameworkSupport.md) ·
|
||||
[`ir/SCHEMA.md`](./ir/SCHEMA.md).
|
||||
Reference in New Issue
Block a user