feat(design-systems): the master sheet — purpose tokens, not per-element values
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 34s

Operator's new requirement (#2299, architecture in #2296): a design system does
not just hold tokens, it generates and manages a master CSS sheet. That settles
the milestone's open "authority mechanism" question — the record is
authoritative because the stylesheet comes out of it.

**The sheet is shaped by purpose and styles no elements.** It declares custom
properties, grouped by what they mean, and contains no `.btn-primary`, no
`table`, no `input`. That is the design, not a shortcut: a sheet that styled
elements would restate the same handful of values once per element and grow with
the UI, where purpose-named values are stated once and reused. Components live
as SNIPPETS that reference these names — a surface that already exists and
already carries prose, locations, drift checks, merge and write-path recall.

A token named after an element (`--fs-button-bg`) is the smell that the two have
been mixed; a purpose name (`--fs-action-primary`) is reused across all of them.

Alongside the CSS the endpoint returns what the text cannot say for itself:
which tokens are still valueless, and which VALUES are declared under more than
one name. The second is the operator's "reuse consistent values" constraint made
checkable — and it reports rather than refuses, because a design system
legitimately aligns colours on purpose ("Success = Moss, by design") and only a
human knows which case it is.

Mode maps to selector the way the codebase already does it: base on the root
selector, every other mode layered on `[data-theme="…"]`. The root selector is a
PARAMETER — #251 recorded that a container-scoped preview cannot use `:root`, so
hardcoding it would have made the generator useless to the preview surface.

A token the rulebook names but states no value for is emitted as a commented-out
declaration IN ITS GROUP rather than dropped. Its absence is the finding, and a
comment puts that finding where the reader already is.

Values are validated, not escaped, and this is a real boundary rather than
tidiness: design systems are shareable records (rule #47), so `red; } body {
display: none` in a system shared with you would otherwise inject CSS into your
page. A value containing `{ } ; @ < >`, a comment delimiter or a newline is
REFUSED and rendered as a comment saying so — rejecting beats stripping, since a
partially-sanitised value is one the operator never wrote and the sheet's whole
claim is that it is the record.

Not in scope, and deliberately: serving this as the app's actual stylesheet.
Generating and exposing a sheet is reversible; swapping theme.css for a
generated one is not, and it should be an explicit call rather than a side
effect.
This commit is contained in:
2026-07-30 21:42:08 -04:00
parent 4dc57f8ab2
commit b0a7d9e89b
9 changed files with 737 additions and 1 deletions
+29
View File
@@ -139,6 +139,34 @@ async def delete_design_system(design_system_id: int) -> dict:
return {"message": f"Design system {design_system_id} deleted."}
async def get_design_system_stylesheet(
design_system_id: int,
root_selector: str = ":root",
) -> dict:
"""The master CSS sheet a design system generates.
Purpose tokens only — this sheet declares what values MEAN and styles no
elements. Components (buttons, tables, input schemes) are SNIPPETS that
reference these names, so a value is stated once and reused rather than
restated per element. Reach for this when you need the tokens a snippet is
allowed to use.
Also returns `valueless` (tokens the system names but has no value for) and
`duplicates` (values declared under more than one name — a deliberate alias,
or one idea recorded twice).
Args:
design_system_id: The system to render.
root_selector: Selector for the base layer. Defaults to `:root`; pass a
container selector to scope the sheet to a preview region.
"""
uid = current_user_id()
result = await ds_svc.stylesheet_for_system(uid, design_system_id, root_selector)
if result is None:
raise ValueError(f"design system {design_system_id} not found")
return result
async def import_design_system_from_rulebook(
design_system_id: int,
rulebook_id: int,
@@ -307,6 +335,7 @@ def register(mcp) -> None:
resolve_design_system,
update_design_system,
delete_design_system,
get_design_system_stylesheet,
import_design_system_from_rulebook,
create_design_token,
list_design_tokens,
+21
View File
@@ -114,6 +114,27 @@ async def resolve_design_system(design_system_id: int):
})
@design_systems_bp.get("/design-systems/<int:design_system_id>/stylesheet")
@login_required
async def get_design_system_stylesheet(design_system_id: int):
"""The master CSS sheet this design system generates.
JSON by default (the UI wants the reuse report alongside the CSS); add
`?format=css` for the raw stylesheet as `text/css`, which is what a build
step or a `curl` wants.
`?root=` overrides the base selector — a container-scoped preview cannot use
`:root`, so the generator takes it as a parameter.
"""
root = (request.args.get("root") or ":root").strip() or ":root"
result = await ds_svc.stylesheet_for_system(_uid(), design_system_id, root)
if result is None:
return _not_found()
if request.args.get("format") == "css":
return result["css"], 200, {"Content-Type": "text/css; charset=utf-8"}
return jsonify(result)
@design_systems_bp.post("/design-systems/<int:design_system_id>/import")
@login_required
async def import_design_system(design_system_id: int):
+213
View File
@@ -0,0 +1,213 @@
"""Render a design system's resolved tokens as its master CSS sheet.
Pure, like `design_cascade` and for the same reasons: no database import, so the
rendering rule is testable without one and the preview surface can reuse it.
WHAT THIS SHEET IS, AND DELIBERATELY IS NOT
-------------------------------------------
It declares **purpose tokens only** — custom properties, grouped by what they
mean. It contains no rules for elements or classes: no `.btn-primary`, no
`table`, no `input`.
That is the point rather than a limitation. A sheet that styled every element
would restate the same handful of values once per element and grow with the UI;
a sheet of purpose-named values states each once and is reused. Components —
buttons, tables, input schemes — live as SNIPPETS that reference these names and
carry prose about the idea, which is a surface that already exists and already
has recall, locations, drift checks and merge.
So the division is: this sheet says what the values MEAN; snippets say what
things LOOK LIKE, in terms of those values. A token named after an element
(`--fs-button-bg`) is the smell that the two have been mixed — it multiplies
with every new element, where a purpose name (`--fs-action-primary`) is reused.
SAFETY
------
Values are interpolated into a stylesheet, and design systems are shareable
records (rule #47). A value of `red; } body { display: none` in a system someone
shared with you would otherwise inject CSS into your page. Everything rendered
here is validated or dropped — never escaped-and-hoped.
"""
from __future__ import annotations
import re
from collections.abc import Sequence
BASE_MODE = "base"
# A custom property name, strictly. Anything else is dropped rather than
# sanitised: a name is an identifier, and a "cleaned up" identifier is a
# different token than the one the operator recorded.
_VALID_NAME = re.compile(r"^--[A-Za-z0-9_-]+$")
# Characters that would end the declaration, open a block, start an at-rule, or
# begin a tag. A value containing any of them is not a value.
_UNSAFE_VALUE = re.compile(r"[{};@<>]|\*/|/\*|\n|\r")
def is_valid_token_name(name: str) -> bool:
return bool(_VALID_NAME.match((name or "").strip()))
def safe_value(value: str) -> str | None:
"""A CSS value that cannot escape its declaration, or None.
Rejects rather than strips. A partially-sanitised value is a value the
operator did not write, and silently rendering a different colour than the
record holds is worse than rendering none — the sheet's whole claim is that
it IS the record.
"""
candidate = (value or "").strip()
if not candidate or _UNSAFE_VALUE.search(candidate):
return None
return candidate
def safe_comment(text: str) -> str:
"""Comment text that cannot close its comment or break the line."""
return re.sub(r"\*/|/\*|[\n\r]", " ", (text or "")).strip()
def selector_for_mode(mode: str, root_selector: str = ":root") -> str:
"""Which selector a mode's declarations belong under.
`base` gets the caller's root selector; every other mode gets a bare
attribute selector, matching the convention already in the codebase.
The root selector is a PARAMETER because a container-scoped preview cannot
use `:root` — #251 recorded that mode scoping is one-way (light lives on
`:root`, dark layers over it), so a generator that hardcoded `:root` could
not serve a preview at all.
"""
if mode == BASE_MODE:
return root_selector
safe_mode = re.sub(r"[^A-Za-z0-9_-]", "", mode)
return f'[data-theme="{safe_mode}"]' if safe_mode else root_selector
def _grouped(tokens: Sequence) -> list[tuple[str | None, list]]:
"""Tokens by group, preserving the order they arrive in (already sorted)."""
groups: dict[str | None, list] = {}
for token in tokens:
groups.setdefault(getattr(token, "group_name", None), []).append(token)
return list(groups.items())
def _modes_present(tokens: Sequence) -> list[str]:
"""Every mode any token declares, base first then the rest alphabetically."""
modes = {
mode
for token in tokens
for mode in (getattr(token, "value_by_mode", None) or {})
}
rest = sorted(modes - {BASE_MODE})
return ([BASE_MODE] if BASE_MODE in modes else []) + rest
def render_stylesheet(
tokens: Sequence,
*,
root_selector: str = ":root",
title: str = "",
design_system_id: int | None = None,
) -> str:
"""The master sheet for a resolved token set.
One block per mode. Within a block, only the tokens that declare a value for
that mode — so a mode block is an override layer, exactly as the storage
model has it.
Tokens with no value at all are emitted as COMMENTED-OUT declarations in
their group, not dropped. The rulebook named them, so their absence is a
finding, and a commented line puts that finding where the reader is already
looking.
"""
header = [
"/*",
f" * {safe_comment(title) or 'Design system'} — generated stylesheet",
]
if design_system_id is not None:
header.append(f" * Source: design system {int(design_system_id)}.")
header += [
" *",
" * Purpose tokens only. This sheet declares what values MEAN; it styles",
" * no elements. Buttons, tables and input schemes live as snippets that",
" * reference these names, so each value is stated once and reused rather",
" * than restated per element.",
" *",
" * Generated — edit the design system, not this file.",
" */",
"",
]
lines = list(header)
valueless = [
t for t in tokens
if is_valid_token_name(getattr(t, "name", ""))
and not (getattr(t, "value_by_mode", None) or {})
]
for mode in _modes_present(tokens):
block: list[str] = []
for group, group_tokens in _grouped(tokens):
entries: list[str] = []
for token in group_tokens:
name = (getattr(token, "name", "") or "").strip()
if not is_valid_token_name(name):
continue
raw = (getattr(token, "value_by_mode", None) or {}).get(mode)
if raw is None:
continue
value = safe_value(str(raw))
if value is None:
entries.append(
f" /* {name}: value rejected — not a safe CSS value */"
)
continue
purpose = safe_comment(getattr(token, "purpose", "") or "")
comment = f" /* {purpose} */" if purpose and mode == BASE_MODE else ""
entries.append(f" {name}: {value};{comment}")
if entries:
if group:
block.append(f" /* {safe_comment(group)} */")
block.extend(entries)
block.append("")
# Declared-but-valueless tokens belong to the base layer: they have no
# mode to sit under, and repeating them per mode would triple the noise.
if mode == BASE_MODE and valueless:
block.append(" /* Declared by the design system, no value set yet: */")
block.extend(f" /* {t.name}: ; */" for t in valueless)
block.append("")
if not block:
continue
lines.append(f"{selector_for_mode(mode, root_selector)} {{")
lines.extend(block[:-1] if block[-1] == "" else block)
lines.append("}")
lines.append("")
return "\n".join(lines).rstrip() + "\n"
def duplicate_values(tokens: Sequence) -> dict[str, list[str]]:
"""Values declared by more than one token, mapped to the names declaring them.
Two names for one value are either a deliberate alias or the same idea
recorded twice — the token-level form of the duplicated-definition shape.
Reported rather than refused: a design system legitimately aligns colours on
purpose ("Success = Moss, by design"), and a generator that rejected that
would be wrong about the operator's intent.
Compares the BASE value only. Two tokens agreeing in one mode and diverging
in another are not duplicates of each other — they are a near-miss, which is
a different and less interesting finding.
"""
by_value: dict[str, list[str]] = {}
for token in tokens:
name = getattr(token, "name", "")
base = (getattr(token, "value_by_mode", None) or {}).get(BASE_MODE)
if not name or not base:
continue
by_value.setdefault(str(base).strip().lower(), []).append(name)
return {value: names for value, names in by_value.items() if len(names) > 1}
+35
View File
@@ -21,6 +21,7 @@ from scribe.models import async_session
from scribe.models.design_system import DesignSystem, DesignToken
from scribe.models.project import Project
from scribe.services import access
from scribe.services.design_stylesheet import duplicate_values, render_stylesheet
from scribe.services.design_cascade import (
ResolvedToken,
ancestry,
@@ -402,3 +403,37 @@ async def import_from_rulebook(
"created": created,
"skipped": skipped,
}
# --- the master sheet -------------------------------------------------------
async def stylesheet_for_system(
user_id: int, design_system_id: int, root_selector: str = ":root"
) -> dict | None:
"""The master CSS sheet a design system generates, plus its reuse report.
Purpose tokens only — the sheet styles no elements. See
`services/design_stylesheet.py` for why that split is the design rather than
a shortcut.
Returns None if the caller may not read the system. The `duplicates` half is
advisory: two tokens sharing a value are either a deliberate alias or one
idea recorded twice, and only the operator knows which.
"""
resolved = await resolve_design_system(user_id, design_system_id)
if resolved is None:
return None
system = await get_design_system(user_id, design_system_id)
return {
"design_system_id": design_system_id,
"css": render_stylesheet(
resolved,
root_selector=root_selector,
title=system.title if system else "",
design_system_id=design_system_id,
),
"token_count": len(resolved),
"valueless": [t.name for t in resolved if not t.value_by_mode],
"duplicates": duplicate_values(resolved),
}