Import this repo's components into a Claude Design project so the design
agent builds with the real Cloudrite library instead of generic components.
The repo is a Next.js app, not a component library: no dist/, no exports
map, no .d.ts tree, and Tailwind exists only as build directives. So
.design-sync/ carries a small build that manufactures a consumable package
from source (staged to the gitignored .ds-pkg/):
- tsconfig.dts.json declaration emit, for real <Name>Props contracts
- build-css.mjs Tailwind v4 -> a static stylesheet via @tailwindcss/postcss
- make-pkg.mjs stages the package; index.js exports all 292 symbols
while index.d.ts exports only the 61 roots, so shadcn's
flat compound parts don't each become a preview card.
Also generates the per-root parts tables in docs/
- shims/next-link router-free anchor; the real next/link needs
AppRouterContext and drags in the App Router runtime
- fonts/ Space Grotesk + Inter woff2 (SIL OFL). next/font supplies
these at runtime in the app, so the bundle has none
previews/ holds 40 authored preview stories, conventions.md is prepended to
the generated README as the design agent's usage guide, and NOTES.md records
the gotchas a future sync would otherwise rediscover.
Co-Authored-By: Claude Opus 5 <[email protected]>
9.7 KiB
design-sync notes — Cloudrite
Repo-specific gotchas for future syncs. Read this before re-running.
What this repo is
Not a component library — a Next.js 16 marketing site (private: true, no dist/,
no exports, no .d.ts tree). The DS surface is components/ui/* (54 shadcn/ui
primitives, new-york style) plus 7 hand-built Cloudrite page sections.
The staging package (.ds-pkg/)
The converter needs a package with a built entry and a .d.ts tree. Three scripts
under .design-sync/ build one from source; run them in this order (this is
cfg.buildCmd):
node_modules/.bin/tsc -p .design-sync/tsconfig.dts.json— declaration emit, 69.d.tsfiles. Exits 0; all three steps are chained with&&, so a real failure stops the run.../next-env.d.tsmust stay in that config'sinclude. It pulls in Next's JSX augmentation for styled-jsx; without it the<style jsx>blocks inhero.tsxandfooter.tsxraise TS2322 and the emit exits non-zero. Those are not repo errors —pnpm buildandtsc --noEmit -p tsconfig.jsonare both clean.node .design-sync/build-css.mjs— compiles Tailwind v4 →.design-sync/compiled.css.node .design-sync/make-pkg.mjs— writes.ds-pkg/{package.json,index.js,index.d.ts}, copies the stylesheet + fonts in, and regenerates.design-sync/docs/*.md.
index.js re-exports every symbol from source (292 exports → window.Cloudrite),
while index.d.ts re-exports only the 61 roots. That split is deliberate: shadcn
exports compound parts flat (CardHeader, not Card.Header), and the converter's
subcomponent grouping only recognises TS-namespace compounds — so without the split
all 292 exports would each get their own preview card. The parts are instead
documented per-root in the generated .design-sync/docs/<Root>.md parts tables,
which become the .prompt.md the design agent reads.
Known render warns
[DTS_STYLE_SYSTEM] filtering @types/react props— expected. Most shadcn primitives type their props asReact.ComponentProps<'div'>, so the CSS-shorthand filter trips on React's own DOM prop bag. The emitted<Name>Propsstill carry the real API (variant unions,asChild, etc.). Not a defect.inlined npm packages: 1in the build log is a pnpm artifact, not a real count — the metafile regex matches.pnpmfirst in pnpm'snode_modules/.pnpm/<pkg>@ver/...layout. The bundle really does inline radix, lucide, cva, etc.
Gotchas that cost a debugging cycle
cfg.tsconfigmust use/* */comments only. The converter strips//comments with a regex that also mangles a"//"JSON key, leaving unparseable JSON. The failure is silent:tsconfigPathsPluginreturns null, esbuild falls back to autodiscovering the repo's owntsconfig.json, andnext/linkresolves to the real Next runtime — which bundles the App Router and makes every preview die withReferenceError: process is not defined.next/linkis shimmed to a plain anchor (.design-sync/shims/next-link.tsx) viacompilerOptions.paths. Header, Hero and Footer import it; the real one needsAppRouterContext, which doesn't exist outside a Next app.- CSS edits need a full
package-build.mjs.lib/preview-rebuild.mjsonly recompiles_preview/*.js; it does not re-copy the stylesheet, so a CSS fix appears to do nothing until the full build runs. - Never set
html { background }in the DS stylesheet. The card harness paintsbody{background:#fff}in a later inline<style>, so a darkhtmlunder a whitebodybox paints a black band across the bottom of every card.bodyalone is correct: designs get the brand surface, cards keep the harness's white chrome. - Fonts are self-hosted.
next/font/googlesupplies Space Grotesk + Inter at runtime in the app, so the repo has no font files. Latin + latin-ext woff2 subsets were downloaded from Google Fonts into.design-sync/fonts/(SIL OFL) and are committed.app/globals.cssdeclares--font-sans: var(--font-sans), …, which is a self-reference that is invalid outside Next —.design-sync/tailwind-entry.cssre-declares both families with real stacks.
Preview conventions (apply to every new preview)
- Wrap every story in a
Surface—<div className="bg-background text-foreground rounded-lg p-6">. Cloudrite is dark-only;ghost,link,outlineandborder-borderare all invisible against the harness's white card body. - Overlay / menu components need an open state. Give them a
defaultOpenstory and setcfg.overrides.<Name> = {"cardMode":"single","primaryStory":"Open","viewport":"WxH"}— seeSelect. A closed trigger shows none of the compound parts. - Animated sections need transitions zeroed.
Herofades in on a 300 ms timer plus a 1 s transition; the capture harness only waits on fonts and images, so an unmodified render screenshots at ~10 % opacity. See thestillstyle inpreviews/Hero.tsx.FeaturesandProcessuse IntersectionObserver instead and settle on their own. - Use real Cloudrite copy (Auckland IT services, 021 107 7483, the four service
lines), never
foo/bar. - Recompile the CSS after authoring previews: Tailwind only emits utilities for
classes it can see, and
@source './previews'covers the preview files.
Excluded from the DS, on purpose
components/chatwidget.tsx— Chatwoot script injector. Renders no markup and fetches/api/chatwooton mount.components/ui/toaster.tsx— legacyToaster, colliding withui/sonner.tsx's. Sonner wins; the site uses neither.ThemeProvider— in the bundle (wrap-able) but given no card.
Re-sync risks
.ds-pkg/and.design-sync/compiled.cssare gitignored and regenerated. A fresh clone must run all threebuildCmdsteps before the converter, or the entry and stylesheet won't exist.- The scaffolding is inert to the app build, but only because of dot-prefixing.
tsconfig.jsonincludes**/*.ts(x)from the repo root with onlynode_modulesexcluded; TypeScript's glob matcher skips directories beginning with., which is the sole reason.design-sync/previews/*.tsx(they import'cloudrite', which does not resolve in the app) and.ds-pkg/types/**don't enter the app's program. Verified:pnpm buildand a coldtsc --noEmitboth exit 0. If any of this is ever moved to a non-dotted path, add it to the app tsconfig'sexcludefirst. next buildis not a substitute for the staging package, but it does compile the Tailwind stylesheet (~136 KB, hash-named under.next/static/chunks/*.css). That is smaller than.design-sync/compiled.css(~170 KB) because Tailwind only emits classes it can see and the app never references the utilities used by.design-sync/previews/. The app build supplies no library entry and no declarations, which is what.ds-pkg/exists to provide.- The fonts are a point-in-time copy. If Google Fonts revs the woff2, nothing here notices; the committed files keep working. Re-download only deliberately.
- The parts tables in
.design-sync/docs/are generated bymake-pkg.mjsfrom the declaration emit — never hand-edit them; they are overwritten every run. The prop signature column falls back to—for declarations that aren't plain functions. ROOT_OVERRIDEinmake-pkg.mjsis a hand-maintained exception list (currently justtoast.tsx → Toast, whose first export is the provider). A new shadcn component whose first export isn't the root needs an entry there.- Only 40 of 61 components have authored previews; the other 21 ship the floor card and
can be authored on any later sync.
.design-sync/previews/and the grades carry forward. - Playwright drives the machine's installed Google Chrome via
DS_CHROMIUM_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"(no 200 MB chromium download). Export it beforepackage-validate.mjs/package-capture.mjs, or the render check is skipped.
Findings for the Cloudrite team (not sync bugs)
--accentis set to the brand green, byte-identical to--primary(oklch(0.75 0.18 145)), in both the:rootand.darkblocks ofapp/globals.css. shadcn's default is a low-contrast neutral used for hover and placeholder surfaces, so everybg-accentconsumer is now vivid green — most visiblySkeleton(loading blocks render as solid green bars), plus the hover states indropdown-menu,command,menubar,navigation-menu,calendar,item,toggle,buttonanddialog. The previews render this faithfully rather than papering over it. If it wasn't intentional, giving--accentits own muted value is a one-line change.styles/globals.cssis an unused light-theme duplicate ofapp/globals.css(v0 scaffolding). Nothing imports it.
Gotchas found after the first NOTES pass
package-build.mjswipes the whole--outdir,_screenshots/included. Any review sheet captured before a full rebuild is gone; re-runpackage-capture.mjsfor anything still awaiting a grade.- Overlay content must not be given
className="relative". Radix positionsDialogContentwithfixedplustranslate(-50%,-50%); switching it torelativekeeps the translate but drops the centring anchor, so the card clips the title. Size the card viacfg.overrides.<Name>.viewportinstead. - Radix autofocuses the first focusable in an open overlay, which screenshots as a
green text selection.
onOpenAutoFocus={(e) => e.preventDefault()}gives a clean still. DS_CHROMIUM_PATHmust be exported forresync.mjstoo — without it the driver's validate stage fails[RENDER_SKIPPED]and skips capture onprior_failure.