Closes the provider→recipe direction of the CSS-var drift stumble the
architecture-faithful way: the morfo is the source of truth. A component now
declares the functional custom properties its soma provider writes and its eidos
recipe reads:
cssVars: [{ name: 'progress', … }, { name: 'angle', … }] // → --knob-progress, --knob-angle
- types: `MorfoCssVar` + optional `cssVars` on `Morfo` (additive; the 133
existing morfos are unaffected).
- schema: validates `cssVars` (array of `{ name, description? }`).
- compile: exposes `contracts.cssVars` as full names `--{kebab}-{name}`.
- knob morfo declares its two contract vars.
- guard (recipe-css-contract.test.ts): enumerates morfos via `import.meta.glob`
and verifies BOTH sides honour each declaration — the provider writes
`--{kebab}-{name}` AND the recipe reads it — so renaming one side without the
other fails loudly. Proven non-vacuous (a bogus cssVar is flagged on both
sides).
Why A1 and not a grep cross-check (A2): in the CSS a provider-written functional
var and a consumer-override alias are indistinguishable, and providers publish
hook vars the recipe doesn't consume — a grep gives ~17 false positives (proven
earlier). The morfo declaration is what disambiguates.
npm run check 59 (baseline, 0 in touched files); eidos recipe-css-contract 24/24;
morfo suite green (only the pre-existing dialog-role test fails); docs:check 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Empirically confirmed the grep approach can't distinguish a provider-written
functional var from a consumer-override alias (identical in CSS), and providers
publish API-hook vars the recipe doesn't consume (17 such, none read by var()).
Only A1 (morfo cssVars declaration) can close the provider→recipe direction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the recipe→theme direction of the soma→eidos CSS-var drift stumble. New
check in recipe-css-contract.test.ts: every NO-FALLBACK `var(--x)` in a
component recipe must resolve to a token declared somewhere in the eidos CSS
tree (foundation + shared layers + the component's own self-declarations). A
`var(--x, default)` stays exempt — those are runtime-optional by construction
(provider/floating vars whose fallback IS the default), matching the existing
`collectOwnPublicVariables` rule.
This is the exact class that silently broke the Knob (`--color-neutral-content`,
`--state-hover`, `--color-neutral-bg` resolved to nothing, only visible in the
render). The guard immediately caught 3 MORE real phantoms — fixed, not silenced
(silencing would be the anti-pattern of a guard exempting the gap it exists to
catch):
- card-group: `--color-content-default` → `--color-content-primary`
- link-preview: `--leading-body` → `--leading-normal`
- textarea: `--font-family-body` → `--font-family-primary`
Full eidos suite 290/290. The provider→recipe direction (renaming a
provider-written `--knob-angle` breaking the recipe) remains open as option A
(morfo `cssVars` declaration or a grep cross-check) — noted in STUMBLES.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a '### Authoring notes' subsection to soma.md §6 grounded in the real
defs — state<T>() vs $state, role optionality on a Provider, Without<>/
PrimitiveDivAttributes, and gesture pointermove/up ownership. Closes STUMBLES
#9; only #7 (soma->eidos CSS-var contract, design-open) remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Knob now built across all 5 layers, validating the exercise. #7 confirmed
first-hand: the Knob build hit phantom theme tokens + uncontracted provider
CSS vars (silent drift), exactly as predicted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>