feat: foundation (milestone 1) and DTCG token core #1

Merged
marti merged 18 commits from feature/foundation-and-token-core into main 2026-10-26 23:21:46 +01:00
Owner

Summary

Milestone 1 (foundation) complete, plus the core of milestone 2 (DTCG token engine, drafts, versions).

apps/
├── api/        # Fastify: Better Auth at /api/auth, app API at /api/v1, services own access + audit
└── web/        # React SPA: TanStack Router/Query, daisyUI themes, Radix primitives
packages/
├── shared/     # Zod contracts: orgs, members, invitations, resources, DTCG values, drafts, versions
├── core/       # pure rules: roles/access, DTCG flatten/resolve/validate/diff/CSS, contrast, semver
└── db/         # Drizzle schema + 2 migrations (Postgres 18, uuidv7 ids)
compose.yaml    # postgres -> secrets -> migrate -> api -> web, one `docker compose up`
docs/           # install, development, deployment, architecture, database, ADRs 0001-0007

Request path and access check:

sequenceDiagram
    participant B as Browser
    participant W as web (Caddy / Vite)
    participant A as api
    participant D as Postgres
    B->>W: /api/v1/... (session cookie, same origin)
    W->>A: proxy
    A->>D: session -> membership + share -> effective role (core/access.ts)
    A->>D: query / write + audit event in one transaction
    A-->>B: JSON parsed through the shared Zod schema

Token resolution order (ADR 0006): primitive -> semantic -> component -> project override -> mode -> project override for that mode, then alias resolution on the merged tree.

Evidence

  • Before: empty repo (README.md only).
    After:
    • pnpm lint clean, pnpm typecheck 5/5 packages
    • @designbase/core 41 tests (access rules, alias chains, cycles reported per member, modes + overrides, diff -> operations round trip, CSS output, WCAG contrast, starter palette passes AA in light and dark)
    • @designbase/api 37 tests through app.inject() against real Postgres 18 (auth rejection, org/member/invitation rules, last-owner protection, sharing, cross-org isolation, draft revision conflicts, publish blocked on invalid tokens, semver bump suggestion)
    • @designbase/web 16 tests (Testing Library + MSW)
    • Full stack run with docker compose up (built images, migrations, seed), walked through sign-up, invitations, editor and viewer views, light and dark themes.

Merge Danger

Door: two-way

First code on main; nothing deployed depends on it. The two migrations become the baseline once anyone runs them, so later schema changes must be new migrations.

Blast Radius: none

Notes for review:

  • trail check reports 20 conflicts. All are keyword matches on this branch's own learnings ("drizzle-orm must come from @designbase/db/orm" and "token documents are json, not jsonb"); the flagged lines follow both rules.
  • Not yet in this PR: project version pinning and theme overrides in the API/UI (rest of milestone 2), the token editor and canvas (milestone 3).
  • CI is a Forgejo workflow (.forgejo/workflows/ci.yml) expecting a runner labelled docker; it has not run yet.

🤖 Generated with Claude Code

## Summary Milestone 1 (foundation) complete, plus the core of milestone 2 (DTCG token engine, drafts, versions). ```text apps/ ├── api/ # Fastify: Better Auth at /api/auth, app API at /api/v1, services own access + audit └── web/ # React SPA: TanStack Router/Query, daisyUI themes, Radix primitives packages/ ├── shared/ # Zod contracts: orgs, members, invitations, resources, DTCG values, drafts, versions ├── core/ # pure rules: roles/access, DTCG flatten/resolve/validate/diff/CSS, contrast, semver └── db/ # Drizzle schema + 2 migrations (Postgres 18, uuidv7 ids) compose.yaml # postgres -> secrets -> migrate -> api -> web, one `docker compose up` docs/ # install, development, deployment, architecture, database, ADRs 0001-0007 ``` Request path and access check: ```mermaid sequenceDiagram participant B as Browser participant W as web (Caddy / Vite) participant A as api participant D as Postgres B->>W: /api/v1/... (session cookie, same origin) W->>A: proxy A->>D: session -> membership + share -> effective role (core/access.ts) A->>D: query / write + audit event in one transaction A-->>B: JSON parsed through the shared Zod schema ``` Token resolution order (ADR 0006): primitive -> semantic -> component -> project override -> mode -> project override for that mode, then alias resolution on the merged tree. ## Evidence - **Before:** empty repo (`README.md` only). **After:** - `pnpm lint` clean, `pnpm typecheck` 5/5 packages - `@designbase/core` 41 tests (access rules, alias chains, cycles reported per member, modes + overrides, diff -> operations round trip, CSS output, WCAG contrast, starter palette passes AA in light and dark) - `@designbase/api` 37 tests through `app.inject()` against real Postgres 18 (auth rejection, org/member/invitation rules, last-owner protection, sharing, cross-org isolation, draft revision conflicts, publish blocked on invalid tokens, semver bump suggestion) - `@designbase/web` 16 tests (Testing Library + MSW) - Full stack run with `docker compose up` (built images, migrations, seed), walked through sign-up, invitations, editor and viewer views, light and dark themes. ## Merge Danger **Door:** two-way First code on `main`; nothing deployed depends on it. The two migrations become the baseline once anyone runs them, so later schema changes must be new migrations. **Blast Radius:** none Notes for review: - `trail check` reports 20 conflicts. All are keyword matches on this branch's own learnings ("drizzle-orm must come from @designbase/db/orm" and "token documents are json, not jsonb"); the flagged lines follow both rules. - Not yet in this PR: project version pinning and theme overrides in the API/UI (rest of milestone 2), the token editor and canvas (milestone 3). - CI is a Forgejo workflow (`.forgejo/workflows/ci.yml`) expecting a runner labelled `docker`; it has not run yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Root config (Biome, TypeScript, Turborepo, Nix devShell), the shared Zod
contracts for organisations, members, invitations, design systems and
projects, and the pure role rules that the API and MCP server will both
enforce.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Better Auth tables, organisations, memberships, invitations, design
systems, projects, per-resource shares and the audit log. Ids are
Postgres 18 uuidv7(); invitation tokens are stored as SHA-256 hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Better Auth handles email/password sessions (and GitHub/Google when
configured) under /api/auth. /api/v1 covers organisations, members,
invitations, design systems, projects and sharing, all checked against
the role rules in @designbase/core and written to the audit log.
Route tests run through app.inject() against a real Postgres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compose.yaml brings up Postgres, a one-shot migrate service, the API and
the web app with a single command; a throwaway service generates the
session secret into a volume so a fresh install needs no .env.
AUTH_SECRET_FILE lets the API read that (or a Docker secret).
compose.dev.yaml swaps in the hot-reload targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TanStack Router and Query over the shared Zod contracts, daisyUI themes
for light and dark, Radix primitives for dialogs, menus and selects.
Covers sign-in and sign-up, invitation links, the organisation shell,
members and invitations, design systems (stack profile, access,
sharing) and projects. The Caddy-based runtime image serves the bundle
and proxies /api so the app and API share an origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flattening with group type inheritance and $extends, alias and JSON
Pointer resolution with cycle detection, layered resolution of tiers,
project overrides and modes, validation (types, values, references,
WCAG contrast), dependents, diffs and edit operations, CSS output, OKLCH
colour handling via culori, legacy value conversion, and a starter
document whose light and dark roles pass AA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every design system gets a draft (starter tokens and guidelines) edited
by full replace or token operations under a revision check. Publishing
snapshots draft, guidelines and stack profile into an immutable semver
version, refuses invalid tokens and suggests the bump from the diff.
Resolved tokens and multi-mode CSS are served for the draft, the latest
or any version. Token documents are stored as json so key order
survives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docker compose run --rm migrate pnpm --filter @designbase/api db:seed`
loads the demo user, organisation, design system and project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore: leave agent config formatting to trail
Some checks failed
ci / check (pull_request) Failing after 1m15s
aa2be01424
trail rewrites .claude/settings.json on session start; excluding it from
Biome stops the two tools reformatting it back and forth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What is left of the original specification, the design decisions already
taken for it, and a checkable done condition per milestone, so another
agent can pick the work up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci: keep the pnpm store out of the checkout
Some checks failed
ci / check (pull_request) Failing after 1m13s
61f9d51a5c
On the runner pnpm fell back to ./.pnpm-store, and Biome then linted
the ~600 store index files and failed. Point the store at the path the
cache step saves and ignore .pnpm-store wherever it appears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci: run Postgres inside the job container
All checks were successful
ci / check (pull_request) Successful in 1m52s
d1bc37ce20
The runner attaches job and service containers to Docker's default
bridge, where service names do not resolve, so the API tests could not
reach the postgres service. Installing Postgres 18 from PGDG in the job
container works on any runner network setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
marti merged commit a3cf971b04 into main 2026-10-26 23:21:46 +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!1
No description provided.