CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Failing after 36s
CI & Build / Build & push image (push) Skipped
Second pass over the tests/ ledger after bbee0d0. fake_record(**attrs) is the
one MagicMock-with-real-attributes builder (to_dict mirrors them; the
note-2109 hazard documented once); fake_note/fake_task/fake_snippet/
fake_project/fake_milestone/fake_system/fake_rulebook/fake_topic/fake_rule
carry each model's ordinary defaults on top of it, replacing 14 per-file
factories (two rulebook trios in tool-vs-service wordings, _fake_task, _fake_ms,
_fake_project, _plan_note, _fake_snippet, two _snippet adapters now one-liners
over fake_snippet). FakeMCP replaces the five closure-over-a-list registrar
fakes (+ _Recorder); loc() and design_token_stub() replace the paired _loc /
_token / _T stand-ins; every hand-built async_session mock (9 helper defs and
14 inline copies) now starts from make_mock_session(). Call sites rewritten by
AST with each file's former defaults made explicit, so behaviour is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
430 lines
18 KiB
Python
430 lines
18 KiB
Python
"""Rendering a design system as its master CSS sheet.
|
|
|
|
The generator is pure, so a whole sheet is one literal of tokens in and a string
|
|
out. Two things here are load-bearing rather than cosmetic: what the sheet
|
|
deliberately does NOT contain, and the fact that a value cannot escape its
|
|
declaration.
|
|
"""
|
|
from types import SimpleNamespace
|
|
|
|
from scribe.services.design_stylesheet import (
|
|
check_code_against_tokens,
|
|
derivation_report,
|
|
duplicate_values,
|
|
is_valid_token_name,
|
|
render_stylesheet,
|
|
safe_comment,
|
|
safe_value,
|
|
selector_for_mode,
|
|
)
|
|
from tests.helpers import design_token_stub
|
|
|
|
|
|
# --- safety -----------------------------------------------------------------
|
|
#
|
|
# Design systems are shareable records. A value that can close its declaration
|
|
# can inject arbitrary CSS into the page of anyone the system was shared with,
|
|
# which makes this a real boundary rather than tidiness.
|
|
|
|
def test_a_value_that_would_escape_its_declaration_is_refused():
|
|
"""`red; } body { display: none` is the whole attack: end the declaration,
|
|
close the block, open your own."""
|
|
assert safe_value("red; } body { display: none") is None
|
|
assert safe_value("#fff}") is None
|
|
assert safe_value("#fff;") is None
|
|
|
|
|
|
def test_at_rules_and_tags_are_refused():
|
|
assert safe_value("@import url(evil.css)") is None
|
|
assert safe_value("</style><script>") is None
|
|
|
|
|
|
def test_comment_delimiters_in_a_value_are_refused():
|
|
"""A value is not rendered inside a comment, but `/*` would comment out
|
|
every declaration after it — silently blanking the rest of the sheet."""
|
|
assert safe_value("red /* ") is None
|
|
assert safe_value("*/ red") is None
|
|
|
|
|
|
def test_a_newline_in_a_value_is_refused():
|
|
assert safe_value("red\n color: blue") is None
|
|
|
|
|
|
def test_ordinary_values_survive_untouched():
|
|
for value in ("#14171a", "8px", "cubic-bezier(0.2, 0.6, 0.2, 1)",
|
|
"0 4px 12px rgba(0,0,0,0.35)", "color-mix(in srgb, red 15%, transparent)"):
|
|
assert safe_value(value) == value
|
|
|
|
|
|
def test_a_rejected_value_is_dropped_not_cleaned_up():
|
|
"""Rejecting beats stripping. A partially-sanitised value is one the operator
|
|
never wrote, and the sheet's entire claim is that it IS the record — quietly
|
|
rendering a different colour would break that claim invisibly."""
|
|
css = render_stylesheet([design_token_stub(name="--fs-x", value_by_mode={"base": "red; } body { color: blue"})])
|
|
assert "body" not in css
|
|
assert "value rejected" in css
|
|
|
|
|
|
def test_comment_text_cannot_close_its_comment():
|
|
"""`purpose` is operator prose rendered into a comment — `*/` in it would
|
|
end the comment and spill the rest into the stylesheet as code."""
|
|
assert "*/" not in safe_comment("ends the comment */ then color: red")
|
|
|
|
|
|
def test_a_malformed_token_name_is_dropped():
|
|
"""A name is an identifier. A 'cleaned up' identifier is a different token
|
|
than the one recorded, so it is dropped rather than repaired."""
|
|
assert is_valid_token_name("--fs-obsidian")
|
|
assert not is_valid_token_name("--fs obsidian")
|
|
assert not is_valid_token_name("color: red")
|
|
assert not is_valid_token_name("fs-obsidian") # no leading --
|
|
|
|
css = render_stylesheet([design_token_stub(name="--bad name", value_by_mode={"base": "red"})])
|
|
assert "bad name" not in css
|
|
|
|
|
|
def test_a_mode_name_cannot_break_out_of_its_selector():
|
|
assert selector_for_mode('dark"] body {') == '[data-theme="darkbody"]'
|
|
|
|
|
|
# --- what the sheet is ------------------------------------------------------
|
|
|
|
def test_the_sheet_declares_properties_and_styles_no_elements():
|
|
"""THE shape decision. A sheet that styled elements would restate the same
|
|
handful of values once per element and grow with the UI. Purpose tokens are
|
|
stated once and reused; components are snippets that reference them."""
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}, group_name="surface"),
|
|
design_token_stub(name="--fs-moss", value_by_mode={"base": "#4a5d3f"}, group_name="action"),
|
|
])
|
|
assert "--fs-obsidian: #14171a;" in css
|
|
# No element or class rules — the sheet has exactly one block here, and
|
|
# every declaration in it is a custom property.
|
|
assert css.count("{") == 1
|
|
declarations = [
|
|
line.strip() for line in css.splitlines()
|
|
if ":" in line and line.strip().endswith(";")
|
|
]
|
|
assert declarations and all(d.startswith("--") for d in declarations)
|
|
|
|
|
|
def test_the_header_says_what_the_sheet_is_for():
|
|
"""A generated file with no explanation gets hand-edited, and then it has
|
|
diverged from the record it claims to be."""
|
|
css = render_stylesheet([design_token_stub(name="--fs-x", value_by_mode={"base": "1px"})], title="FabledSword")
|
|
assert "FabledSword" in css
|
|
assert "Generated" in css
|
|
assert "snippets" in css
|
|
|
|
|
|
# --- modes ------------------------------------------------------------------
|
|
|
|
def test_base_goes_on_the_root_selector_and_other_modes_layer_over_it():
|
|
"""Matches the convention already in the codebase, and the one-way scoping
|
|
#251 recorded: light on `:root`, dark layered on an attribute selector."""
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-bg", value_by_mode={"base": "#f5f1e8", "dark": "#14171a"}),
|
|
])
|
|
assert ":root {" in css
|
|
assert '[data-theme="dark"] {' in css
|
|
assert css.index(":root {") < css.index('[data-theme="dark"] {')
|
|
|
|
|
|
def test_a_mode_block_contains_only_what_that_mode_declares():
|
|
"""A mode block is an OVERRIDE layer, exactly as the storage model has it.
|
|
Repeating every token in every block would make the sheet claim each mode
|
|
redefines the whole system."""
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-bg", value_by_mode={"base": "#f5f1e8", "dark": "#14171a"}),
|
|
design_token_stub(name="--fs-radius-md", value_by_mode={"base": "8px"}),
|
|
])
|
|
dark_block = css.split('[data-theme="dark"] {')[1]
|
|
assert "--fs-bg" in dark_block
|
|
assert "--fs-radius-md" not in dark_block
|
|
|
|
|
|
def test_the_root_selector_is_caller_chosen():
|
|
"""A container-scoped preview cannot use `:root`. A generator that hardcoded
|
|
it could not serve the preview surface at all."""
|
|
css = render_stylesheet(
|
|
[design_token_stub(name="--fs-x", value_by_mode={"base": "1px"})], root_selector="[data-preview]"
|
|
)
|
|
assert "[data-preview] {" in css
|
|
assert ":root {" not in css
|
|
|
|
|
|
# --- grouping and honesty ---------------------------------------------------
|
|
|
|
def test_tokens_are_grouped_by_purpose_with_the_group_named():
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}, group_name="surface"),
|
|
design_token_stub(name="--fs-radius-md", value_by_mode={"base": "8px"}, group_name="radius"),
|
|
])
|
|
assert "/* surface */" in css
|
|
assert "/* radius */" in css
|
|
|
|
|
|
def test_a_purpose_becomes_an_inline_comment_on_the_base_layer_only():
|
|
"""Repeating the same prose in every mode block is noise: the token means
|
|
the same thing in dark mode."""
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a", "dark": "#000000"}, purpose="page bg, deepest surface"),
|
|
])
|
|
assert css.count("page bg, deepest surface") == 1
|
|
|
|
|
|
def test_a_declared_token_with_no_value_appears_as_a_comment_not_a_silence():
|
|
"""The rulebook named it, so its absence is a FINDING. A commented line puts
|
|
that finding where the reader is already looking; dropping it would make the
|
|
sheet look complete."""
|
|
css = render_stylesheet([
|
|
design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}),
|
|
design_token_stub(name="--fs-radius-sm", value_by_mode={}),
|
|
])
|
|
assert "--fs-radius-sm" in css
|
|
assert "no value set yet" in css
|
|
# Commented, so it cannot be mistaken for a live declaration.
|
|
assert " --fs-radius-sm:" not in css
|
|
|
|
|
|
def test_an_empty_system_renders_a_sheet_with_no_blocks_rather_than_failing():
|
|
"""A system with no tokens is an ordinary state (rule #115), including one
|
|
that was just created."""
|
|
css = render_stylesheet([])
|
|
assert "{" not in css
|
|
assert "Generated" in css
|
|
|
|
|
|
# --- the reuse report -------------------------------------------------------
|
|
|
|
def test_two_tokens_sharing_a_value_are_reported_not_refused():
|
|
"""The operator's constraint, made checkable: reuse consistent values rather
|
|
than restating them. But a design system legitimately aligns colours on
|
|
purpose — "Success = Moss, by design" — so this reports and lets a human
|
|
decide which it is."""
|
|
dupes = duplicate_values([
|
|
design_token_stub(name="--fs-moss", value_by_mode={"base": "#4A5D3F"}),
|
|
design_token_stub(name="--fs-success", value_by_mode={"base": "#4a5d3f"}),
|
|
design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}),
|
|
])
|
|
assert dupes == {"#4a5d3f": ["--fs-moss", "--fs-success"]}
|
|
|
|
|
|
def test_tokens_that_agree_in_one_mode_but_differ_in_another_are_not_duplicates():
|
|
"""A near-miss is a different, weaker finding, and calling it a duplicate
|
|
would send someone to merge two tokens that genuinely diverge."""
|
|
assert duplicate_values([
|
|
design_token_stub(name="--fs-a", value_by_mode={"base": "#fff", "dark": "#000"}),
|
|
design_token_stub(name="--fs-b", value_by_mode={"base": "#fff", "dark": "#111"}),
|
|
]) == {"#fff": ["--fs-a", "--fs-b"]}
|
|
|
|
|
|
def test_valueless_tokens_never_count_as_duplicates_of_each_other():
|
|
"""Otherwise every unfilled token would collide with every other one and the
|
|
report would be nothing but noise on a fresh import."""
|
|
assert duplicate_values([design_token_stub(name="--fs-a", value_by_mode={}), design_token_stub(name="--fs-b", value_by_mode={})]) == {}
|
|
|
|
|
|
# --- reading the sheet from the other side ----------------------------------
|
|
#
|
|
# "The snippets use the tags from the sheet" made checkable. All three findings
|
|
# below are currently SILENT in this codebase — no error, no failing test.
|
|
|
|
def _tok(name, base=None, supersedes=()):
|
|
return SimpleNamespace(
|
|
name=name,
|
|
value_by_mode={"base": base} if base else {},
|
|
supersedes=list(supersedes),
|
|
group_name=None, purpose=None,
|
|
)
|
|
|
|
|
|
SHEET = [
|
|
_tok("--fs-obsidian", "#14171a"),
|
|
_tok("--fs-parchment", "#e8e4d8", supersedes=["#fff", "#ffffff"]),
|
|
]
|
|
|
|
|
|
def test_a_reference_to_a_token_that_does_not_exist_is_reported():
|
|
"""THE bug this exists for. `var(--color-accent)` where no such token exists
|
|
renders as nothing at all — invisible focus rings, unstyled elements, no
|
|
error and no failing test. It happened in this codebase this session."""
|
|
report = check_code_against_tokens("outline: 2px solid var(--color-accent);", SHEET)
|
|
assert report["unknown"] == ["--color-accent"]
|
|
assert report["used"] == []
|
|
|
|
|
|
def test_a_reference_that_resolves_is_reported_as_used_not_as_a_finding():
|
|
report = check_code_against_tokens("background: var(--fs-obsidian);", SHEET)
|
|
assert report["used"] == ["--fs-obsidian"]
|
|
assert report["unknown"] == []
|
|
|
|
|
|
def test_a_superseded_literal_is_found_and_names_its_replacement():
|
|
"""The reframe paying off end to end: the finding says what to WRITE, not
|
|
merely what is wrong. That is only possible because the token declared it."""
|
|
report = check_code_against_tokens("color: #fff;", SHEET)
|
|
assert report["superseded_literals"] == [
|
|
{"literal": "#fff", "use_instead": "--fs-parchment"}
|
|
]
|
|
|
|
|
|
def test_a_shorter_hex_does_not_match_inside_a_longer_one():
|
|
"""`#fff` must not fire on `#ffffff` — different colours, and a finding on
|
|
the wrong one sends someone to change code that was already correct."""
|
|
report = check_code_against_tokens("color: #ffffffee;", SHEET)
|
|
assert [f["literal"] for f in report["superseded_literals"]] == []
|
|
|
|
|
|
def test_the_literal_match_is_case_insensitive():
|
|
"""A record writes `#FFFFFF` and code writes `#ffffff`. A case-sensitive
|
|
check would silently find nothing — the same trap `normalizeColour` in
|
|
utils/designDrift.ts exists for on the client side."""
|
|
report = check_code_against_tokens("color: #FFFFFF;", SHEET)
|
|
assert report["superseded_literals"] == [
|
|
{"literal": "#ffffff", "use_instead": "--fs-parchment"}
|
|
]
|
|
|
|
|
|
def test_a_token_defined_and_read_locally_is_reported_once_not_twice():
|
|
"""A snippet minting its own custom property is the bloat a shared sheet
|
|
exists to prevent — but when it also reads it back, that is ONE fact. The
|
|
unknown-reference check owns it, because "--btn-bg does not exist in the
|
|
sheet" is the more precise statement of the same problem."""
|
|
report = check_code_against_tokens(".btn { --btn-bg: #333; background: var(--btn-bg); }", SHEET)
|
|
assert report["unknown"] == ["--btn-bg"]
|
|
assert report["local_definitions"] == []
|
|
|
|
|
|
def test_a_local_definition_never_read_back_is_still_reported():
|
|
report = check_code_against_tokens(".btn { --btn-bg: #333; }", SHEET)
|
|
assert report["local_definitions"] == ["--btn-bg"]
|
|
|
|
|
|
def test_a_declaration_that_is_not_a_custom_property_is_not_mistaken_for_one():
|
|
report = check_code_against_tokens(".btn { color: red; background: blue; }", SHEET)
|
|
assert report["local_definitions"] == []
|
|
|
|
|
|
def test_clean_code_reports_nothing_to_act_on():
|
|
report = check_code_against_tokens(
|
|
".btn { background: var(--fs-obsidian); color: var(--fs-parchment); }", SHEET
|
|
)
|
|
assert report["unknown"] == []
|
|
assert report["superseded_literals"] == []
|
|
assert report["local_definitions"] == []
|
|
assert report["used"] == ["--fs-obsidian", "--fs-parchment"]
|
|
|
|
|
|
def test_the_inline_comment_falls_back_to_rationale_when_there_is_no_purpose():
|
|
"""A token carrying only the why still says something in the sheet, rather
|
|
than rendering bare because the other field happened to be empty."""
|
|
token = SimpleNamespace(
|
|
name="--fs-success", value_by_mode={"base": "#4a5d3f"},
|
|
group_name=None, purpose=None, rationale="equals Moss, by design",
|
|
)
|
|
assert "equals Moss, by design" in render_stylesheet([token])
|
|
|
|
|
|
def test_purpose_wins_over_rationale_in_the_comment():
|
|
"""What a token is FOR is what a reader of the stylesheet needs first."""
|
|
token = SimpleNamespace(
|
|
name="--fs-x", value_by_mode={"base": "1px"},
|
|
group_name=None, purpose="hairline borders", rationale="because thin",
|
|
)
|
|
css = render_stylesheet([token])
|
|
assert "hairline borders" in css
|
|
assert "because thin" not in css
|
|
|
|
|
|
# --- derivation -------------------------------------------------------------
|
|
#
|
|
# A formula needs no special storage: `color-mix(..., var(--fs-accent) 15%, ...)`
|
|
# is a value, and the browser resolves it live. What it needs is a check, because
|
|
# a formula pointing at a missing token is dropped silently.
|
|
|
|
def _dtok(name, base):
|
|
return SimpleNamespace(
|
|
name=name, value_by_mode={"base": base},
|
|
group_name=None, purpose=None, supersedes=[],
|
|
)
|
|
|
|
|
|
ACCENT = "color-mix(in srgb, var(--fs-accent) 15%, transparent)"
|
|
|
|
|
|
def test_a_formula_survives_the_value_sanitiser_untouched():
|
|
"""LOAD-BEARING for the whole approach. If `color-mix(... var(...) ...)` were
|
|
rejected as unsafe, derivation would need a storage shape of its own."""
|
|
assert safe_value(ACCENT) == ACCENT
|
|
assert ACCENT in render_stylesheet([_dtok("--fs-accent-soft", ACCENT)])
|
|
|
|
|
|
def test_a_derived_token_reports_what_it_is_computed_from():
|
|
report = derivation_report([
|
|
_dtok("--fs-accent", "#5b4a8a"),
|
|
_dtok("--fs-accent-soft", ACCENT),
|
|
])
|
|
assert report["derived"] == {"--fs-accent-soft": ["--fs-accent"]}
|
|
assert report["unknown_refs"] == {}
|
|
|
|
|
|
def test_a_formula_pointing_at_a_token_that_does_not_exist_is_reported():
|
|
"""The browser drops the whole declaration — invalid at computed-value time —
|
|
and nothing errors. Exactly the failure mode this system exists to end."""
|
|
report = derivation_report([_dtok("--fs-accent-soft", ACCENT)])
|
|
assert report["unknown_refs"] == {"--fs-accent-soft": ["--fs-accent"]}
|
|
|
|
|
|
def test_a_plain_value_is_not_reported_as_derived():
|
|
report = derivation_report([_dtok("--fs-obsidian", "#14171a")])
|
|
assert report["derived"] == {}
|
|
|
|
|
|
def test_a_derivation_loop_is_reported_once():
|
|
"""CSS resolves a loop to nothing rather than hanging, so this is about
|
|
telling the operator — but a token that quietly resolves to nothing is
|
|
precisely the thing worth being told."""
|
|
report = derivation_report([
|
|
_dtok("--fs-a", "var(--fs-b)"),
|
|
_dtok("--fs-b", "var(--fs-a)"),
|
|
])
|
|
assert len(report["cycles"]) == 1
|
|
assert set(report["cycles"][0]) == {"--fs-a", "--fs-b"}
|
|
|
|
|
|
def test_a_chain_of_derivations_is_not_a_cycle():
|
|
"""a <- b <- c is ordinary and must not trip the loop check."""
|
|
report = derivation_report([
|
|
_dtok("--fs-a", "#000"),
|
|
_dtok("--fs-b", "var(--fs-a)"),
|
|
_dtok("--fs-c", "var(--fs-b)"),
|
|
])
|
|
assert report["cycles"] == []
|
|
assert report["derived"] == {"--fs-b": ["--fs-a"], "--fs-c": ["--fs-b"]}
|
|
|
|
|
|
def test_a_token_referencing_itself_is_not_treated_as_a_dependency():
|
|
"""`--fs-x: var(--fs-x, fallback)` is a self-reference with a fallback, not a
|
|
derivation — counting it would report every such token as a one-node loop."""
|
|
report = derivation_report([_dtok("--fs-x", "var(--fs-x, 8px)")])
|
|
assert report["cycles"] == []
|
|
assert report["derived"] == {}
|
|
|
|
|
|
def test_a_derived_token_needs_only_a_base_value_to_follow_every_mode():
|
|
"""The reason formulas beat computed literals. One declaration in the base
|
|
layer tracks its source through dark mode too, because `var()` resolves where
|
|
it is USED, not where it is written — so there is nothing to re-derive when a
|
|
colour changes."""
|
|
css = render_stylesheet([
|
|
SimpleNamespace(
|
|
name="--fs-accent", value_by_mode={"base": "#5b4a8a", "dark": "#7a68b0"},
|
|
group_name=None, purpose=None,
|
|
),
|
|
_dtok("--fs-accent-soft", ACCENT),
|
|
])
|
|
dark_block = css.split('[data-theme="dark"] {')[1]
|
|
assert "--fs-accent:" in dark_block
|
|
assert "--fs-accent-soft" not in dark_block # stated once, follows anyway
|