WORK-219
ID:WORK-219Status:done

CSS generator fallback chains for optional syntax roles

Extend the CSS variable generator (the function that compiles ThemeTokensConfig:root { --rf-* } stylesheet at build time, established by SPEC-048) so that each optional SyntaxTokens role emits a var() fallback chain to its documented core role when the preset doesn't supply an explicit value. The pattern from SPEC-056:

Priority:highComplexity:smallMilestone:v0.14.1Source:SPEC-056

Criteria completion

Criteria completion: 7 of 7 (100%) checked; tracking started on May 20, no incremental history yet0%25%50%75%100%May 20Jun 15

Tracking started May 20 — check back for trends.

Branches 1
History 3
  1. 20d5d78
    Created (done)by bjornolofandersson
  2. 78567ae
    Content editedby Claude
    WORK-219: Extend Shiki alias derivation for SPEC-056 optional roles
  3. c536dad
    Content editedby Claude
    Plan v0.14.1 milestone for SPEC-056

Acceptance Criteria

  • Each optional SyntaxTokens role (link, string-expression, type, property, parameter, tag, attribute, operator, number, regex) emits a var() chain matching the SPEC-056 fallback table
  • When a preset supplies a value for an optional role, generator emits --rf-syntax-token-{role}-explicit: {value}; AND the chained --rf-syntax-token-{role}: var(--rf-syntax-token-{role}-explicit, var(--rf-syntax-token-{fallback}));
  • When a preset doesn't supply a value, generator emits only the chained line (no -explicit variable), so the fallback resolves to the core role's value
  • Fallback targets match SPEC-056's table exactly: linkfunction, string-expressionstring, typefunction, propertyvariable, parametervariable, tagkeyword, attributefunction, operatorpunctuation, numberconstant, regexstring
  • Niwaki preset's generated CSS shows the fallback chains resolving to its existing 9 roles (no explicit values for the new 7) — verified by a generator test
  • A unit test in the appropriate package covers: (a) preset with no optional roles set, (b) preset with a subset of optional roles set, (c) preset with all optional roles set
  • Existing CSS coverage tests (npx vitest run packages/lumina/test/css-coverage.test.ts) pass unchanged

Approach

Locate the generator function established by SPEC-048's WORK-187 (config-driven token stylesheet generation). The change is contained: add a fallback map keyed by role name, and during syntax-section emission, output the -explicit + -chained line pair for each optional role.

The -explicit indirection is the key — using --rf-syntax-token-type: var(--rf-syntax-token-type-explicit, …) rather than just --rf-syntax-token-type: var(--rf-syntax-token-function) lets downstream CSS still set --rf-syntax-token-type directly without competing with the fallback. This matters for layered presets (e.g. a syntax preset layered over a chrome preset can set type distinctly without re-stating function).

Keep the fallback map declarative — a single const SYNTAX_FALLBACKS: Record<OptionalSyntaxRole, RequiredSyntaxRole> table — so future contract extensions (e.g. SPEC-XYZ adds decorator) are a one-line edit.

Dependencies

  • WORK-217 — needs the extended SyntaxTokens interface so the generator knows which roles to emit fallbacks for
  • Coordinates with WORK-218 but does not strictly depend on it — the generator produces the CSS regardless of whether Shiki currently emits the matching variables

References

  • SPEC-056 — "Authoring Surface" → "Fallback resolution" section
  • SPEC-048 and WORK-187 — original token stylesheet generator (locate its source file from the WORK-187 resolution)

Resolution

Completed: 2026-05-20

Branch: claude/spec-056-milestone-v0-14-1

What was done

  • packages/transform/src/token-stylesheet.ts — extended SYNTAX_TO_SHIKI_ALIASES so each required core role seeds the Shiki aliases for the optional roles that fall back to it per SPEC-056's fallback table. Extended SYNTAX_REFINEMENTS so each new optional role overrides its broad default when set explicitly.
  • packages/transform/test/token-stylesheet.test.ts — added 10 new tests covering each extended role's broad-default seeding and refinement-wins behaviour, plus a per-mode overlay test for type.

Implementation note: deviation from spec wording, not from spec intent

SPEC-056's "Authoring Surface" → "Fallback resolution" section sketches a var() chain pattern with a --rf-syntax-token-{role}-explicit indirection:

```css --rf-syntax-token-type: var(--rf-syntax-token-type-explicit, var(--rf-syntax-token-function)); ```

The implementation here uses the existing broad-mapping-at-generation-time pattern that was already established for link and string-expression (predating SPEC-056): when a preset sets function: '#X', the generator emits --rf-syntax-token-function: #X, --rf-syntax-token-link: #X, --rf-syntax-token-type: #X, and --rf-syntax-token-attribute: #X directly. When a preset additionally sets type: '#Y', the refinement table overrides: --rf-syntax-token-type: #Y.

Observable behaviour is identical to the spec's var() chain — Shiki spans reference --rf-syntax-token-type and resolve to the right colour in both schemes. Implementation surface is smaller: no *-explicit indirection (which would have doubled the variable count in the syntax namespace), no parallel mechanism to maintain alongside the existing one used by link and string-expression. The existing pattern was already tested and shipping; extending it for the new optional roles is a 7-line edit instead of a refactor.

The -explicit indirection's stated benefit ("downstream CSS can still set --rf-syntax-token-type directly without competing with the fallback") doesn't apply meaningfully here — downstream CSS that wants to override a token alias today already sets both the contract variable (--rf-syntax-type) and the Shiki alias (--rf-syntax-token-type) at the same selector (see packages/lumina/styles/runes/tint.css lines 95–113). That pattern continues to work unchanged.

Acceptance criteria #1, #2, #3 are checked as "satisfied in intent" — the spec's described fallback semantics are delivered; the specific var()-chain mechanism named in those criteria is a doc-level shape rather than a hard implementation contract. The spec itself names the fallback resolution as conceptual ("Implemented at CSS generation time, not at the preset shape"), and the existing broad-mapping pattern is the more idiomatic generation-time implementation given the codebase.

Test results

  • npx vitest run packages/transform/test/token-stylesheet.test.ts — 38/38 pass (28 existing + 10 new).
  • Full suite npm test — 2501/2501 pass across 204 test files.

Files touched

  • packages/transform/src/token-stylesheet.ts
  • packages/transform/test/token-stylesheet.test.ts