feat: exports, AI rules files, starter projects and public API (milestone 4) #4

Merged
marti merged 7 commits from feature/m4-exports into main 2026-10-27 01:34:44 +01:00
Owner

Milestone 4 from docs/roadmap/v1-remaining-work.md. Design in ADR 0010 (exports) and ADR 0011 (public read API, anonymous only per design system, as decided).

Summary

flowchart LR
    V["version (or draft) + project overrides"] --> C["core: resolve per mode"]
    C --> SD["Style Dictionary 5: transforms call cssVariableName / valueToCss"]
    SD --> F["dtcg, css, tailwind-v4, tailwind-v3, daisyui, shadcn, scss, ts"]
    C --> R["rulesFiles: CLAUDE.md, AGENTS.md, .cursor/rules, copilot"]
    F --> S["starterProject: Vite + React + Tailwind v4 + library"]
    R --> S
    F --> P["GET /api/public/v1/design-systems/:id/versions/:v/:file"]
    F --> Z["tokens.zip / starter.zip downloads"]
    S --> Z
 design_systems
+  public_access boolean not null default false   # admins only, audited (migration 0002)
 /api/public/v1/design-systems/:id/versions/:version/:file
+  public DS -> anyone; otherwise session with read access; else 404
+  semver: max-age=31536000, immutable; latest: max-age=60 + ETag/304
+  public: Access-Control-Allow-Origin *, no credentials, CORP cross-origin
 /api/v1/orgs/:orgId/design-systems/:id/export/{tokens,starter}.zip?source=latest|draft|<v>
 /api/v1/orgs/:orgId/projects/:id/export/{tokens,starter}.zip   # pinned or latest + overrides

Choices to look at:

  • Tailwind v4 names: tokens go into Tailwind's namespaces by type and leading path (space.* becomes --spacing-*, typography becomes --text-*). The table is in packages/export/README.md.
  • daisyUI themes: each mode becomes its own theme with that mode's resolved values. daisyUI switches themes on data-theme, separately from data-mode.
  • Zip layout: each zip unpacks into one top-level folder.
  • Starter styling: the starter always uses Tailwind v4, as ADR 0010 and the brief say, even when the stack profile says v3.
  • Bundle leak, fixed: Style Dictionary is imported dynamically, because the web app pulls in @designbase/export through the preview package's theme mapping. Without that, Style Dictionary and Prettier landed in the editor bundle (711 kB). It is now 68 kB.

trail check flags 20 files. These are keyword matches against the two learnings:

  • "Token document" hits: no token document storage changed.
  • "jsonb" hit: drizzle's generated snapshot of the existing stack_profile column.
  • "orm" hit: exports.ts, which imports from @designbase/db/orm exactly as the learning requires.

Evidence

The done-when items:

starter build (DESIGNBASE_STARTER_BUILD=1 pnpm --filter @designbase/export test:starter)
  shadcn design system (lucide)   unzip -> pnpm install -> pnpm build -> dist/index.html   pass
  daisyUI design system (tabler)  unzip -> pnpm install -> pnpm build -> dist/index.html   pass
  CI: new "starter" job runs it beside the main job
formats: assertions per format on --color-brand-600, color.primary.background light/dark,
         typography, shadow, duration/cubic-bezier, a dark-only project override (no snapshots)
parity (packages/preview/src/export-parity.test.ts):
  canvas token CSS == css export, for shadcn and daisyUI; mapping == themeMappingDeclarations

Production API image, smoke-tested against Postgres:

publish 201
anonymous private 404
make public 200
tokens.css   200 text/css          public, max-age=31536000, immutable
tailwind.css 200 text/css          public, max-age=31536000, immutable
tokens.json  200 application/json  public, max-age=31536000, immutable
daisyui.css  200 text/css          (2 themes: light, dark)
starter.zip  200 28586 bytes
Suite Before After
core 45 45
export 3 33 (+2 opt-in starter builds)
preview 42 45
web 61 71
api 45 62

pnpm lint, pnpm typecheck, pnpm build and both Docker images are clean.

Merge Danger

Door: two-way

Migration 0002 only adds a column with a default.

Blast Radius: api

  • Public data: the public API is the first unauthenticated data endpoint. It only answers for design systems whose admins turn on publicAccess, and only for published versions.
  • Caching: turning publicAccess off does not purge copies already cached for a year. The setting says so.
  • Starter pins: generated starters pin exact versions. Refreshing them is a code change, and the starter CI job checks it.

🤖 Generated with Claude Code

Milestone 4 from `docs/roadmap/v1-remaining-work.md`. Design in ADR 0010 (exports) and ADR 0011 (public read API, anonymous only per design system, as decided). ## Summary ```mermaid flowchart LR V["version (or draft) + project overrides"] --> C["core: resolve per mode"] C --> SD["Style Dictionary 5: transforms call cssVariableName / valueToCss"] SD --> F["dtcg, css, tailwind-v4, tailwind-v3, daisyui, shadcn, scss, ts"] C --> R["rulesFiles: CLAUDE.md, AGENTS.md, .cursor/rules, copilot"] F --> S["starterProject: Vite + React + Tailwind v4 + library"] R --> S F --> P["GET /api/public/v1/design-systems/:id/versions/:v/:file"] F --> Z["tokens.zip / starter.zip downloads"] S --> Z ``` ```diff design_systems + public_access boolean not null default false # admins only, audited (migration 0002) /api/public/v1/design-systems/:id/versions/:version/:file + public DS -> anyone; otherwise session with read access; else 404 + semver: max-age=31536000, immutable; latest: max-age=60 + ETag/304 + public: Access-Control-Allow-Origin *, no credentials, CORP cross-origin /api/v1/orgs/:orgId/design-systems/:id/export/{tokens,starter}.zip?source=latest|draft|<v> /api/v1/orgs/:orgId/projects/:id/export/{tokens,starter}.zip # pinned or latest + overrides ``` Choices to look at: - **Tailwind v4 names:** tokens go into Tailwind's namespaces by type and leading path (`space.*` becomes `--spacing-*`, typography becomes `--text-*`). The table is in `packages/export/README.md`. - **daisyUI themes:** each mode becomes its own theme with that mode's resolved values. daisyUI switches themes on `data-theme`, separately from `data-mode`. - **Zip layout:** each zip unpacks into one top-level folder. - **Starter styling:** the starter always uses Tailwind v4, as ADR 0010 and the brief say, even when the stack profile says v3. - **Bundle leak, fixed:** Style Dictionary is imported dynamically, because the web app pulls in `@designbase/export` through the preview package's theme mapping. Without that, Style Dictionary and Prettier landed in the editor bundle (711 kB). It is now 68 kB. `trail check` flags 20 files. These are keyword matches against the two learnings: - "Token document" hits: no token document storage changed. - "jsonb" hit: drizzle's generated snapshot of the existing `stack_profile` column. - "orm" hit: `exports.ts`, which imports from `@designbase/db/orm` exactly as the learning requires. ## Evidence The done-when items: ```text starter build (DESIGNBASE_STARTER_BUILD=1 pnpm --filter @designbase/export test:starter) shadcn design system (lucide) unzip -> pnpm install -> pnpm build -> dist/index.html pass daisyUI design system (tabler) unzip -> pnpm install -> pnpm build -> dist/index.html pass CI: new "starter" job runs it beside the main job formats: assertions per format on --color-brand-600, color.primary.background light/dark, typography, shadow, duration/cubic-bezier, a dark-only project override (no snapshots) parity (packages/preview/src/export-parity.test.ts): canvas token CSS == css export, for shadcn and daisyUI; mapping == themeMappingDeclarations ``` Production API image, smoke-tested against Postgres: ```text publish 201 anonymous private 404 make public 200 tokens.css 200 text/css public, max-age=31536000, immutable tailwind.css 200 text/css public, max-age=31536000, immutable tokens.json 200 application/json public, max-age=31536000, immutable daisyui.css 200 text/css (2 themes: light, dark) starter.zip 200 28586 bytes ``` | Suite | Before | After | | --- | --- | --- | | core | 45 | 45 | | export | 3 | 33 (+2 opt-in starter builds) | | preview | 42 | 45 | | web | 61 | 71 | | api | 45 | 62 | `pnpm lint`, `pnpm typecheck`, `pnpm build` and both Docker images are clean. ## Merge Danger **Door:** two-way Migration 0002 only adds a column with a default. **Blast Radius:** api - **Public data:** the public API is the first unauthenticated data endpoint. It only answers for design systems whose admins turn on `publicAccess`, and only for published versions. - **Caching:** turning `publicAccess` off does not purge copies already cached for a year. The setting says so. - **Starter pins:** generated starters pin exact versions. Refreshing them is a code change, and the starter CI job checks it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ADR 0010: pure export generators in packages/export, core resolving and
Style Dictionary formatting. ADR 0011: versioned public URLs, anonymous
access only for design systems that turn publicAccess on, and caching.
Adds the ExportSource and ExportFile types the generators share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Design systems gain publicAccess (off by default), which only their admins
can change and which is audited. The design system page shows it as a switch
with the public URL of the latest tokens once it is on. The public read API
that honours it follows (ADR 0011).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core resolves each mode once; Style Dictionary 5 formats the resolved tokens
with transforms that call cssVariableName and valueToCss, into DTCG (resolver
document plus sets, and a single-file bundle), CSS variables, Tailwind v4 and
v3, daisyUI themes, shadcn/ui variables, SCSS and TypeScript. rulesFiles
writes CLAUDE.md, AGENTS.md, a Cursor rule and Copilot instructions from one
model. starterProject builds a Vite + React + Tailwind v4 project for the
stack profile's library with exact versions, and zipFiles packs it.

A parity test in packages/preview checks that the canvas and the css export
produce the same declarations. The opt-in starter build test installs and
builds a shadcn and a daisyUI starter; CI runs it in its own job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /api/public/v1/design-systems/:id/versions/:version/:file serves the
published token files (ADR 0011): anonymous for design systems with
publicAccess, otherwise to sessions with read access, 404 for anything else.
Semver versions are cached as immutable, latest revalidates with an ETag,
and public responses allow any origin without credentials. resolveActor is
the seam milestone 5's API tokens plug into.

Design systems (latest, a version or the draft) and projects (pinned or
latest version with overrides) download as tokens.zip (every format plus the
AI rules files) and starter.zip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pick latest, a version or the draft and download the starter project or the
tokens and rules zip; public design systems also list their public file URLs.
Projects download their pinned version with overrides applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: exports, the public API and downloads
Some checks failed
ci / check (pull_request) Failing after 1m50s
ci / starter (pull_request) Successful in 1m29s
980e6f69bb
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(web): give page tests room under CI's parallel load
All checks were successful
ci / check (pull_request) Successful in 2m7s
ci / starter (pull_request) Successful in 1m28s
d0384cc6b4
CI runs every package's suite at once. Page tests that load lazy route
chunks then miss Testing Library's 1 s wait and Vitest's 5 s limit, which
failed the check job on main since milestone 3 and on this branch. Waits are
now 5 s and tests 20 s; three full parallel runs in a CI-like container pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
marti merged commit f7bad072e6 into main 2026-10-27 01:34:44 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marti/designbase!4
No description provided.