Files
homepage_relaunch/.design-sync/NOTES.md
T
2026-10-09 14:28:57 +02:00

8.3 KiB

design-sync notes — ciphercell-homepage

What this repo is, for sync purposes

  • The site itself is not a component library. It is Astro: .astro files compile to HTML at build time and have no runtime form, so they cannot be bundled or shipped to a design agent. The first sync therefore went out tokens-only, and the Claude Design component pane was empty — correct output, but useless to design with.
  • What ships instead is .design-sync/ds-src/: fourteen React primitives that emit exactly the classes src/styles/base.css already defines, and no CSS of their own. That is the point — the visual truth stays in the site's stylesheet, so the design system cannot drift from the site even as base.css changes, and each component's markup ports back to .astro one to one (every <Name>.md states the Astro equivalent).
  • Do not port the page-level .astro components (Hero, HowItWorks, GridObject). Their styles are scoped inside the .astro files, so a React copy would duplicate CSS and drift. The line is: global primitive classes are fair game, component-scoped styles are not.
  • Stack is the one component with no class behind it. Nothing in this system carries its own margins, so without it the design agent hand-rolls pixel gaps, which the brief forbids. It emits display: grid; gap: var(--space-N) and nothing else.
  • Ownership: ds-src/*.tsx + ds-src/*.md are the source; ds-types/ is generated by tsc -p .design-sync/ds-src/tsconfig.json; previews/*.tsx are the authored preview cards. Regenerate the types after ANY edit to a component's props, or the design agent gets a contract that does not match the code.

Repo-specific gotchas

  • React and its types come from .ds-sync/node_modules by symlink, so the site's package.json keeps no React dependency: ln -sfn ../.ds-sync/node_modules/react node_modules/react (same for react-dom), plus ln -sfn ../../.ds-sync/node_modules/@types/react node_modules/@types/react — that last one is what lets tsc resolve ReactNode when emitting ds-types/, and what keeps the converter from collapsing inherited props to any. Recreate all three links after any npm install in the repo root — installs prune them.
  • package.json carries "types": ".design-sync/ds-types/index.d.ts". That is how the converter discovers the components and their props (findTypesRoot → exportedNames). It is inert for Astro: the package is private and never published. Remove it and the sync silently falls back to tokens-only with an empty component pane.
  • --node-modules must be ./node_modules, not .ds-sync/node_modules. The font containment bound is the git repo enclosing dirname(--node-modules), and this repo is not a git repo, so the bound collapses to that directory itself. Pointing at .ds-sync puts public/fonts/ outside the bound and the fonts are silently refused.
  • cssEntry is derived, not the site's own file. Run node .design-sync/make-ds-styles.mjs before the converter. It rewrites exactly two things in src/styles/base.css:
    • @import './tokens.css' → the token file's contents, inlined. The converter copies the CSS entry verbatim, so an @import pointing outside the bundle dangles and every var(--token) resolves to nothing. This is silent: the build and validate both pass, the fonts look right, and the whole palette is simply missing. Check grep -- '--paper:' ds-bundle/_ds_bundle.css after any build.
    • url('/fonts/…') → url('../public/fonts/…'). The site serves fonts from the web root, so the URLs are absolute; lib/css.mjs resolves them with resolve(srcDir, url), an absolute path resolves to /fonts/… on disk, fails, and the rule is emitted verbatim — leaving fonts/fonts.css pointing at a path that does not exist. Every design then renders in a fallback font with nothing flagged.
  • The brief cannot ship under its own name. claude.ai/design refuses any uploaded path whose basename is CLAUDE.md (and anything under .claude/) with permission_denied: … 1 path(s) are reserved, and the converter names each guideline file after its source (lib/docs.mjs:emitGuidelines uses the source's package-relative path). So guidelinesGlob points at docs/design-brief.md, a verbatim copy of CLAUDE.md that make-ds-styles.mjs regenerates, and it uploads as guidelines/docs/design-brief.md. Edit CLAUDE.md; never edit the copy. The refusal is a hard API rule, not a warning, and it rejects the whole write_files call without writing any of its other paths.
  • cfg.tokensGlob does not work here. lib/css.mjs:copyTokens resolves it inside node_modules for a tokens package; these tokens live in the repo. tokens/ is therefore empty by design and the definitions ship inside _ds_bundle.css, which is in the styles.css import closure — which is what designs actually receive. Do not chase the empty directory.
  • .design-sync/overrides/css.mjs fork: cpSync of the font files fails with EACCES on this machine's virtiofs mount (/home/ciphercell/projects), creating a 0-byte, write-only file and aborting the build. The fork copies with readFileSync/writeFileSync instead, which works. Reproducible, not transient. Declared in cfg.libOverrides.
  • This repo is not under git, so none of the durable files can be committed. If it is ever initialised, commit .design-sync/{config.json,NOTES.md,conventions.md,make-ds-styles.mjs, ds-src/,ds-types/,previews/,overrides/} plus the generated docs/design-brief.md and the types field in package.json (the sync needs both present), and keep .ds-sync/, ds-bundle/ and .design-sync/.cache/ ignored (already in .gitignore).

Known render warns

None outstanding. Two [GRID_OVERFLOW] warns fired once on Grid and Display, whose stories are wider than a grid cell; both are resolved by cfg.overrides.*.cardMode: "column" and cannot re-fire by construction. All 14 cards render clean, 30 cells graded good.

The card scaffold sets body{background:#fff} AFTER the stylesheets, so a preview that does not wrap in Surface renders on white instead of paper. Every authored preview wraps in Surface for that reason. Keep it that way.

Re-sync risks — what can go stale

  • .design-sync/ds-styles.css is generated and gitignored-adjacent. It is a copy of base.css at sync time. If src/styles/base.css or tokens.css change and nobody re-runs make-ds-styles.mjs, the sync ships the old look with no warning. Always regenerate first. The script throws if base.css stops @importing ./tokens.css, which is the one structural change that would break it silently otherwise.
  • conventions.md enumerates real token and class names. It was validated against the build at sync time (30 tokens, 25 classes, all present). Renaming or removing a token or primitive class in src/styles/ makes the header lie to the design agent, which will then write vocabulary that does not resolve. Re-validate on every sync.
  • The brief in guidelines/docs/design-brief.md is a generated copy of the repo's own CLAUDE.md, which mixes the design brief with project instructions. It is shipped whole on purpose — it is the system's rulebook — but if it ever grows non-design content, narrow what make-ds-styles.mjs copies. The copy is generated, so a CLAUDE.md edit that nobody regenerates ships the old brief silently: always run the script before the converter.
  • The React symlinks are machine state, not committed. A fresh clone needs them recreated before the converter will run.
  • conventions.md was rewritten when the components landed; the tokens-only version that said window.CipherCell is empty is gone. If a future sync ever drops back to tokens-only, that file has to change with it — a header that promises components which do not exist is worse than no header.
  • Preview cards are verified by rendering: package-validate.mjs screenshots all 14, and package-capture.mjs captures each of the 30 cells for grading. Grades live in the gitignored .cache/review/; what makes them durable is the uploaded _ds_sync.json.
  • _screenshots/ must never be uploaded. It is local review state and is not in the approved write paths; including it fails the whole write_files call.