mirror of
https://github.com/Piebald-AI/claude-code-system-prompts.git
synced 2026-06-13 14:43:33 +08:00
303 lines
52 KiB
Markdown
303 lines
52 KiB
Markdown
<!--
|
||
name: 'Skill: Design sync Storybook source shape'
|
||
description: Design sync sub-skill instructions for using a repo's Storybook as the fidelity oracle when building, validating, matching, uploading, and re-syncing component previews
|
||
ccVersion: 2.1.172
|
||
-->
|
||
# Storybook source shape
|
||
|
||
Storybook is the **fidelity oracle, not the runtime**. The converter bundles the package's compiled `dist/` into `_ds_bundle.js` — the same bundle the claude.ai/design agent builds with — and generates each preview by **compiling the story source module itself** (hooks, fixtures, local helpers — the whole closure comes along), with every component import resolved to that shipped bundle (`lib/story-imports.mjs` redirects package *and* relative component imports to `window.<Global>`). The repo's own storybook render is the ground truth those previews must match: a compare harness screenshots each story in the reference storybook and the matching preview render side by side, and you iterate until they match. Nothing from storybook-static is uploaded, and no story code is ever evaluated at build time — stories run only in the browser, against the real artifact.
|
||
|
||
|
||
Requires React 18+. Playwright + chromium are **required** for this shape (the compare loop is the verification), not optional.
|
||
|
||
**First sync or re-sync?** A re-sync is marked by a config whose `projectId` and `pkg` were both in place before this run started — most of this document then doesn't apply; go to §7, where one driver run routes the work and untouched components cost nothing. Everything else takes the full flow (§2 build → §3 self-heal → §4 match → §6 upload), where every component gets verified and graded once — that includes a partial config left by an aborted run, and a pin this run itself just recorded in the base skill's §1. (Only the old `design-sync.config.json` present? Move it first and commit: `mkdir -p .design-sync && mv -n design-sync.config.json .design-sync/config.json`, then apply the same test.)
|
||
|
||
## 2. Build, then run the converter
|
||
|
||
1. **Build the DS package *and its workspace dependencies*.** The converter bundles `dist/` into `window.<Global>`. Run `<pm> run build`; in a monorepo use `turbo run build --filter=<pkg>` or `pnpm -F "<pkg>..." build` (the trailing `...` is required — bare `-F <pkg>` skips dependencies and you'll see `Cannot find module '@scope/tokens'`). If `package.json` `module`/`exports['.']` points at TS source, find the actual built entry and pass it via `--entry`. **Do this before step 2** — storybook often imports sibling packages from their built `dist/`.
|
||
2. **Build the reference storybook ONCE into `.design-sync/sb-reference/`** — NOT under `ds-bundle/` (the converter wipes `--out` on every rebuild, and storybook builds take minutes; the reference must survive the fix loop):
|
||
|
||
```bash
|
||
npx storybook build -c <storybookConfigDir> -o .design-sync/sb-reference
|
||
```
|
||
|
||
Run it from the directory whose `package.json` has the storybook devDependencies — usually the one containing `.storybook/`; monorepos often have several storybooks, so pick the one covering the package you're syncing. **Make `-o` the repo-root path** (e.g. `-o "$(git rev-parse --show-toplevel)/.design-sync/sb-reference"`): the converter and compare resolve `.design-sync/` from the repo root, so a cwd-relative `-o` in a subpackage puts the reference where nothing will find it. Use `npx storybook build` directly, **not** the repo's `npm run build-storybook` script (wrong output dir). Then check `.design-sync/sb-reference/iframe.html` exists and is >10KB — `index.json` alone can exist with a failed build.
|
||
|
||
Long builds: background them **through your shell tool's background mode only** and wait for the completion notification. Never a bare `&` (untracked — the notification never comes), and never a `pgrep -f '<script>'` poll loop (it matches its own command line and spins to timeout).
|
||
|
||
`.gitignore` additions: `.design-sync/sb-reference/`, `.design-sync/learnings/`, `.design-sync/.cache/`, `.ds-sync/`, `ds-bundle/` — build artifact, transient scratch, verification working state, staged scripts, regenerated output. Committed: `previews/` (your authored files ONLY — generated story-module wrappers live in `.design-sync/.cache/previews/` and regenerate every build; the converter never writes or deletes anything in `previews/`) and `NOTES.md`. Verification state is never committed — cross-machine carry-forward comes from the uploaded project's `_ds_sync.json`. Rebuild the reference only when stories or the DS source change.
|
||
3. **Write `.design-sync/config.json`** — only `pkg` and `globalName` required. **If it already exists, read it first and keep what's there** — `titleMap`, `overrides`, and `provider` accumulate fixes from prior syncs. Also Read `.design-sync/NOTES.md` first — its **Re-sync risks** section is the prior run's watch-list; re-verify those items instead of assuming carry-forward covers them. The package-shape field table in `../non-storybook/SKILL.md` §2.6 applies verbatim; the fields that matter most here:
|
||
|
||
| Field | Value |
|
||
|---|---|
|
||
| `pkg` / `globalName` | `pkg` required; `globalName` auto-derived from it when omitted |
|
||
| `shape` | `"storybook"` — pins detection |
|
||
| `storybookStatic` | `".design-sync/sb-reference"` — so re-syncs and compare find the reference without flags |
|
||
| `storybookConfigDir` | the `.storybook/` dir (monorepos) |
|
||
| `buildCmd` | what to re-run before the converter on re-sync |
|
||
| `titleMap` | `{title: ExportName}` when story titles don't match export names; `{title: null}` excludes a non-visual/internal component from the sync entirely |
|
||
| `overrides` | `{<Name>: {skip: [storyIds], cardMode: "single", primaryStory: "<Export>", viewport: "WxH"}}` — `skip` for stories that can't render statically; the card keys for overlay components (§4a.5, §5) |
|
||
| `provider` | usually unnecessary for **previews** — `.storybook/preview` decorators are auto-bundled; set only when that fails. Before §6 upload, distill decorator-provided context into `cfg.provider` — README/prompt.md wrap guidance is generated from config only (decorator-only wrapping ships a generic note). **Setting it also replaces the decorators as the preview wrapper on the next build**: scoped-compare a themed component after the switch — an incomplete distillation regresses previews the decorators rendered fine, and carried-forward grades won't catch it. Format: `{"component": "ThemeProvider", "props": {…}, "inner": {…}}` — a nested chain, outermost first; each `component` must be a bundle export. Literal `props` are for small scalars (`"theme": "light"`) and stable snippets. For data that already exists in the repo — a locale JSON, a theme object — **prefer `{"$ref": "<export>"}`** backed by a 2-line module added via `cfg.extraEntries` (e.g. `export { default as previewI18n } from '../locales/en.json'`): a `$ref` emits `window.<Global>.<export>`, so the data lives once in the bundle and re-reads from its source file on every build. Inlining a copy is acceptable for something tiny and stable, but know the cost — a literal duplicates into every card's html and silently rots when the source file changes, so anything sizable or evolving belongs behind a `$ref`. Path forms for `extraEntries`: a bare name resolves from `node_modules`; a repo-owned module needs an explicit `./`/`../` package-relative path (workspace-bounded — the build logs `! extraEntries: … skipped` if it escapes). |
|
||
|
||
4. **Stage scripts + install converter deps** (isolated in `.ds-sync/`, repo lockfile untouched):
|
||
|
||
```bash
|
||
mkdir -p .ds-sync && cp -r "<skill-base-dir>"/package-build.mjs "<skill-base-dir>"/package-validate.mjs "<skill-base-dir>"/resync.mjs "<skill-base-dir>"/lib "<skill-base-dir>"/storybook "<skill-base-dir>"/non-storybook .ds-sync/
|
||
echo '{"name":"ds-sync-deps","private":true}' > .ds-sync/package.json
|
||
(cd .ds-sync && npm i esbuild ts-morph @types/react playwright && npx playwright install chromium)
|
||
```
|
||
|
||
If chromium install fails, `npx playwright install-deps chromium` first; if the environment can't install chromium, set `DS_CHROMIUM_PATH=<system-chromium>`.
|
||
5. **Run the converter, validator, and compare** — synchronously, stopping at the first non-zero exit (compare only runs once build + validate are clean — §3). Large DSes (≈100+ components) may need `NODE_OPTIONS=--max-old-space-size=<MB>` for the build; **never pipe the build through `head`/`tail`** (the pipeline masks the exit code — an OOM looks like success); redirect to a file and read it:
|
||
|
||
```bash
|
||
node .ds-sync/package-build.mjs --config .design-sync/config.json --node-modules <pkg-node-modules> \
|
||
--entry <built-dist-entry> --out ./ds-bundle
|
||
node .ds-sync/package-validate.mjs ./ds-bundle
|
||
node .ds-sync/storybook/compare.mjs --out ./ds-bundle --storybook-static .design-sync/sb-reference \
|
||
--components <solo-phase picks> # scope the FIRST compare to the §4b solo components
|
||
```
|
||
|
||
In a monorepo, `--node-modules` is the DS package's own `node_modules` — unless hoisting leaves it sparse (yarn's `node-modules` linker keeps `react` only at the repo root): if `react/` or `react-dom/` is missing inside, pass the repo-root `node_modules` instead. In the DS's own source repo `node_modules/<pkg>` doesn't exist, hence `--entry`. The build logs `[ICON_PKG]` / `[TOKENS_PKG]` auto-detections and bundles `.storybook/preview` decorators as the preview wrapper (`preview-decorators.js`) so previews get the same provider chain stories do.
|
||
|
||
Scope the first compare run: a full capture of a large DS is thousands of chromium navigations — pointless before the solo phase has flushed global issues (each global fix invalidates every capture). Run the **full** compare for the first time at §4b step 3. For a DS with >100 storied components, also tell the user the expected scale (components × stories) before fan-out and let them narrow scope if they want.
|
||
|
||
## 3. Self-heal loop (build + validate)
|
||
|
||
Fix `[TAG]` errors → rebuild → re-validate until both exit 0, **before** starting the compare loop in §4 — there's no point pixel-matching previews while the bundle itself is broken. Shared converter tags (`[NO_DIST]`, `[WORKSPACE_SIBLING]`, `[CSS_*]`, `[FONT_*]`, `[TOKENS_MISSING]`, `[DTS_*]`, `[RENDER*]`, …) behave identically to the package shape — use the table in `../non-storybook/SKILL.md` §3. Storybook-specific:
|
||
|
||
| Tag | Symptom | Fix |
|
||
|---|---|---|
|
||
| `[SB_REFERENCE_MISSING]` | compare can't find `iframe.html` | Build the reference (§2.2); set `cfg.storybookStatic`. |
|
||
| `[SB_BUILD_FAIL]` | converter's own storybook build failed | You skipped §2.2 — build the reference yourself and set `cfg.storybookStatic` so the converter never needs to. |
|
||
| `[ZERO_MATCH]` (storybook flavor) | no story entries matched | Check the storybook config's `stories` glob; then `titleMap`. |
|
||
| `[TITLE_UNMAPPED]` | N titles don't match an export | `cfg.titleMap {<title-name>: <export-name>}`. |
|
||
| `(preview: <Name> — no story exports paired …)` | index story names couldn't be matched to module export keys (pairing tries the display name, then the story ID's tail) | the component shows the floor card; fix the pairing — usually an owned `.tsx` re-exporting the stories under matchable names. |
|
||
| a preview cell errors with `undefined`-component / wrong-context messages | a story import resolved the wrong way — relative, tsconfig-alias, and bare-workspace imports all go through the same policy (see `lib/story-imports.mjs`'s rules) | `cfg.storyImports.shim` / `cfg.storyImports.bundle` substring patterns force the resolution per resolved path — the cheap fix before forking the seam. |
|
||
| `! preview build failed: <Name>` | the story module didn't COMPILE (top-level await, an import of a package esbuild can't resolve, an asset extension with no loader) | read the esbuild error above the line. Unknown asset extension → `cfg.storyImports.loaders` (merged over the defaults, e.g. `{".yaml": "text"}`); unresolvable import → own the `.tsx` and drop it. The component shows the floor card until fixed. |
|
||
| a story's own stylesheet is missing from its cell | story-local `.css`/`.scss` side-effect imports compile as empty (component styles ship via the bundle css). Exception: `.module.css` IS compiled — classes resolve and `_preview/<Name>.css` is linked automatically | usually nothing — the styles are decoration the storybook page adds. If the story genuinely depends on them, inline the styles in an owned `.tsx`. |
|
||
| `[BUNDLE_EXPORT]` | components aren't functions on `window.<Global>` | `extraEntries` for subpath/icon exports; check the dist entry is the full build. |
|
||
| `[SCHEDULER_MISSING]` | dist imports `scheduler` | react-dom leaked into the DS dist — check its build's externals. |
|
||
| `! preview decorator bundle failed` | decorators couldn't be bundled | Set `cfg.provider` manually, or run `node .ds-sync/storybook/probe.mjs --storybook-static .design-sync/sb-reference` to infer the chain from the live storybook (replace each `$hint` with a real value). |
|
||
| previews error at `_vendor/preview-decorators.js` load (storybook-API `undefined` errors) | the `.storybook/preview` import graph reached a storybook-runtime module the stubs don't cover | `manager-api`/`preview-api` are stubbed with functional no-op hooks and every other `@storybook/*`/`msw` module with inert callables (`fn()`, `action()`, `setupWorker()` at module scope all evaluate harmlessly); if some other API still crashes, set `cfg.provider` explicitly — it skips decorator bundling entirely. |
|
||
| `[ASSETS_BLOCKED]` from compare | the capture browser inherited a network-sandboxed shell — story assets (CDN images/fonts) failed on **both** panels, so grades can falsely pass while end users see different output | re-run `package-validate.mjs` + `compare.mjs --force` from a shell with egress to the listed hosts: approve running the command without the sandbox when prompted, or add the hosts to the sandbox allowlist. Don't grade image-bearing components while this prints. |
|
||
|
||
**Incremental path (base SKILL.md §3) — this is the open-the-channel gate.** The first time build + validate both exit 0, open the upload channel before starting §4: the user approves once here, then watches components land as grading proceeds. Nothing uploads until the first graded batch — the shared base files ride with it — and the batch pushes come from §4b/§4c. (Atomic path: nothing uploads until §6.)
|
||
|
||
## 4. Match previews to storybook
|
||
|
||
`compare.mjs` is a **capture harness — it photographs, you grade.** It computes no similarity heuristics (pixel/text/font scores mislead whenever framing legitimately differs); the judgment is made from the two true screenshots. Compiled previews capture **per story** — each story renders alone via `?story=<Export>` at the full capture viewport, exactly as storybook frames the reference side — so sibling stories can't interfere (portal stacking, shared radio-group names, focus, container measurement). Two output tiers:
|
||
- **Transient** (under `ds-bundle/`, wiped by rebuilds): `_screenshots/compare/<group>__<Name>.png` — sheet with one row per story: the **true storybook render | the true preview render**, side by side. Sheet images are shrunk to fit; the full-resolution originals are in `…/compare/raw/` (`…__sb.png` / `…__ds.png`) — Read those when the sheet is too small to judge confidently.
|
||
- **Campaign state** (in `.design-sync/.cache/compare/`, gitignored): `<Name>.grade.json` — your verdicts — and `<Name>.json` — capture facts: story↔cell pairing, shot paths, `previewKind`, the component's `srcSha` (story-file fingerprint), spot-check anchors. Reconstructible — absence just means "capture again". The only verdicts the script emits are factual: `sb-error` (story doesn't render in storybook), `unpaired` (no preview cell for the story), `error` (cell threw); every rendered pair is `needs-grade`.
|
||
|
||
Compare captures at most 6 stories per component by default — `[STORY_CAP]` in the log names components with more, and `--max-stories <n>` raises the cap. The cap is NOT part of the grade contract: raising it just captures the tail stories for incremental grading, and existing verdicts survive. One consequence to know: a capped component that grades fully `match`/`close` is verified-by-upload in full on future syncs even though its tail stories were never individually graded — raise the cap when those tail stories carry distinct variants worth verifying. Fan-out subagents must not change it mid-wave (sheets would cover different story sets than the orchestrator's worklist assumed).
|
||
|
||
**State across runs** — the first run verifies everything once; after that, one rule: **grades follow your sources** — the story files, your owned previews, the story set, the preview-affecting config (`provider`/`storyImports`/`extraEntries`/`overrides`/`titleMap`), and committed `.design-sync/overrides/` forks. Pipeline churn (a skill or toolchain update re-rendering everything) is auto-verified by a sampled `[SPOT_CHECK]` with grades kept; your edits re-grade only what they touch. Pixel jitter can never churn grades.
|
||
- *Sources unchanged* + fully graded `match`/`close` → **skipped outright** (`carried forward`): no capture, no re-grade — even when the bundle, styling, storybook, or the converter itself were rebuilt. `--force` recaptures everything **and clears all grades** — systemic re-verification, not casual sheet regeneration.
|
||
- *Sources changed* (story edited, `.tsx` edited, config/fork edited) → recapture, grade cleared, re-grade from the fresh sheet. `[STORY_CHANGED]` marks stories whose code moved — those are the ones where an OWNED `.tsx` **must be updated** (generated previews re-derive automatically); a recapture *without* `[STORY_CHANGED]` usually just needs the re-grade.
|
||
- *`[SPOT_CHECK]`* → re-captures named components **without clearing their grades**; Read the fresh sheets and confirm they still match the recorded grades. It can arrive driver-triggered after pipeline churn — the normal verification of a skill/toolchain update, not a bug. Divergence remediation scales with the churned set: a couple of components → re-grade just those; widespread → stop, diagnose, then `--force` a full pass. `--spot-check N` tunes the full-run random sample (0 disables); `--spot-check-components A,B` names picks explicitly, honored on scoped runs too (the §7 step-4 audit).
|
||
- *`[REFERENCE_STALE?]`* → the bundle changed but the reference storybook didn't. If the DS source changed, rebuild `.design-sync/sb-reference` before grading — a stale reference makes every grade a comparison against the *old* design.
|
||
- *A story renders differently every capture* (`new Date()`/`Math.random()` content) → the fingerprint is the story FILE, so the contract is stable — but the pixels aren't, and grading judges pixels. The frozen capture clock stabilizes date renders; for truly random content, pin values in an owned `.tsx` or `cfg.overrides.<Name>.skip` the story with a NOTES.md line.
|
||
|
||
Captures are stabilized for grading comparability (animations fast-forwarded, reduced motion, frozen clock — both panels show the same settled frame, the same rendered date). This is verification-only: shipped previews are untouched and fully animated.
|
||
|
||
**Grading is done by whoever is working the component** — you in the solo phase, each subagent for its own components in fan-out. After each compare run: Read the sheet (and raw PNGs when in doubt), judge each story **from the images alone**, Write the verdicts to `.design-sync/.cache/compare/<Name>.grade.json` (campaign-local working state — what makes a verdict durable is the upload: the uploaded `_ds_sync.json` anchors verified-by-upload skips on every future sync, any machine):
|
||
|
||
```json
|
||
{"stories": {"Default": {"verdict": "match"}, "Loading": {"verdict": "mismatch", "note": "spinner missing — story uses MSW mock"}}}
|
||
```
|
||
|
||
Rubric — grade what a designer would care about, looking at the two renders:
|
||
- `match` — same content, composition, and styling. Ignore antialiasing fuzz, scrollbar slivers, sub-5px offsets, and framing differences (the storybook canvas and the preview page frame differently — judge the component, not its surroundings).
|
||
- `close` — recognizably the same rendering with a minor delta (slightly different padding, focus ring, placeholder text). **`close` is still a fix target, not an exit:** if you can name the delta, you can usually name the knob — keep iterating. Accept `close` only after an iteration fails to improve it or no actionable cause remains, and the note must then say both *what's off* and *what you tried / why it's not fixable* (e.g. "focus ring color differs — storybook applies a global focus addon, not part of the DS").
|
||
- `mismatch` — wrong/missing content, unstyled output, wrong variant, missing icons/images, default fonts. The note must say *what* differs — it drives the next fix.
|
||
|
||
When the REFERENCE side is the artifact — storybook gates the story behind UI chrome (a theme/control toggle message) while the preview renders the real component — judge the component render on its own and note the gating; a preview that renders *more* than the gated reference is not `close`.
|
||
|
||
### 4a. Fix decision tree — global first
|
||
|
||
Work top-down; a global fix repairs every component at once, a per-component fix repairs one:
|
||
|
||
1. **Most/all components wrong the same way** → global, fix in config + full rebuild:
|
||
- Context/provider errors in cells (`use<X> must be inside <Provider>`) → decorators didn't bundle (§3 `! preview decorator bundle failed` rows) → `cfg.provider`.
|
||
- Everything unstyled / default fonts → `cfg.cssEntry` (check `[CSS_FROM_STORYBOOK]` in the build log), `cfg.tokensPkg`, `cfg.extraFonts`.
|
||
- **`[FONT_MISSING]` — the compare loop cannot see this one.** When neither side ships the font, both panels render the same chromium fallback, so the sheets look "matching" while every claude.ai/design user gets the wrong font — never accept "both sides fall back the same way" as a pass. Resolve per the `[FONT_MISSING]` row in `../non-storybook/SKILL.md` §3; storybook-specific extras: `cfg.extraFonts` paths are bounded by the git repo enclosing `dirname(--node-modules)` — sibling typography packages in the monorepo work as-is; only with no `.git` ancestor does the bound narrow to `dirname(--node-modules)`, and if you add a font the reference lacks, inject the same `@font-face` into `.design-sync/sb-reference/iframe.html` so the oracle verifies with the real font on both sides.
|
||
- Icons missing everywhere → `cfg.extraEntries` (check `[ICON_PKG]`).
|
||
2. **One component, `unpaired` or `fallback preview`** → its `.tsx` lacks a cell for that story. Previews compile the story MODULE whole (hooks, fixtures, local helpers all included — closures are not a failure mode), so the causes are: pairing failed (`storyName` override), the wrapper build failed (`! preview build failed` in the build log), or the module threw at load — check the sheet's `(page)` error row for the real exception (module-scope calls into a package the stubs don't cover). Open the wrapper (generated: `.design-sync/.cache/previews/<Name>.tsx`; owned: `.design-sync/previews/<Name>.tsx`), add/rename the export or drop the offending import — and if it's the generated one, save your fix as `.design-sync/previews/<Name>.tsx` WITHOUT the first-line marker (an in-place cache edit is preserved on this machine but gitignored — it vanishes on a fresh clone). Story imports use the location-independent `@ds-stories/<repo-relative path>` form, so the file works unchanged from either home.
|
||
3. **One component, you graded `mismatch`** → wrong props/composition. Read the story source; mirror it in an owned `.design-sync/previews/<Name>.tsx` (copy the cache wrapper there minus its marker line). That's the only lever for compiled story previews.
|
||
4. **`sb-error`** → the story doesn't render in storybook either (data-fetching, interaction-driven). Add its id to `cfg.overrides.<Name>.skip` and note why in NOTES.md.
|
||
5. **`[PORTAL?]` / overlay components** (Dialog/Tooltip/Toast) → grading is already isolated (per-story capture), but the PRODUCT card renders the whole grid html, so open-overlay stories paint over sibling cells there too. Set `cfg.overrides.<Name>.cardMode: "single"` — the card renders one story (`primaryStory` picks it; first export otherwise) full-bleed in a wrapper that contains `position:fixed` descendants, and declares the grading viewport on the card so the product renders at the size you verified. Rebuild + re-grade that component.
|
||
|
||
**Rebuild rules:**
|
||
- Config change (provider/css/fonts/entries/titleMap/skip) → full `package-build.mjs` + `package-validate.mjs`, then full `compare.mjs`. Styling changes (css/fonts/tokens) re-render every preview without moving any grade contract — grades carry forward. Provider, `storyImports`, `extraEntries`, and fork edits are part of the grade contract (they change what the preview mounts) — affected grades clear and re-grade on the rebuild. **On a large DS, verify the fix is right BEFORE paying the full rebuild**: run the targeted loop below on one affected component (or probe its rendered page) first — a wrong guess validated by a full rebuild costs the whole cycle. **Intermediate validates can sample**: global breakage is systemic by nature, so `--render-sample 10` answers "did the fix work?" at a fraction of the cost; the FULL render-check is required only once, at the §4d/§6 upload gate.
|
||
- `.tsx`-only edit → fast targeted loop, seconds not minutes:
|
||
```bash
|
||
node .ds-sync/lib/preview-rebuild.mjs --config .design-sync/config.json --node-modules <nm> --out ./ds-bundle --components <Name>
|
||
node .ds-sync/storybook/compare.mjs --out ./ds-bundle --storybook-static .design-sync/sb-reference --components <Name>
|
||
```
|
||
|
||
### 4b. Solo phase — one, then a few
|
||
|
||
Do NOT fan out immediately. Global issues must be flushed into config first, or every subagent rediscovers them.
|
||
|
||
1. **One component.** Pick a simple, well-storied one (Button-like: several stories, no portals). Run the §4a loop until you've graded every story `match` from its images — settle for `close` only when an iteration stops improving it (rubric above). **Every fix becomes a bullet in `.design-sync/NOTES.md`**: symptom → root cause → fix, marked `[GENERAL]` when it isn't component-specific.
|
||
2. **Three more, chosen for diversity:** one compound/overlay (Dialog/Tabs), one icon- or asset-heavy, one theme/provider-sensitive — and make sure the set spans one **text-heavy** component (font/typography bugs hide from button-only solos and then invalidate a whole grading wave). Same loop, solo. *Incremental path:* the solo set, once every story grades `match` (or `close` per the rubric's acceptance bar), is the first verified batch — push it (base SKILL.md §3).
|
||
3. **Full compare.** If ≥30% of remaining components fail with the *same* reason, that's a global issue you missed — fix it in config and re-run before fanning out. **Batch every skip and pairing fix the listing shows before rebuilding** — each rebuild+compare cycle costs minutes; fixing them one at a time pays that cost per item.
|
||
|
||
### 4c. Fan-out — parallel subagents
|
||
|
||
Partition the remaining non-matching components into batches of 5–8. Launch up to 4 subagents per wave (Agent tool, in one message so they run concurrently), each with this prompt — fill every `{…}`, and paste the **current** NOTES.md content in (subagents inherit the solo phase's learnings through it):
|
||
|
||
```text
|
||
Fix design-sync previews so they match the repo's own storybook render.
|
||
Repo: {REPO_ROOT}. Your components (yours alone): {COMPONENT_LIST}.
|
||
|
||
Why this matters: this design system is being synced to claude.ai/design, where
|
||
a design agent will build real UIs from this exact compiled bundle. The
|
||
storybook render is the proof of how each component is supposed to look; a
|
||
preview that matches it proves the component arrived intact, and one that
|
||
doesn't means every design the agent builds with it will be wrong the same way.
|
||
|
||
Artifacts per component (read these first):
|
||
- {OUT}/_screenshots/compare/<group>__<Name>.png — the true storybook render (left) vs the true preview render (right), per story. Full-res originals in {OUT}/_screenshots/compare/raw/.
|
||
- .design-sync/.cache/compare/<Name>.json — pairing facts + shot paths (no similarity scores — your eyes are the judge).
|
||
- The preview source (real JSX importing from '{PKG}'): .design-sync/previews/<Name>.tsx when owned, else the generated .design-sync/.cache/previews/<Name>.tsx. Your fixes are written to .design-sync/previews/<Name>.tsx (step 2).
|
||
- {OUT}/.stories-map.json — maps components to story ids; find each story's source file via its id in .design-sync/sb-reference/index.json (`importPath`). The story source is the authority on intended props/composition.
|
||
- .ds-sync/storybook/SKILL.md §4 — the grading rubric and fix decision tree.
|
||
|
||
Per component (max 3 iterations):
|
||
1. Read the sheet; judge each story FROM THE TWO IMAGES (raw PNGs when the sheet is too small); diagnose failures via the decision tree.
|
||
2. Copy .design-sync/.cache/previews/<Name>.tsx to .design-sync/previews/<Name>.tsx and DELETE its first-line `// @ds-preview generated …` marker (owned files live in previews/, win over the generated twin, and are durable + committed; an in-place cache edit survives rebuilds on this machine but is gitignored and vanishes on a fresh clone). The `@ds-stories/...` imports work unchanged from the new location. Mirror the story's JSX; inline story-local fixture data.
|
||
3. node .ds-sync/lib/preview-rebuild.mjs --config .design-sync/config.json --node-modules {NM} --out {OUT} --components <Name>
|
||
4. node .ds-sync/storybook/compare.mjs --out {OUT} --storybook-static {SB_REF} --components <Name> (your edit changed the component's contract, so this clears its old grade — that's intended)
|
||
5. Re-Read the fresh sheet and Write your verdicts to .design-sync/.cache/compare/<Name>.grade.json ({"stories": {"<story>": {"verdict": "match|close|mismatch", "note": "…"}}}). Done when you grade every story match. A close story is still a fix target — if you can name the delta, try the knob for it; accept close only when an iteration didn't improve it or there's no actionable cause, and the note must say what's off AND what you tried. Blocked after 3 iterations → grade honestly (mismatch/close + note), record the exact blocker, move on.
|
||
|
||
HARD RULES — violating these corrupts other agents' work:
|
||
- Edit ONLY .design-sync/previews/{<your components>}.tsx, your components' .design-sync/.cache/compare/*.grade.json files, and .design-sync/learnings/{BATCH_ID}.md.
|
||
- NEVER edit .design-sync/config.json, .design-sync/NOTES.md, .ds-sync/, or any other component's files.
|
||
- NEVER run package-build.mjs or package-validate.mjs — they rewrite the shared bundle. preview-rebuild.mjs + compare.mjs scoped via --components are your only build commands.
|
||
- NEVER write a grade for images you haven't Read in this iteration.
|
||
- A story that doesn't render in storybook either (sb-error) needs cfg.overrides.<Name>.skip; likewise [PORTAL?] needs cfg.overrides.<Name>.cardMode "single". Both are config edits you may NOT make — record them in your learnings file and final report; the orchestrator applies them. NEVER "fix" overlay bleed by neutralizing a story's open state in the .tsx — that destroys the fidelity being verified.
|
||
- If ≥half your components fail identically (same provider/css/font/token error), STOP — it's global. Write it to your learnings file, report it, do not work around it per-component.
|
||
|
||
Learnings: append to .design-sync/learnings/{BATCH_ID}.md as you go — one bullet per discovery:
|
||
`<Component>: <symptom> → <root cause> → <fix>`, prefixed [GENERAL] if it applies beyond that component.
|
||
|
||
Known repo gotchas (read before starting):
|
||
{CURRENT_NOTES_MD_CONTENT}
|
||
|
||
Final report: per component — match/close/blocked + one-line reason; then any [GENERAL] learnings verbatim.
|
||
```
|
||
|
||
**Between waves (orchestrator) — the learnings fold is mandatory, not optional:**
|
||
1. Read every `.design-sync/learnings/*.md`. Promote `[GENERAL]` bullets into `.design-sync/NOTES.md` (dedup; keep them terse), then delete each learnings file you've folded. Full `compare.mjs` runs print `[LEARNINGS_UNMERGED]` while any learnings file exists — that line is an **upload blocker** (§4d), so an overlooked fold can't silently ship.
|
||
2. If any subagent reported a global issue → apply the config fix, full rebuild + validate + full compare. Components that fix repaired drop out of the queue.
|
||
3. *Incremental path:* push the wave's components that now meet the §4d grade bar (every story `match`, or `close` per the rubric) as a verified batch (base SKILL.md §3) — after steps 1–2, so a global fix from this wave rebuilds them first.
|
||
4. Next wave gets the updated NOTES.md content and the still-failing components. After the last wave, repeat step 1 for whatever remains and delete `.design-sync/learnings/`.
|
||
|
||
### 4d. Done criteria + report
|
||
|
||
- The final `compare.mjs` run exits 0 (no `error`/`unpaired`/`sb-error`). First syncs and full-scope campaigns: a FULL run that does **not** print `[LEARNINGS_UNMERGED]`. Re-syncs: the gate is the §7 driver's verdict — `ok: true` with `verification.pendingGrade` empty (its capture scope is the capturable subset of the `changed`+`added` worklist — uncapturable members re-ship via the upload partition with nothing to grade; verified-by-upload components are outside the gate). The driver's scoped capture skips the learnings check and `.compare-report.json` aggregation — run `ls .design-sync/learnings/` yourself (must be empty) before upload. On this final run (after the final rebuild) every in-scope component should print `carried forward` with zero `grade cleared` — that line IS the proof the next sync will be fast. A cleared grade on a no-change run means a nondeterministic source input (volatile story content) — chase it now; a driver-triggered `[SPOT_CHECK]` is not that (pipeline churn being auto-verified — confirm the sheets and move on).
|
||
- Every IN-SCOPE storied component has a current `.grade.json` with every story `match` — or `close` meeting the rubric's acceptance bar (§4) — or skipped via `cfg.overrides.<Name>.skip` with a NOTES.md justification. On full runs `.compare-report.json` joins grades in; components with `"grades": null` or missing stories are not done (verified-by-upload components are exempt — they're not in the report's pending set on scoped runs).
|
||
- `package-validate.mjs` still exits 0 after the final rebuild, with no unresolved `[FONT_MISSING]` (§4a — the one warning the compare oracle can't see).
|
||
- Call `DesignSync({method: 'report_validate', counts: {total, bad, thin, variantsIdentical, iterations}})` from the final `ds-bundle/.render-check.json` (written by `package-validate.mjs`; `iterations` = full rebuild passes).
|
||
- NOTES.md has a current **Re-sync risks** section, written now while you still know them: what can silently go stale (data inlined into config, neutralized story exports, owned previews tied to upstream APIs), what was verified only partially (story caps, accepted `close` rationales), and what the build assumed (toolchain version, CDN-fetched assets). Fixes record what you did; this section tells the next run what to watch.
|
||
- Tell the user: N/M components graded match, which are `close` (and why that's acceptable), which were skipped and why.
|
||
|
||
## 5. When the repo is strange — escape hatches
|
||
|
||
First runs against unusual repos WILL hit things the defaults don't cover. Every heuristic has a committed override — the rule is: **never hand-patch generated output; put the fix in the file the next run reads.** Map from failure class to knob:
|
||
|
||
| The repo's strangeness | Knob | Lives in |
|
||
|---|---|---|
|
||
| Nonstandard build/entry (`module` points at TS source, exotic dist layout) | `cfg.entry`, `cfg.buildCmd` | config |
|
||
| CSS built by a separate pipeline / no dist sidecar / CSS-in-JS | `cfg.cssEntry` if there's a file; otherwise rely on `[CSS_FROM_STORYBOOK]` — the converter scrapes the **compiled** CSS out of `sb-reference`, which is the universal catch-all: however weird the pipeline, its output is in the storybook build | config |
|
||
| Tokens shipped as a separate package | `cfg.tokensPkg` | config |
|
||
| Fonts from a runtime service / proprietary CDN | `cfg.extraFonts`, `cfg.runtimeFontPrefixes` | config |
|
||
| Icons or components on subpath exports | `cfg.extraEntries` | config |
|
||
| Naming conventions (story titles ≠ export names) | `cfg.titleMap`; story↔cell pairing also falls back to order | config |
|
||
| Decorators/providers that won't bundle (vite-only plugins, MDX, aliases) | `cfg.provider` — an explicit chain beats the decorator bundle; `probe.mjs` infers it from the live storybook; or compose providers **inline in the component's own `.tsx`** (an owned preview can import and wrap anything the package exports) | config / previews |
|
||
| Stories that can't render statically (MSW, data fetching, interaction tests) | `cfg.overrides.<Name>.skip` + a NOTES.md line saying why. Skip removes the story's cell, but the wrapper still imports the whole story MODULE — if the file crashes at import (module-scope fetch/worker), own the `.tsx` and drop the import instead | config |
|
||
| `[PORTAL?]` — overlay/portal stories paint outside their cells in the grid card | `cfg.overrides.<Name>.cardMode: "single"` (+ optional `primaryStory`, `viewport: "WxH"`) — single-story card, fixed-position containment, declared product viewport. Compare still grades every story via `?story=` | config |
|
||
| `[EXPORT_COLLISION]` — a sibling package (icons etc.) exports names the main package also exports | the main package wins the global merge, so stories importing the losing name from the sibling render the wrong thing | the log names the fix: `cfg.storyImports.bundle: ["<sibling>"]` |
|
||
| `[FILE_OVER_5MB]` — a build output exceeds the upload's per-file cap | usually a dev-only heavyweight bundled into a preview or the decorator bundle (syntax highlighters, icons-as-code) | slim it NOW, before grading — a post-grade slim of an owned preview re-grades that component |
|
||
| `[PROVIDER_UNEXPORTED]` — a `cfg.provider` component isn't a bundle export | the build exits 1 before emitting any component previews or docs — the output dir is left partial; rebuild after fixing | use the exact exported name, or re-export it via `cfg.extraEntries`. The check reads the bundle's own export list, so absence is reliable; names hidden behind bundled CommonJS re-exports can't be enumerated — those build with a `[PROVIDER_UNVERIFIED]` warning instead; if every preview then fails "Element type is invalid", the name is wrong |
|
||
| A story import resolves the wrong way (shimmed when it should bundle, or vice versa — any import style) | `cfg.storyImports.shim` / `cfg.storyImports.bundle` — substring patterns matched against resolved paths (bare package imports shim by **specifier**, without resolution — pattern-match the specifier for those). Unknown package subpaths (`<pkg>/utils`) bundle by default; if one should ride the global instead, add it to `cfg.extraEntries`. In the package's own source repo a bundled self-import has nothing to resolve to — symlink `node_modules/<pkg>` → the built `dist/` first | config |
|
||
| Story files import an asset type the defaults can't load (`.yaml`, `?raw`, svg-as-component) | `cfg.storyImports.loaders` — an esbuild loader map merged over the defaults (e.g. `{".yaml": "text"}`) | config |
|
||
| Generated preview has wrong props/composition | copy `.design-sync/.cache/previews/<Name>.tsx` to `.design-sync/previews/<Name>.tsx` minus its marker line (owned forever) | previews |
|
||
| Source/docs discovery misses (unusual repo layout) | `cfg.componentSrcMap`, `cfg.docsMap`, `cfg.dtsPropsFor`, `cfg.srcDir` | config |
|
||
| Anything deeper — custom story format, exotic args extraction, CSS transform | fork the adapter: copy the bundled lib module to `.design-sync/overrides/<name>.mjs` and declare it in `cfg.libOverrides` with a one-line reason (the build cross-checks both directions: `[OVERRIDE_UNDECLARED]` / `[OVERRIDE_MISSING]`). Forks are committed, so re-syncs use them automatically. **`emit.mjs` and `bundle.mjs` are app-contract surface — never fork them.** | `.design-sync/overrides/` |
|
||
|
||
For **story handling** specifically, the fork points by concern: `story-imports.mjs` (ALL import-resolution policy for preview compiles — the seam built for per-repo customization; honored by both the full build and `preview-rebuild.mjs`), `source-storybook.mjs` (index.json discovery, title→component mapping, story-source resolution + export pairing), `preview-gen-storybook.mjs` (the wrapper template / composeStories semantics), `css-fallback.mjs` (CSS/font scraping from the storybook build). Fork the *narrowest* module that owns the breakage, keep its export signature, and record what the repo does differently in NOTES.md — the next sync inherits all of it. A fork loads from `.design-sync/overrides/` while its siblings stay in the staged scripts — repoint the fork's relative imports (`./common.mjs` etc.) at `../../.ds-sync/lib/`. A fork that imports a bare converter dep (`esbuild`) also needs `ln -sfn ../.ds-sync/node_modules .design-sync/node_modules` so node can resolve it from the fork's location — once per clone, not once ever: the link is gitignored (`node_modules` rules) while the committed fork that needs it survives the clone, so recreating it is part of the fresh-clone setup.
|
||
|
||
The ladder's last rung, for repos genuinely outside the converter's envelope: **the upload format is the contract, not the converter** (see the base skill). Generate the layout however the repo allows — but `package-validate.mjs` and the compare/grading gate apply unchanged to whatever you produce. The oracle is never forked.
|
||
|
||
Everything in that table is a committed file, and §2.3 requires reading the existing config + NOTES.md before doing anything — so run N+1 replays every decision run N made. When you fix something on a strange repo, ask: "which committed file makes this automatic next time?" If the answer is none, that's a NOTES.md entry at minimum — and likely a missing row here worth reporting.
|
||
|
||
## 6. Upload
|
||
|
||
Which of the two paths applies was decided by the base skill §1 router (pinned-at-run-start → atomic; otherwise empty → incremental, non-empty → atomic):
|
||
|
||
**Incremental path** (first sync into an empty project): the plan has been open since this file's §3 gate and verified batches have already landed. After §4d passes, run the close-out in base SKILL.md §3 — sentinel fence → full content writes → reconciliation deletes → sentinel re-arm → `_ds_sync.json` last. This section's chunking, hygiene, and stays-local rules apply to those writes; `projectId` was already recorded in §1; the handoff audit at the end of this section still applies. Skip the rest of this section's sequence — it is the atomic path.
|
||
|
||
**Atomic path** (re-sync, or any non-empty target — it may be in active use, so it updates in one pass after everything is verified): everything below. Only after §4d. `DesignSync(finalize_plan)` with `localDir: "./ds-bundle"`.
|
||
|
||
- **Writes — everything, always** (full re-verifies and re-syncs alike): `writes: ["components/**", "tokens/**", "fonts/**", "_vendor/**", "_preview/**", "guidelines/**", "_ds_bundle.js", "_ds_bundle.css", "styles.css", "README.md", "_ds_sync.json", "_ds_needs_recompile"]`. Re-uploading unchanged files is idempotent and cheap. An under-scoped writes list silently and permanently desyncs the project — full writes are the safe default.
|
||
- **Deletes.** Anchored re-syncs: verbatim from the diff — copy `.sync-diff.json`'s `upload.deletePaths` exactly; never hand-derive the list, never pass `[]` when the diff lists paths. No anchor (a re-adopted or recovered non-empty project being fully re-verified): the diff can't see the project's history, so review its `list_files` NOW — before `finalize_plan` — for files this build doesn't produce, and put those reviewed paths in the plan's `deletes` (a delete not named in the plan is rejected).
|
||
- **Make the session's FINAL build a §7 driver run.** Every `package-build.mjs` run wipes `.sync-diff.json`; the driver's diff stage regenerates it, so `deletePaths` and `upload.any` describe the exact bytes you upload.
|
||
- **`upload.any === false` → skip the upload entirely** — the project already matches this build. (The handoff audit below still applies.)
|
||
- **`_ds_sync.json` is the absolute final write** — after all content writes, all deletes, and the sentinel re-arm, in its own `write_files` call. Uploaded early, a mid-plan failure leaves the anchor vouching for files the project doesn't have, and deterministic rebuilds mean no later sync would repair them.
|
||
- **What stays local**: `_sb/**` (storybook-static is a reference, never uploaded), dot-prefixed entries (`.stories-map.json`, `.compare-report.json`, `.ds-build-meta.json`, `.sb-static/`, `.sync-diff.json`), and `_screenshots/`. `_vendor/` and `_preview/` DO upload — the preview cards load React and the compiled previews from them.
|
||
|
||
If `finalize_plan` is denied, **stop** — denial means the session can't approve, not that the arguments were wrong. Tell the user what was denied and ask how they'd like to proceed: try the approval again, or take the validated `ds-bundle/` and run the upload interactively themselves.
|
||
|
||
After plan approval, the upload is a fixed sequence:
|
||
|
||
1. **Sentinel first**: `DesignSync(write_files, [{path: "_ds_needs_recompile", localPath: "_ds_needs_recompile"}])` — it fences the app's manifest/copy machinery against a half-uploaded state.
|
||
2. **All content writes**, chunked into ≤256-file `write_files` calls under the same `planId`. The server also bounds payload BYTES, not just file count — batch binary-heavy dirs (fonts/, images) into smaller chunks, and on a 500 halve the chunk size and retry.
|
||
3. **All deletes**: `DesignSync(delete_files)` over every path in `upload.deletePaths`. (No anchor: the paths you reviewed into the plan's `deletes` at `finalize_plan` — the deletes bullet above.) If `delete_files` rejects paths that don't exist remotely (floor-card components have no `_preview/` files), retry without the rejected entries — that not-found rejection is the ONLY failure you may continue past.
|
||
4. **Sentinel re-arm, then `_ds_sync.json` last.** The anchor goes after deletes too — a failed delete would leave remote files the refreshed anchor can no longer see.
|
||
|
||
Any other write/delete failure that retries don't clear means **STOP** — no sentinel re-arm, no `_ds_sync.json`. An un-anchored project merely re-verifies next sync; a fresh anchor over a half-applied upload is permanent.
|
||
|
||
**Upload hygiene**: keep file lists and chunk manifests under `.design-sync/` — never bare `/tmp` paths, where a stale list from another repo's sync uploads the wrong design system. Regenerate the list from the live `ds-bundle/` immediately before upload, and sanity-check it: component names belong to THIS design system, and the bundle's `window.<globalName>` matches. Finish with `DesignSync(list_files)` to confirm the count.
|
||
|
||
Only after the post-upload `list_files` count verifies, **record `projectId` in `.design-sync/config.json`** if absent or different (this is a backstop — §1 records the id at target settlement for every route, so it's normally already present; what must never happen is recording an id here before the upload verifies, pinning a config to a project whose content isn't real yet) — it pins which project anchors future re-syncs. When done, tell the user: the project URL (`https://claude.ai/design/p/<projectId>`), component count, compare results summary, and that validate exited clean. The durable set — `.design-sync/config.json`, `NOTES.md`, `previews/`, `.design-sync/overrides/` — must land in the repo for re-syncs to reuse every fix; verified-state lives with the uploaded `_ds_sync.json`, not in git. The handoff audit below covers the offer to commit.
|
||
|
||
**Last step — audit the handoff.** A future run is only as fast and correct as what this one leaves behind; verify it, don't assume it:
|
||
|
||
1. `git status` — the durable set (`.design-sync/` — `config.json`, `NOTES.md`, `previews/`, `overrides/`) is the sync's repo footprint; `sb-reference/`, `learnings/`, `.cache/`, `.ds-sync/` are ignored. If this run created or changed any of the durable files, **offer to commit them and open a PR** (one commit, sync state only — no unrelated files). An uncommitted fix is a fix the next sync doesn't have.
|
||
2. Re-read NOTES.md as if you were the next agent, knowing nothing from this session: could you skip today's debugging with only what's written? Every owned preview, skip, config knob, and lib fork should trace to a bullet, and the Re-sync risks section should be current (§4d). Write whatever's missing now — it costs a minute today and a re-derivation later.
|
||
3. After a re-sync — however much it changed or re-graded — leave NOTES.md and the git state exactly as you found them unless the run produced something the next run needs to know; only hand the user something to commit when it adds value for a future sync.
|
||
|
||
## 7. Re-syncs — one command routes the work
|
||
|
||
The repo carries the sync's inputs (config, owned previews, NOTES.md); the uploaded project carries the anchor (`_ds_sync.json`). Read NOTES.md first (Re-sync risks is the watch-list), then:
|
||
|
||
1. **Refresh inputs.** Re-copy the staged scripts (§2.4's `cp -r` line — instant; a stale `.ds-sync/` runs an old converter against these instructions). Re-run `buildCmd` **and rebuild `.design-sync/sb-reference`** whenever the DS source may have changed — they must move together; when in doubt rebuild both (deterministic builds make an unnecessary rebuild a no-op; `[REFERENCE_STALE?]` in the capture log means you forgot). Fresh-clone extras: the §2.4 dep install + chromium, the §2.2 sb-reference build, and — if the repo carries `.design-sync/overrides/` forks with bare imports — `ln -sfn ../.ds-sync/node_modules .design-sync/node_modules`.
|
||
2. **Fetch the anchor**: `DesignSync(get_file, path: "_ds_sync.json")` → save to `.design-sync/.cache/remote-sync.json`. No sidecar in the project → first-sync scope (omit `--remote` below).
|
||
3. **Run the driver** from the repo root:
|
||
|
||
```sh
|
||
node .ds-sync/resync.mjs --config .design-sync/config.json --node-modules <nm> \
|
||
[--entry <dist-entry>] --out ./ds-bundle --remote .design-sync/.cache/remote-sync.json
|
||
```
|
||
|
||
It chains build → diff → validate → capture (scoped to new + contract-changed components) and prints one verdict JSON (also written to `ds-bundle/.resync-verdict.json`). Stage logs stream to stderr. The driver is idempotent — re-run it after fixes. For per-component preview iteration use the §4a targeted loop instead (seconds, not a full build + render-check); the driver re-run is the closing receipt.
|
||
4. **Act on the verdict** — every field that needs you:
|
||
|
||
| Field | Your work |
|
||
|---|---|
|
||
| `ok: false` | the failed stage (`stages.<name>`) logged its [TAG]s — fix per that stage's section above, re-run |
|
||
| `verification.pendingGrade` | grade those fresh sheets (§4 rubric). In the capture log: `[STORY_CHANGED]` → mirror the story in the owned `.tsx` first; `unpaired` → add the export; `extraCells` naming an owned export → prune it |
|
||
| `verification.canary` | pipeline churn (or a reference-storybook change) with your sources stable — grades kept; confirm the named `[SPOT_CHECK]` sheets against the recorded grades. A couple diverge → re-grade those components; widespread divergence → `--force` full pass |
|
||
| warn lines in the validate log (`[RENDER_THIN]` etc.) | check NOTES.md's known list — a warn recorded there was triaged on a prior sync (legitimately-short components read as thin forever); a warn NOT recorded there is new — look at that component, then fix it or record it in NOTES.md |
|
||
| `verification.removed` | components gone upstream — confirm the deletions are intentional |
|
||
| `upload.styling: true` | styling re-ships automatically; grades stay |
|
||
| `upload.any: false` | nothing to upload — done |
|
||
| `upload.any: true` | §6 upload — full writes by default, `deletes` verbatim from `upload.deletePaths` (never scope writes by the verification partition) |
|
||
|
||
Grades follow your sources by design — DS source, CSS, and bundle changes carry, and pipeline churn arrives as `verification.canary` rather than re-grades. To deliberately audit carried-forward grades anyway (after a major DS version bump, or on suspicion), run `node .ds-sync/storybook/compare.mjs --out ./ds-bundle --components <A,B> --spot-check-components <A,B>` — fresh sheets, grades kept — and confirm the sheets still match the recorded grades.
|
||
5. Re-fetch the sidecar right before `finalize_plan`; if it moved (concurrent sync), re-run the driver and act on the fresh verdict.
|
||
|