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
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:
@@ -373,3 +373,49 @@ async def test_an_unreadable_or_empty_rulebook_reports_nothing_rather_than_faili
|
||||
from scribe.services.design_systems import import_from_rulebook
|
||||
report = await import_from_rulebook(1, 3, 9, apply=True)
|
||||
assert report == {"rulebook_id": 9, "proposed": [], "created": [], "skipped": []}
|
||||
|
||||
|
||||
# --- the master sheet -------------------------------------------------------
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stylesheet_denied_returns_none():
|
||||
with patch("scribe.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=None)):
|
||||
from scribe.services.design_systems import stylesheet_for_system
|
||||
assert await stylesheet_for_system(1, 3) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stylesheet_reports_what_the_sheet_cannot_say_for_itself():
|
||||
"""The CSS alone hides two things a reviewer needs: which tokens are still
|
||||
valueless, and which values are declared twice. Both ride alongside rather
|
||||
than being left for the reader to derive from the text."""
|
||||
from scribe.services.design_cascade import Contribution, ResolvedToken
|
||||
|
||||
def _resolved(name, base=None):
|
||||
return ResolvedToken(
|
||||
name=name,
|
||||
contributions=(
|
||||
{"base": (Contribution(system_id=1, value=base),)} if base else {}
|
||||
),
|
||||
group_name=None, purpose=None, supersedes=(), order_index=0,
|
||||
)
|
||||
|
||||
system = MagicMock()
|
||||
system.title = "FabledSword"
|
||||
with patch("scribe.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=[
|
||||
_resolved("--fs-moss", "#4a5d3f"),
|
||||
_resolved("--fs-success", "#4a5d3f"),
|
||||
_resolved("--fs-radius-sm"),
|
||||
])), \
|
||||
patch("scribe.services.design_systems.get_design_system",
|
||||
AsyncMock(return_value=system)):
|
||||
from scribe.services.design_systems import stylesheet_for_system
|
||||
result = await stylesheet_for_system(1, 3)
|
||||
|
||||
assert result["token_count"] == 3
|
||||
assert result["valueless"] == ["--fs-radius-sm"]
|
||||
assert result["duplicates"] == {"#4a5d3f": ["--fs-moss", "--fs-success"]}
|
||||
assert "--fs-moss: #4a5d3f;" in result["css"]
|
||||
assert "FabledSword" in result["css"]
|
||||
|
||||
Reference in New Issue
Block a user