8.3 KiB
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:
.astrofiles 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 classessrc/styles/base.cssalready 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 asbase.csschanges, and each component's markup ports back to.astroone to one (every<Name>.mdstates the Astro equivalent). - Do not port the page-level
.astrocomponents (Hero, Stages, HowItWorks, GridObject). Their styles are scoped inside the.astrofiles, so a React copy would duplicate CSS and drift. The line is: global primitive classes are fair game, component-scoped styles are not. Stackis 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 emitsdisplay: grid; gap: var(--space-N)and nothing else.- Ownership:
ds-src/*.tsx+ds-src/*.mdare the source;ds-types/is generated bytsc -p .design-sync/ds-src/tsconfig.json;previews/*.tsxare 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_modulesby symlink, so the site'spackage.jsonkeeps no React dependency:ln -sfn ../.ds-sync/node_modules/react node_modules/react(same forreact-dom), plusln -sfn ../../.ds-sync/node_modules/@types/react node_modules/@types/react— that last one is what letstscresolveReactNodewhen emittingds-types/, and what keeps the converter from collapsing inherited props toany. Recreate all three links after anynpm installin the repo root — installs prune them. package.jsoncarries"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-modulesmust be./node_modules, not.ds-sync/node_modules. The font containment bound is the git repo enclosingdirname(--node-modules), and this repo is not a git repo, so the bound collapses to that directory itself. Pointing at.ds-syncputspublic/fonts/outside the bound and the fonts are silently refused.cssEntryis derived, not the site's own file. Runnode .design-sync/make-ds-styles.mjsbefore the converter. It rewrites exactly two things insrc/styles/base.css:@import './tokens.css'→ the token file's contents, inlined. The converter copies the CSS entry verbatim, so an@importpointing outside the bundle dangles and everyvar(--token)resolves to nothing. This is silent: the build and validate both pass, the fonts look right, and the whole palette is simply missing. Checkgrep -- '--paper:' ds-bundle/_ds_bundle.cssafter any build.url('/fonts/…')→url('../public/fonts/…'). The site serves fonts from the web root, so the URLs are absolute;lib/css.mjsresolves them withresolve(srcDir, url), an absolute path resolves to/fonts/…on disk, fails, and the rule is emitted verbatim — leavingfonts/fonts.csspointing 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/designrefuses any uploaded path whose basename isCLAUDE.md(and anything under.claude/) withpermission_denied: … 1 path(s) are reserved, and the converter names each guideline file after its source (lib/docs.mjs:emitGuidelinesuses the source's package-relative path). SoguidelinesGlobpoints atdocs/design-brief.md, a verbatim copy ofCLAUDE.mdthatmake-ds-styles.mjsregenerates, and it uploads asguidelines/docs/design-brief.md. EditCLAUDE.md; never edit the copy. The refusal is a hard API rule, not a warning, and it rejects the wholewrite_filescall without writing any of its other paths. cfg.tokensGlobdoes not work here.lib/css.mjs:copyTokensresolves it insidenode_modulesfor 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 thestyles.cssimport closure — which is what designs actually receive. Do not chase the empty directory..design-sync/overrides/css.mjsfork:cpSyncof the font files fails withEACCESon this machine's virtiofs mount (/home/ciphercell/projects), creating a 0-byte, write-only file and aborting the build. The fork copies withreadFileSync/writeFileSyncinstead, which works. Reproducible, not transient. Declared incfg.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 generateddocs/design-brief.mdand thetypesfield inpackage.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.cssis generated and gitignored-adjacent. It is a copy ofbase.cssat sync time. Ifsrc/styles/base.cssortokens.csschange and nobody re-runsmake-ds-styles.mjs, the sync ships the old look with no warning. Always regenerate first. The script throws ifbase.cssstops@importing./tokens.css, which is the one structural change that would break it silently otherwise.conventions.mdenumerates 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 insrc/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.mdis a generated copy of the repo's ownCLAUDE.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 whatmake-ds-styles.mjscopies. The copy is generated, so aCLAUDE.mdedit 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.mdwas rewritten when the components landed; the tokens-only version that saidwindow.CipherCellis 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.mjsscreenshots all 14, andpackage-capture.mjscaptures 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 wholewrite_filescall.