CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 59s
CI & Build / Build & push image (push) Successful in 40s
The design surface is for the projects an install tracks. /design read the running app's own stylesheet — names out of a bundled theme.css, values out of getComputedStyle(document.documentElement) — so it could only ever describe the instance serving the page. Scribe is one project among the projects Scribe tracks; it gets no view hardcoded into every install. The mechanism that makes this a tool rather than a mirror already existed and already covers Scribe: scripts/check_design_tokens.py runs in CI against a sheet path it knows nothing about, using check_code_against_tokens — the same engine behind check_snippets_against_system. /design was redundant even here. Removed: DesignView, DesignTabs (nothing left to tab between), api/design.ts, routes/design.py and its blueprint, the /design route, ui_design_system() and its setting, and the Settings picker that designated "this app's UI". utils/designTokens.ts and utils/designDrift.ts go with it — between them they were the browser-reading half. What survives is utils/designValues.ts, which works on a record rather than a document: valueForMode, modesPresent, and resolveDeclared. resolveDeclared gained real isolation in the move. Custom properties inherit and `all: initial` does not reset them, so a probe sitting in this page would resolve any reference a record leaves undeclared against the SURROUNDING app's tokens — previewing another project's system would quietly borrow this one's palette wherever that system was incomplete, and a token already reported under unknown_refs would render as though it were fine. Undeclared references are now blanked on the probe first, so they resolve to nothing, which is what the record says they are. Migration 0075 absorbs ui_design_system_id alongside design_rulebook_id rather than an 0076 undoing it: 0075 has not run anywhere, since dev is unmerged and deploys come from main. Both keys named a design source for the running install, and a project already carries its own pointer. This retires the agreement panel shipped yesterday. It asked whether the sheet was actually loaded and applied — the one question a record cannot answer about itself — but only ever about the app you are already inside. Nothing replaces it; recorded in #2430 rather than quietly dropped. Step 1 of milestone #274. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
/**
|
|
* Turning a design system's RECORDED values into ones you can look at.
|
|
*
|
|
* Replaces `designTokens.ts` and `designDrift.ts`, which between them read the
|
|
* running app's own stylesheet — names out of a bundled `theme.css`, values out
|
|
* of `getComputedStyle(document.documentElement)`. That could only ever describe
|
|
* the install serving the page, and the design surface is for the projects an
|
|
* install TRACKS (#274). What is left here works on any system's record,
|
|
* including one for an app this browser has never loaded.
|
|
*
|
|
* Nothing in this module reads the document's own tokens or mutates the page.
|
|
*/
|
|
|
|
/** The base mode's key in `value_by_mode`, mirroring services/design_stylesheet. */
|
|
export const BASE_MODE = "base";
|
|
|
|
/**
|
|
* Which declared value applies in `mode`.
|
|
*
|
|
* Falls back to base, which is the storage model rather than a convenience: a
|
|
* mode block is an OVERRIDE layer, so a token with no entry for the current
|
|
* mode is not missing — it is inheriting, exactly as the generated sheet has it.
|
|
*/
|
|
export function valueForMode(
|
|
valueByMode: Record<string, string>,
|
|
mode: string,
|
|
): string {
|
|
const own = valueByMode[mode];
|
|
if (own !== undefined && own !== "") return own;
|
|
return valueByMode[BASE_MODE] ?? "";
|
|
}
|
|
|
|
/** Every mode any token in the set declares, base first then the rest by name. */
|
|
export function modesPresent(
|
|
tokens: { value_by_mode: Record<string, string> }[],
|
|
): string[] {
|
|
const modes = new Set<string>();
|
|
for (const token of tokens) {
|
|
for (const [mode, value] of Object.entries(token.value_by_mode)) {
|
|
if (value) modes.add(mode);
|
|
}
|
|
}
|
|
const rest = [...modes].filter((m) => m !== BASE_MODE).sort();
|
|
return modes.has(BASE_MODE) ? [BASE_MODE, ...rest] : rest;
|
|
}
|
|
|
|
/**
|
|
* Resolve declared values the way a browser would, without applying them.
|
|
*
|
|
* A record holds `color-mix(in srgb, var(--fs-accent) 15%, transparent)`. Shown
|
|
* as text that is a string; shown as a swatch it needs `var()` substituted and
|
|
* the mix evaluated. Rather than write a CSS parser, set the declarations on an
|
|
* offscreen probe and read them back — the substitution is done by the
|
|
* implementation that would do it for real.
|
|
*
|
|
* Custom properties INHERIT, and `all: initial` does not reset them — so a probe
|
|
* sitting in this page would resolve any reference the record leaves undeclared
|
|
* against the surrounding app's own tokens. Previewing another project's system
|
|
* would then quietly borrow this one's palette wherever that system was
|
|
* incomplete, and a token the record already knows is broken (it shows up under
|
|
* `unknown_refs`) would render as though it were fine.
|
|
*
|
|
* So every name referenced but not declared is blanked on the probe first. It
|
|
* resolves to nothing, which is what the record says it is.
|
|
*/
|
|
const VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/g;
|
|
|
|
export function resolveDeclared(declared: Map<string, string>): Map<string, string> {
|
|
const probe = document.createElement("div");
|
|
probe.style.display = "none";
|
|
for (const value of declared.values()) {
|
|
for (const match of value.matchAll(VAR_REFERENCE)) {
|
|
if (!declared.has(match[1])) probe.style.setProperty(match[1], " ");
|
|
}
|
|
}
|
|
for (const [name, value] of declared) probe.style.setProperty(name, value);
|
|
document.body.appendChild(probe);
|
|
try {
|
|
const computed = getComputedStyle(probe);
|
|
const out = new Map<string, string>();
|
|
for (const name of declared.keys()) {
|
|
out.set(name, computed.getPropertyValue(name).trim());
|
|
}
|
|
return out;
|
|
} finally {
|
|
probe.remove();
|
|
}
|
|
}
|