Apply pipeline
How the tier resolver turns TabOverrides into CSS-var writes, the v2 export schema, applyEndpoint + applyRouting, request/response envelopes, and atomicity guarantees.
The apply pipeline turns the panel's in-memory override state into CSS writes and (when the host wires an endpoint) into source-file rewrites on disk. There are two distinct paths:
:rootinline style — applied client-side, immediately, on every user tweak.Apply-to-disk — triggered by the Apply button; POSTs a diff to the host's dev endpoint which routes it to the bin server.
State → tier resolver → CSS emission
TabOverrides shape
The panel's persisted state for each tab is a two-level nested map:
type TabOverrides = Readonly<Record<string, Readonly<Record<string, string>>>>;
// tierId → itemId → overrideValueExample:
const overrides: TabOverrides = {
raw: { 'ease-in': 'cubic-bezier(0.42, 0, 1, 1)' },
semantic: { 'tab-open': 'ease-in' }, // reference tier — value is a raw-tier item id
};resolveTierItemValue
The core resolver (resolveTierItemValue) determines the effective CSS value for a single item:
Literal tier (no
referencesTier): returns the override string, orpill.customDefaultwhen a pill is present and no override is active, oritem.defaultas the final fallback.Reference tier (
referencesTieris set): the override is interpreted as the id of an item in the named tier. Returns{ kind: 'ref', targetCssVar }pointing at that item'scssVar.
emitTierItemCssValue
Converts a ResolvedTierItem to the final string written to the CSS custom property:
Literal → value string as-is (e.g.
1.25rem).Ref →
var(--targetCssVar)(e.g.var(--myapp-easing-ease-in)).
Cross-tier reference example
Given a two-tier easing tab (raw + semantic):
semantic.tab-open override = 'ease-in'
→ looks up 'ease-in' in raw tier
→ raw tier item ease-in has cssVar '--myapp-easing-ease-in'
→ emits --myapp-transition-tab-open: var(--myapp-easing-ease-in)Only the raw tier item's cssVar is written to :root directly. The semantic tier item always emits var(...) — it never writes a raw CSS value.
:root inline style write
The panel writes overrides to document.documentElement.style.setProperty(cssVar, value) on every user change. When no override is active for an item, the inline property is removed (so the stylesheet default wins).
The Color tab's palette, base-role, and semantic slots are written by the same mechanism — palette items directly, semantic items via var(--palette-cssVar).
Export schema (v2)
The panel exports and imports overrides as a JSON envelope with a $schema field for version identification:
{
"$schema": "zudo-design-tokens/v2",
"--myapp-spacing-md": "1.5rem",
"--myapp-easing-ease-in": "cubic-bezier(0.42, 0, 1, 1)"
}The top-level keys (after $schema) are the CSS custom property names of every active override. The values are CSS strings. On import, the panel validates the $schema field against the canonical SCHEMA_V1 / SCHEMA_V2 / SCHEMA_V3 constants — NOT against PanelConfig.schemaId, which is a display-only label the serde never reads (see configurePanel reference). A $schema value that matches none of the three canonical constants is rejected with a schema-mismatch error.
Flat cssVar-keyed shape
The v2 export format is a flat map of cssVar → CSS string. Reference-tier items are expanded to their emitted var(...) value at export time — the exported file contains only the CSS values the user would see in :root, not the intermediate item ids.
Apply-to-disk
applyEndpoint
When PanelConfig.applyEndpoint is set, the Apply button is enabled. Clicking it POSTs the active override diff to this URL.
When applyEndpoint is undefined, the Apply button stays disabled with a tooltip — hosts that use export/import only can omit this field.
applyRouting
applyRouting: {
'myapp-spacing': 'src/styles/spacing.css',
'myapp-color': 'src/styles/color.css',
}A map of CSS-var prefix family (without leading -- and trailing -) to the repo-relative source file the bin server rewrites. Each token in the POST diff is routed to a file based on its prefix. Tokens with prefixes not in the map are rejected by the bin with "Unsupported cssVar prefix".
Apply is gated on applyEndpoint AND a non-empty applyRouting map. When either is missing, the Apply modal still mounts for diff preview but the action button remains disabled.
Which CSS blocks get rewritten
Each routed file is rewritten by scanning exactly two locations: the FIRST top-level :root { ... } block and the FIRST top-level @theme { ... } block (bare @theme, or with one modifier such as @theme inline — the shape Tailwind v4 prescribes when theme values reference other variables). Later blocks of either kind, and anything nested under @media / @layer / @supports, are not scanned.
Per override var, :root is tried first and @theme is the fallback — a var declared in BOTH blocks is rewritten only in :root. This makes a Tailwind v4 token file reachable out of the box:
:root {
--palette-cool-700: oklch(0.21 0.03 264);
}
@theme {
--spacing-md: 0.75rem;
--color-ink: light-dark(var(--palette-cool-700), var(--palette-cool-50));
}All three vars above — --palette-cool-700 (in :root), --spacing-md, and --color-ink (both in @theme) — apply cleanly in one request. A file that is 100% @theme (no :root block at all) also applies without error; only a file with neither block returns a 409 (see below).
Note
See Apply pipeline setup for the routing-JSON recipe that wires a Tailwind v4 host's @theme file into applyRouting.
Per-tier independence
The bin server processes each tier's tokens independently against the applyRouting map. Reference-tier and semantic reference overrides are included in the diff payload after resolution: they emit var(--target-cssvar) (for example, semantic.tab-open = 'ease-in' emits --myapp-transition-tab-open: var(--myapp-easing-ease-in)). The payload carries the resolved CSS value, not the raw item id.
Request & response envelopes
Request
POST <applyEndpoint>
Content-Type: application/json
{
"tokens": {
"--myapp-spacing-md": "1.5rem",
"--myapp-color-bg": "#0f172a"
}
} tokens is a flat object: CSS custom property name (must start with --) → CSS string value.
Response 200 (success)
{
"ok": true,
"updated": [
{
"file": "src/styles/spacing.css",
"changed": ["--myapp-spacing-md"],
"unchanged": [],
"unknown": ["--myapp-spacing-lg"],
"unknownOutsideBlock": ["--myapp-spacing-lg"]
}
],
"unknownCssVars": ["--myapp-spacing-lg"],
"unchangedCssVars": [],
"unknownOutsideBlockCssVars": ["--myapp-spacing-lg"]
}| Field | Meaning |
|---|---|
ok: true | Marks success. |
updated[] | Per-file results. changed[] = rewritten; unchanged[] = found in a scanned block but its value already matched; unknown[] = requested but not found in EITHER the file's scanned :root block or its scanned @theme block. |
updated[].unknownOutsideBlock | Subset of that file's unknown[]: the var IS declared somewhere in the file (found by a whole-file scan), just outside both scanned blocks — e.g. nested under @media/@layer/@supports, inside a grouped selector (:root, html { ... }), or in a SECOND top-level :root/@theme block. Distinguishes "wrong location" from "typo'd name" (the remaining entries in unknown[] not listed here). |
unknownCssVars | unknown[] flattened across all files, for UI feedback. |
unchangedCssVars | unchanged[] flattened across all files, for UI feedback. |
unknownOutsideBlockCssVars | unknownOutsideBlock[] flattened across all files, for UI feedback. |
Response 400 (bad request)
{ "ok": false, "error": "<message>", "rejected": ["--invalid-token"] }Returned for malformed JSON, missing / empty tokens, invalid token names, or prefixes not in the routing map. rejected is optional.
Response 403 (Forbidden)
{ "ok": false, "error": "Origin not allowed" }The bin rejects cross-origin requests.
Response 409 (Conflict)
{ "ok": false, "error": "No top-level :root { ... } or @theme { ... } block in <file>" }The target CSS file has neither a top-level :root block nor a top-level @theme block — see Which CSS blocks get rewritten above. A file with only one of the two (e.g. @theme-only, no :root at all) does NOT 409; it applies normally against whichever block exists.
Response 500 (Internal server error)
{
"ok": false,
"error": "<message>",
"failedFile": "<relativePath>",
"restoreFailures": ["<file1>"]
}Returned when a file write fails. restoreFailures is populated when the rollback also fails.
Atomic write contract
The bin keeps each file's original content in memory. If any write fails, every file written so far is restored from the in-memory original. Three terminal states are possible:
Full success. All routed files updated. Response 200.
Clean rollback. A write fails partway through; all previously-written files are restored. Response 500 with
failedFile.Inconsistent disk state. A write fails AND the rollback also fails. Response 500 with
failedFileANDrestoreFailures[].
Inspect on `restoreFailures`
A non-empty restoreFailures[] array means at least one file was rewritten and could not be restored. Inspect the listed files manually before retrying.
Validation rules
Token name rules
Must start with
--.No spaces, slashes, or special characters.
Must match a prefix family in
applyRouting.
Path safety
Each routing target is resolved to an absolute path.
The resolved path must sit within the bin's
writeRoot.Path-escape attempts (
.) are rejected.. / . . / etc/ passwd
Cross-references
PanelConfig.applyEndpointandapplyRouting— endpoint and routing slots.Token tiers — defines the
TabConfig/TierConfig/TierItemshapes whosecssVarnames appear in the diff.Color cluster — defines the Color-tab CSS-var names that can appear in the diff.