0f80b790c7e8e6406d97c667ff719417c3d734f1
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
46d88f9e7e |
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
"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.
|
||
|
|
b0a7d9e89b |
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. |
||
|
|
4dc57f8ab2 |
feat(design-systems): import a design system out of a rulebook's prose
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 21s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 38s
Milestone #254 step 3 (#2288). Reuses #251's prose extractor as the reader and adds the part that makes it an import rather than a list of claims. **The join is the whole trick.** A rulebook states a design system in two places and neither half is a token: one rule names the colours ("Obsidian #14171A (page bg, deepest surface)"), another names the custom properties (`--fs-obsidian/iron/slate`). The import pairs them on the word — `--fs-obsidian` ends with `obsidian` — which is the only reason it produces something usable instead of seventy empty names. The parenthetical becomes the token's purpose, which is the field a bare hex could never carry. **Prohibitions arrive as replacements, per the operator's reframe.** Rule 52 declares Parchment and forbids pure white in one breath, so the import emits "write --fs-parchment instead of #ffffff" — the same fact stated forwards. It attaches to the FIRST token that rule supplied a value for, not to every token of that rule, because claiming Vellum is also the replacement for white would be putting words in the rulebook's mouth. **A token the rulebook names but states no readable value for is still proposed, with an empty value.** Radius steps and type sizes are prose ("Small 4px") and nothing here parses them; inventing a parse per shape would be guessing. The name is real and the value needs a human, so the proposal says exactly that — and the UI leads with the COUNT of those, because an import that hid them would look more complete than it is. Preview is the default on both surfaces and in the UI. An import is a proposal: rulebooks are written aspirationally and some of what they describe was never built, so every entry carries the rule id and the sentence it came from and a reviewer can check the claim rather than trust it. Existing token names are never overwritten. A value already in the record was put there deliberately — most likely correcting this importer — so a re-run fills gaps and lists the rest as skipped, which also makes it safe to repeat. Colours the rulebook names but never exposes as a custom property produce no token: it never asked for one, and inventing a name would put something in the record no rule sanctions. |
||
|
|
143b968c5d |
feat(design-systems): REST + MCP surfaces, at parity
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 10s
CI & Build / integration (push) Successful in 15s
CI & Build / Python tests (push) Successful in 41s
CI & Build / Build & push image (push) Successful in 26s
Milestone #254 step 4 (#2290). Eleven capabilities, both surfaces, one service. Design systems are owner-scoped top-level records rather than project-scoped ones, so these do not nest under /api/projects/ the way systems do — routes/rulebooks.py was the closer shape. The one exception is the project pointer, which is genuinely about a project: PUT /api/projects/<id>/design-system, PUT rather than PATCH because clearing it is a first-class outcome and not an omission. `/resolved` and `/tokens` are deliberately separate endpoints. One answers "what does this system CHANGE", the other "what does it end up BEING", and a system that overrides nothing has an empty token list and a full resolved set. Shipping only one would have made the other a client-side computation of exactly the kind the record model exists to remove. ResolvedToken.to_dict carries the SHADOWED contributions, not just the winner. Dropping them at the serialisation boundary would have discarded the one thing step 2 was built to preserve, and it would have been invisible — the payload still looks complete. Three sentinel translations on the MCP side, each tested, because an agent cannot omit an argument and a wrong mapping here is silent: - parent_id: 0 = unchanged, -1 = clear (become a family system), positive = set. Renaming a system must not silently re-root it. - order_index: -1 = unchanged, since 0 is a valid position. - value_by_mode: guarded on `is not None`, not truthiness, so `{}` can strip every mode from a token instead of being unreachable. DesignSystemCycle maps to 400 on REST and to a ValueError carrying the message on MCP — kept apart from 404 throughout. An agent told "not found" retries the same call; one told what the loop is can fix it. Two structural guards beyond the parity list: every endpoint must be reachable on the app (catching a decorator copied without its path, where the second handler silently never runs), and every public coroutine in the tools module must be registered (a tool written but never registered is invisible to an agent, and nothing else would notice). |