Initial commit 🚀

This commit is contained in:
Felix Klement committed 2026-10-09 14:00:40 +02:00
commit fd58a8d32f
235 files changed
+19775

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
# 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, Stages, 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 `@import`ing `./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.