feat(design-systems): formulas — derived tokens that follow their source
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 22s
CI & Build / integration (push) Successful in 30s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 42s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 22s
CI & Build / integration (push) Successful in 30s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 42s
Operator: "build in a way to support formulas like this so that the colors shift
as expected and have less to clean up when testing color changes."
The storage needed no change at all, which is the good news. A formula is just a
value:
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent)
It passes the value sanitiser untouched (verified, and now pinned by a test —
had `color-mix(... var(...) ...)` been rejected as unsafe, derivation would have
needed a storage shape of its own), and the browser resolves the `var()` at use
time. Change `--fs-accent` and everything derived from it shifts.
**One declaration covers every mode**, and that is the "less to clean up" part.
A derived token written once in the base layer follows its source through dark
mode automatically, because `var()` resolves where it is USED rather than where
it is written. A stored computed literal would need a row per mode and would
silently stop tracking the source the moment the source changed — the whole
problem this avoids.
What derivation DID need is the check. A formula pointing at a token that does
not exist is invalid-at-computed-value-time: the browser drops the declaration
outright and the token has no value. No error, no warning, nothing in the
toolchain notices — the same family as `--color-accent`, `_parent_map`, and the
scripted edit whose anchor matched nothing.
So `derivation_report` returns three things alongside the sheet: which tokens are
computed and from what, which formulas point at nothing, and which derive from
each other in a loop. CSS resolves a loop to nothing rather than hanging, so the
cycle check is about telling the operator, not protecting the renderer — but a
token that quietly resolves to nothing is exactly what is worth being told.
A self-reference with a fallback (`var(--fs-x, 8px)`) is deliberately not a
dependency; counting it would report every such token as a one-node loop.
The UI leads with broken formulas, then loops, then the healthy derived set —
the first two are unambiguously wrong, where a duplicate value is a judgement
call.
This commit is contained in:
@@ -145,6 +145,14 @@ export interface StylesheetResult {
|
||||
valueless: string[];
|
||||
/** Values declared under more than one name — alias, or one idea twice. */
|
||||
duplicates: Record<string, string[]>;
|
||||
derivation: {
|
||||
/** Tokens computed from others, mapped to what they're computed from. */
|
||||
derived: Record<string, string[]>;
|
||||
/** Formulas pointing at tokens that don't exist — the browser drops these. */
|
||||
unknown_refs: Record<string, string[]>;
|
||||
/** Derivation loops, which resolve to nothing for the same reason. */
|
||||
cycles: string[][];
|
||||
};
|
||||
}
|
||||
|
||||
/** The master CSS sheet a design system generates.
|
||||
|
||||
@@ -442,6 +442,16 @@ watch([selectedId, ownTokens], () => {
|
||||
|
||||
const duplicateEntries = computed(() => Object.entries(sheet.value?.duplicates ?? {}));
|
||||
|
||||
/** Formulas whose source token doesn't exist. The browser drops the whole
|
||||
* declaration — invalid at computed-value time — so nothing errors and the
|
||||
* token simply has no value. Led with, because it is the only entry here that
|
||||
* is unambiguously broken rather than a judgement call. */
|
||||
const brokenFormulas = computed(() =>
|
||||
Object.entries(sheet.value?.derivation.unknown_refs ?? {}),
|
||||
);
|
||||
const derivedEntries = computed(() => Object.entries(sheet.value?.derivation.derived ?? {}));
|
||||
const derivationCycles = computed(() => sheet.value?.derivation.cycles ?? []);
|
||||
|
||||
// --- do the snippets use the sheet? -----------------------------------------
|
||||
|
||||
const snippetCheck = ref<SnippetCheck | null>(null);
|
||||
@@ -691,6 +701,46 @@ function isColourish(value: string): boolean {
|
||||
under more than one name
|
||||
</p>
|
||||
|
||||
<div v-if="brokenFormulas.length" class="notice notice-warn">
|
||||
<strong>Some formulas point at tokens that don't exist.</strong>
|
||||
<p>
|
||||
The browser drops these declarations entirely — no error, no
|
||||
warning, the token just has no value. Either the source token
|
||||
was renamed or the reference is a typo.
|
||||
</p>
|
||||
<ul class="dupe-list">
|
||||
<li v-for="[name, refs] in brokenFormulas" :key="name">
|
||||
<code>{{ name }}</code> → <code>{{ refs.join(", ") }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="derivationCycles.length" class="notice notice-warn">
|
||||
<strong>Some tokens derive from each other in a loop.</strong>
|
||||
<p>
|
||||
CSS resolves a loop to nothing rather than looping forever, so
|
||||
every token in the cycle ends up with no value.
|
||||
</p>
|
||||
<ul class="dupe-list">
|
||||
<li v-for="(cycle, i) in derivationCycles" :key="i">
|
||||
<code>{{ cycle.join(" → ") }} → {{ cycle[0] }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="derivedEntries.length" class="notice">
|
||||
<strong>{{ derivedEntries.length }} tokens are computed from others.</strong>
|
||||
<p>
|
||||
These follow their source automatically, in every mode, from a
|
||||
single declaration — change the source and they shift with it.
|
||||
</p>
|
||||
<ul class="dupe-list">
|
||||
<li v-for="[name, refs] in derivedEntries" :key="name">
|
||||
<code>{{ name }}</code> from <code>{{ refs.join(", ") }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="duplicateEntries.length" class="notice notice-warn">
|
||||
<strong>Some values are declared twice.</strong>
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user