git:20261007.a7e0084 to git:20261020.8d47d7a
61 added, 150 removed. Audit A to A.
- # design-tokens Agent Notes
-
- Scoped instructions for `packages/design-tokens/`. Read together with
- the [root `AGENTS.md`](../../AGENTS.md) and the
- [`README.md`](README.md) (which is the consumer-facing doc).
-
- ## Authority order
-
- 1. The published Figma library (file key in `figma-tokens.lock`).
- - Canonical **semantic colours** come from the committed DTCG export under
- `figma-export/`, regenerated by `:sync-export`. Variables are not
- reachable via the REST `:sync` without Figma Enterprise, so the Plugin-API
- export is the source of truth for their per-mode values.
- 2. `src/overrides.ts` — only for values Figma doesn't own.
- 3. Nothing else. Atoms must not declare hex codes; consumers must not
- re-derive radii or spacing.
-
- If a value belongs to the design system, put it in Figma and re-sync.
- If it doesn't belong (font fallback stack, motion curve, breakpoint),
- add it to `overrides.ts` with a doc comment explaining why.
-
- ## Token sync flows
-
- - **Figma plugin push:** branch `design-tokens/figma-sync`, token path
- `packages/design-tokens/figma-export/`. CI triggers on `push` to that branch
- (`figma-export/**` only) and merges `main` into the sync branch before
- generating so workflow helpers stay current.
- - `:sync-export` (`scripts/sync-from-export.ts`) — a **generic, file-name
- agnostic** DTCG resolver. It reads *every* `*.json` under `figma-export/`,
- builds one cross-collection registry, resolves every `{alias}` (including
- per-mode Light/Dark chains) to a concrete value, and writes the resolved
- shadcn surface to `src/generated/figma.tokens.json`. It **fails loud** on any
- unresolved reference or an empty colour surface — never filter exports by file
- name, and never downgrade a resolution failure to a warning.
- - `:sync` (`scripts/sync-from-figma.ts`) — Figma REST / Enterprise. Does not
- fetch variables on standard plans.
-
- After any sync, run `:generate` and review the snapshot diff.
-
- ### How a collection is classified
+ # Agent Instructions — `packages/design-tokens`
- Roles are **declared** in [`src/collections.ts`](src/collections.ts), keyed by
- Figma collection name (`$metadata.collection`, falling back to the file stem) —
- never inferred from a collection's shape:
+ The Figma-driven `--zl-*` variables, the one contract the login surface and the
+ console share (ADR 055). Consumer docs: [`README.md`](README.md).
- | Role | Lands in | Emitted as |
- | --- | --- | --- |
- | `semantic` | `color.<name>` (from its `base/*` group) | `--zl-<name>` (shadcn roles) |
- | `themed` | `themed.<group>.<name>` | `--zl-<group>-<name>` |
- | `viewport` | `typography.<mode>` | not emitted |
- | `primitives` | `radius`/`text`/`font`/`font-weight`/`container` | per category |
- | `registry-only` | nothing | nothing |
+ ## Authority
- Every collection feeds the alias registry regardless of role, so a
- `registry-only` collection is still referenceable as `{brand.purple.500}` — the
- role controls what a collection *surfaces*, not whether it can be resolved
- through.
+ 1. The published Figma library (its file key is in `figma-tokens.lock`).
+ Semantic colours come from the committed DTCG export in `figma-export/`
+ (`:sync-export`): the REST `:sync` cannot read variables without Figma
+ Enterprise.
+ 2. `src/overrides.ts`, only for values Figma does not own (font fallbacks,
+ motion curves, breakpoints), each with a doc comment saying why.
+ 3. Nothing else: atoms declare no hex codes, and consumers never re-derive
+ radii or spacing.
- **Adding a collection in Figma is a decision, so the sync makes you make it —
- but as a red check, not a dead workflow.** An export the manifest doesn't name
- falls back to `registry-only` (surfacing nothing) and is reported in
- `$source.unclassifiedCollections`; a stale manifest entry lands in
- `$source.staleCollectionRoles`. `sync-from-export.spec.ts` asserts both are
- empty, so `full-pr` goes red on the sync PR until someone classifies it.
+ A value that belongs to the design system goes into Figma and is re-synced.
- Do **not** turn these back into throws. `:sync-export` runs *before* the
- workflow opens or updates the sync PR, so a throw kills the run with no PR and
- therefore no check — the only trace is a workflow log nobody reads. Failing
- soft lands a reviewable PR and puts the signal where the work is. Exactly one
- collection may hold `semantic`; that one does still throw, because there is no
- safe default.
+ ## Syncing
- This replaced shape inference ("the Light/Dark collection is the semantic
- surface, anything else multi-mode is viewport typography"), which silently
- mis-routed `Syntax` and `Gradient Colors` into one bucket where the
- later-sorting file replaced the earlier one outright. Within a role, `claim()`
- still guards two collections landing on the same key. Neither failure is
- catchable downstream: the snapshot test only sees names that already reached
- `build.ts`.
+ - The Figma plugin pushes `figma-export/` to the branch
+ `design-tokens/figma-sync`; CI (`sync-design-tokens.yml`) merges `main` into
+ it and opens the sync PR.
+ - `:sync-export` (`scripts/sync-from-export.ts`) resolves every `*.json` under
+ `figma-export/` through one alias registry. Never pick exports by file name,
+ and never downgrade an unresolved reference or an empty colour surface to a
+ warning.
+ - After any sync, run `:generate` and review the snapshot diff.
+ - Each collection's role is declared in [`src/collections.ts`](src/collections.ts),
+ keyed by its Figma collection name, never inferred from its shape. The
+ roles and what each emits are documented on `CollectionRole`.
+ - A collection the manifest does not name falls back to `registry-only`, with
+ a warning, and the sync PR still opens; `sync-from-export.spec.ts` then
+ fails `full-pr` until someone classifies it. A stale entry fails it the same
+ way.
+ - Never turn those failures into throws: `:sync-export` runs before the sync
+ PR exists, so a throw leaves no check, only a log. The `semantic` role is
+ the exception: anything but exactly one such collection throws, because
+ there is no safe default.
## Colour surfaces
- - **shadcn** (`--zl-background`, `--zl-foreground`, `--zl-primary`, `--zl-card`,
- `--zl-border`, `--zl-sidebar-*`, `--zl-chart-*`) comes from the designer export
- and lives under `tokens.theme.*`. Colours are authored as `{ dark, light }`
- pairs; `build.ts` emits the light halves under `[data-theme="light"]`.
- - **Themed groups** (`--zl-syntax-*`, `--zl-gradient-*`) are the designer's
- other Light/Dark collections, under `tokens.<group>.*`. They theme like the
- shadcn colours but are deliberately kept out of `css/shadcn.css`: that file
- owns the *unprefixed* shadcn contract, and `syntax`/`gradient` are not shadcn
- role names. Reach them as `--zl-*` or `bg-zl-gradient-red-start`.
- - **Composed gradients** (`--zl-gradient-red`) are `linear-gradient()` values
- built in `build.ts` from the exported gradient colours, at the angle and stop
- positions recorded in `overrides.ts`. Figma keeps each gradient as a style,
- which the sync cannot read; the override goes once it publishes the angle and
- positions as variables.
-
- **Console exception:** `apps/console` imports `css/shadcn.css` and authors the
- **unprefixed** shadcn contract (`bg-background`, `text-muted-foreground`). Do
- not reintroduce `bg-zl-*` there — the bridge file owns the mapping.
-
- ## Surfaces you can change
-
- - `src/overrides.ts` — hand-written supplements. Adding a new category
- here means updating `scripts/build.ts` to emit it.
- - `scripts/sync-from-figma.ts` — Figma REST handling. Be careful: this
- runs in CI with a real token; preserve the pinned-library guarantee.
- - `scripts/sync-from-export.ts` + `figma-export/` — offline DTCG sync and its
- committed export provenance. Re-export from Figma and re-run `:sync-export`
- to refresh values.
- - `src/collections.ts` — the role each Figma collection plays. Edit it when the
- designer adds, renames, or repurposes a collection; the sync fails loudly
- until it matches the export.
- - `scripts/build.ts` — emitter. Output must remain deterministic for
- the snapshot test to be meaningful.
- - `src/tokens.snapshot.spec.ts` — snapshot. Update intentionally when
- the Figma sync legitimately renames a token; never blanket-accept the
- snapshot diff without reviewing what consumers will need.
-
- ## Surfaces you must NOT change by hand
-
- - `src/generated/figma.tokens.json` — overwritten by `:sync` /
- `:sync-export`.
- - `src/generated/tokens.css`, `tokens.ts`, `tailwind.css`, `shadcn.css` —
- overwritten by `:generate`.
- - `figma-tokens.lock` — only bumped as part of a sync PR.
-
- ## Output ordering
-
- `scripts/build.ts` emits the shadcn colours (`theme.*`), then the remaining
- categories in a fixed order (spacing → custom roles → radius → text → font
- weight → shadow → font family → motion → focus → breakpoint → container →
- themed groups → composed gradients). Spacing keys are numeric-sorted. Themed
- groups emit after the fixed categories, in export order, so a
- new Light/Dark collection from Figma appends to the diff rather than shifting
- every existing line. Keep new categories appended at the end for the same
- reason.
-
- ## Tests
-
- Single Vitest project, Node mode: `tokens.snapshot.spec.ts` (public-name guard) and `sync-from-export.spec.ts` (resolver logic + real-export
- regression). If you add a token category, extend the snapshot to cover its keys
- so it can't silently disappear.
+ - The shadcn roles (`--zl-background`, `--zl-primary`, …) live under
+ `tokens.theme.*` as `{ dark, light }` pairs; `build.ts` emits the light
+ halves under `[data-theme="light"]`.
+ - Themed groups (`--zl-syntax-*`, `--zl-gradient-*`) theme the same way but
+ stay out of `css/shadcn.css`, which owns the unprefixed shadcn contract.
+ - Composed gradients (`--zl-gradient-red`) are built in `build.ts` from the
+ exported colours, at the angles and stops in `overrides.ts`, until Figma
+ publishes those as variables.
+ - The console imports `css/shadcn.css` and authors the unprefixed utilities
+ (`bg-background`, `text-muted-foreground`); never reintroduce `bg-zl-*`
+ there.
- ## Branding overrides at runtime
+ ## Changing the package
- Tenants override colours/typography/shape on a per-flow basis through
- `<zitadel-login branding="...">`. That overlay is applied by
- `packages/components/src/orchestrator/branding-to-tokens.ts` — it
- writes to the same `--zl-*` variables that `tokens.css` defines, on
- the orchestrator's own shadow root. Do not introduce a separate
- "branding tokens" surface; everything routes through `--zl-*`.
+ - Never hand-edit `src/generated/`: `:sync`/`:sync-export` and `:generate`
+ overwrite it. Fix the input (lock, export or overrides) and regenerate.
+ - `figma-tokens.lock` changes only in a sync PR.
+ - `scripts/build.ts` must stay deterministic. A new category in
+ `overrides.ts` needs `build.ts` to emit it, appended after the existing
+ ones so the diff does not shift every line.
+ - `scripts/sync-from-figma.ts` runs in CI with a real token; keep its
+ pinned-library guarantee.
+ - Update `src/tokens.snapshot.spec.ts` only for an intended rename, after
+ reviewing what consumers need; extend it to cover a new category's keys.
+ - Never import this package from `packages/api` or `packages/api-mock`.
- ## Don't
+ ## Branding at runtime
- - Don't add a token category to `overrides.ts` that Figma should own.
- - Don't import this package from `packages/api` or `packages/api-mock`.
- Tokens are a UI concern.
- - Don't hand-edit anything under `src/generated/`. If the generated
- output is wrong, fix the input (lock, json, or overrides) and re-run
- `:generate`.
- - Don't re-couple `:sync-export` to specific export file names. The designer
- reorganises collections freely; the resolver must stay shape-driven.
+ The orchestrator paints a flow's branding, which arrives in the flow response,
+ by writing the same `--zl-*` variables on its own shadow root
+ (`packages/components/src/orchestrator/branding-to-tokens.ts`). There is no
+ `branding` attribute; never add a separate branding-token surface.