chore: the foundation says where each part of the preset contract actually lives - #770
Merged
Conversation
…ents.json carries only the fields shadcn's schema defines The foundation's source-of-truth table called components.json "the decoded preset configuration", which reads as a promise that every decoded property is represented there. It is not: shadcn's config schema is strict, and a property it has no field for is rejected rather than ignored, so the file cannot restate the whole preset even as documentation. The decoded `radius: medium` therefore has exactly one home, `--radius: 0.625rem` in styles.css, and its absence from components.json is the design working rather than drift. DESIGN.md now separates the three layers - preset intent, runtime realization, schema-supported configuration - with the radius as the worked example, and the Radius section names the stylesheet as the single place the medium step is spelled. The Lucide anti-pattern was also stale in a way that misdirected: it placed the remaining debt at screen level, where no file imports lucide-react any more. Every remaining import sits under frontend/ui-core/src/annotator/, which is what the rule now names. The architecture note picks up the same two corrections, and its count of the VisionSet extensions catches up with the five roles that exist.
…ibed Three places still described the primitives as "Radix + lucide": the ui-core package description, the stack table in the agent guidelines, and the frontend setup skill's instruction to contributors. Tabler is the icon set, and inside the primitives and the screens it is already the only one, so the instruction was pointing new code at the wrong library. Each now says what is true, and the skill adds where the exception lives, so a contributor who finds a lucide-react import under src/annotator/ reads it as debt rather than as the house style.
…defines Nothing in the repository read components.json, so the one repair the radius question keeps inviting - writing `radius: medium` into it because the preset decodes to that - had no gate in front of it. The field would not be ignored: the schema answers `unrecognized_keys`, which breaks every shadcn invocation that reads the file. The gate asserts the key set rather than a count, so a failure names what moved, and it pins the preset values the schema does carry. It lives beside the other design invariants, and its failure message points at styles.css, where a decoded property with no field belongs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The shadcn/Nova foundation is in place and working. What was left over is
representational: three files described the preset contract in ways that were no
longer true, and one machine-readable claim had no gate in front of it. This
reconciles the description with the implementation and changes no runtime value.
The radius question, settled
DESIGN.mdrecords the decoded preset as includingradius: medium, thestylesheet materializes it as
--radius: 0.625rem, andcomponents.jsonhas noradiusfield. The obvious repair — write the field in — is wrong, and verifyingit inside the repository's Linux container is what this change rests on.
The preset decodes to ten values, none of them a
base:The CLI is 4.18.0, the version
DESIGN.mdalready names as its reference, andthe version
pnpm-lock.yamlpins. Its config schema is strict, so the field isrejected rather than absorbed — parsing the current configuration with
radiusadded answers:
Adding it would not be harmless documentation; it would break every
shadcninvocation that reads the file. The configuration as committed parses cleanly and
is unchanged here. So the contract is one intent held in three layers, which is
what
DESIGN.mdnow says:radius: mediumstyles.css--radius: 0.625remcomponents.jsonbase: radixis worth one note for the next reader: it is not a decoded value atall, but an initialization choice folded into
style: "radix-nova".The icon set, described as it is
Four places still called the primitives "Radix + lucide" — the
ui-corepackagedescription, the stack table in
AGENTS.md, the frontend setup skill, and thearchitecture note. The setup skill's version was the costly one, because it
instructed contributors to reach for the library the foundation had moved off.
DESIGN.md's own anti-pattern was stale in a way that misdirected: it placed theremaining debt at screen level. No screen imports
lucide-reactany more. Alleleven remaining imports sit under
frontend/ui-core/src/annotator/, and that iswhat the rule now names.
The gate
Nothing read
components.json, so the repair this change argues against hadnothing standing in its way. The new invariant in
tests/scripts/design_tokens.test.mjsasserts the key set rather than a count,so a failure names what moved, and pins the preset values the schema does carry.
It was verified by breaking it: adding
radiusfails it, driftingiconLibraryoff
tablerfails it, and restoring the file returns it to green.Found, not fixed
@visionset/appdeclareslucide-reactand has no import of it — threementions in
src/demo/ToolStrip.tsxcomments are the only occurrences. Thedeclaration is removable, but the frontend dependency bump (chore(deps): bump the frontend-minor-patch group across 1 directory with 5 updates #768) edits that
exact line and the lockfile, so removing it here would conflict for no gain.
The annotator migration can drop both together.
src/demo/ToolStrip.tsxexplains its hand-drawn glyphs by sayingDESIGN.mdpins
lucide-react. That has not been true since the preset landed. Leftalone as product code outside this change's scope.
Test plan
Everything ran inside Docker/Linux; no repository validation ran natively on the
host. Two things are worth recording for whoever runs these next.
The
appcompose service is not a test runner: its image bakes manifests andtsconfigs but neither
eslint.config.jsnorvitest.config.ts, so the suitesthere fail in the thousands and lint dies on a flat-config error — the same shape
as
exec api pytestexiting 5 becausetests/is not mounted. And.gitattributesforces LF only for
*.sh, so on acore.autocrlf=truecheckout every Markdownfile is CRLF while the blobs are LF; copying that working tree into a container
fails roughly eighty anchor assertions in files nobody touched, because the
trailing
\rcorrupts every heading slug. The run below normalizes to LF, whichis what a Linux checkout gives.
A full tree with a real install, in
node:24-bookworm:The design and documentation gates were re-run against the final content after a
prose rewrap:
design_tokens,docs_linksanddocs_sidebartogether report 19passing, 0 failing.
Playwright was not run. No runtime CSS, token, primitive or product-UI file
changes here, so there is no rendered surface for it to inspect;
components.json,styles.cssandtokens.tsare byte-for-byte unchanged.