feat(design-systems): check the components against the sheet they claim to use
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
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.
This commit is contained in:
@@ -21,7 +21,11 @@ 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_stylesheet import (
|
||||
check_code_against_tokens,
|
||||
duplicate_values,
|
||||
render_stylesheet,
|
||||
)
|
||||
from scribe.services.design_cascade import (
|
||||
ResolvedToken,
|
||||
ancestry,
|
||||
@@ -437,3 +441,57 @@ async def stylesheet_for_system(
|
||||
"valueless": [t.name for t in resolved if not t.value_by_mode],
|
||||
"duplicates": duplicate_values(resolved),
|
||||
}
|
||||
|
||||
|
||||
async def check_snippets_against_system(
|
||||
user_id: int, design_system_id: int, project_id: int = 0
|
||||
) -> dict | None:
|
||||
"""Which recorded snippets disagree with this design system's sheet.
|
||||
|
||||
The relation the operator named — "the snippets use the tags from the sheet"
|
||||
— turned into a check. For each snippet: `var(--x)` references with no such
|
||||
token, literals the sheet says to stop writing, and custom properties the
|
||||
snippet mints for itself instead of using shared ones.
|
||||
|
||||
Snippets with nothing to report are omitted entirely. A list of everything
|
||||
that is fine is a list nobody reads twice.
|
||||
"""
|
||||
resolved = await resolve_design_system(user_id, design_system_id)
|
||||
if resolved is None:
|
||||
return None
|
||||
|
||||
from scribe.services import snippets as snippets_svc
|
||||
|
||||
# `list_snippets` returns (rows, total) and caps limit at 100; `project_id`
|
||||
# must be None — not 0 — to reach across every project, since 0 would filter
|
||||
# to a project with that id.
|
||||
rows, _total = await snippets_svc.list_snippets(
|
||||
user_id=user_id, project_id=project_id or None, limit=100,
|
||||
)
|
||||
|
||||
findings: list[dict] = []
|
||||
for row in rows:
|
||||
snippet_id = row.get("id")
|
||||
if snippet_id is None:
|
||||
continue
|
||||
# The list rows carry a preview, not the code. The check has to read the
|
||||
# whole body or it would report on a truncation.
|
||||
note = await snippets_svc.get_snippet(user_id=user_id, snippet_id=int(snippet_id))
|
||||
if note is None:
|
||||
continue
|
||||
report = check_code_against_tokens(note.body or "", resolved)
|
||||
if not (
|
||||
report["unknown"] or report["superseded_literals"] or report["local_definitions"]
|
||||
):
|
||||
continue
|
||||
findings.append({
|
||||
"snippet_id": int(snippet_id),
|
||||
"title": note.title or "",
|
||||
**report,
|
||||
})
|
||||
|
||||
return {
|
||||
"design_system_id": design_system_id,
|
||||
"checked": len(rows),
|
||||
"findings": findings,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user