CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 35s
"The snippets use the tags from the sheet" was a relation nobody could verify.
Now it is three checks, and all three currently fail SILENTLY in this codebase:
unknown `var(--x)` where the system declares no `--x`. Renders as
nothing at all — no error, no failing test, no visual clue
beyond the element quietly not being styled.
superseded literals a value the sheet said to stop writing, paired with the
token to write instead. Only possible because `supersedes`
is declared rather than inferred.
local definitions custom properties a snippet mints for itself instead of
reusing the sheet's — the bloat a shared sheet exists to
prevent, where a value stops being reused and starts being
restated per component.
The first is not hypothetical. Writing DesignSystemsView.vue earlier in this
same session I used `--color-accent` throughout; it does not exist, and nothing
in the toolchain noticed. This check is the thing that would have.
A token that is both defined and read locally is reported ONCE, as an unknown
reference — "--btn-bg does not exist in the sheet" is the more precise statement
of the same problem, and reporting both would double-count one fact.
Literal matching is boundary-aware and case-insensitive: `#fff` must not fire
inside `#ffffff` (different colours, and a finding on the wrong one sends
someone to change correct code), while `#FFFFFF` in a rulebook has to match
`#ffffff` in a stylesheet — the same trap `normalize_hex` exists for.
Snippets with nothing to report are omitted entirely. A list of everything that
is fine is a list nobody reads twice — the same principle the auto-inject menu
and the drift panel are both built on.
Two integration mistakes fixed while wiring it: `list_snippets` returns
`(rows, total)` and caps its limit at 100, and `get_snippet` returns a Note
model rather than a dict. The list rows carry a preview, not the code, so the
check reads each full body — checking the preview would have reported on a
truncation.
322 lines
13 KiB
Python
322 lines
13 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,
|
|
duplicate_values,
|
|
is_valid_token_name,
|
|
render_stylesheet,
|
|
safe_comment,
|
|
safe_value,
|
|
selector_for_mode,
|
|
)
|
|
|
|
|
|
def _token(name, value_by_mode, group_name=None, purpose=None):
|
|
return SimpleNamespace(
|
|
name=name, value_by_mode=value_by_mode,
|
|
group_name=group_name, purpose=purpose,
|
|
)
|
|
|
|
|
|
# --- 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([_token("--fs-x", {"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([_token("--bad name", {"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([
|
|
_token("--fs-obsidian", {"base": "#14171a"}, group_name="surface"),
|
|
_token("--fs-moss", {"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([_token("--fs-x", {"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([
|
|
_token("--fs-bg", {"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([
|
|
_token("--fs-bg", {"base": "#f5f1e8", "dark": "#14171a"}),
|
|
_token("--fs-radius-md", {"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(
|
|
[_token("--fs-x", {"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([
|
|
_token("--fs-obsidian", {"base": "#14171a"}, group_name="surface"),
|
|
_token("--fs-radius-md", {"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([
|
|
_token("--fs-obsidian", {"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([
|
|
_token("--fs-obsidian", {"base": "#14171a"}),
|
|
_token("--fs-radius-sm", {}),
|
|
])
|
|
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([
|
|
_token("--fs-moss", {"base": "#4A5D3F"}),
|
|
_token("--fs-success", {"base": "#4a5d3f"}),
|
|
_token("--fs-obsidian", {"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([
|
|
_token("--fs-a", {"base": "#fff", "dark": "#000"}),
|
|
_token("--fs-b", {"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([_token("--fs-a", {}), _token("--fs-b", {})]) == {}
|
|
|
|
|
|
# --- 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():
|
|
"""Rulebooks write `#FFFFFF` and code writes `#ffffff`. A case-sensitive
|
|
check would silently find nothing — the same trap normalize_hex exists for."""
|
|
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"]
|