Conversation
rawConfigSchema is strict, so an app that adds a lint key today makes getConfig throw. The new lintConfigSchema makes the block optional and keeps it strict, so a typo in a rule name or severity is a config error rather than a silent no-op. extractHelperAxes reads the variant and size VALUES a helper exposes from its own source text, which is what a no-restyle message has to name, and it lives beside extractHelperSignatures so the two projections cannot drift.
The grammar parses one token into variants and a utility, decides
arbitrary VALUE against arbitrary VARIANT on whether the utility segment
carries a bracket, and resolves the utility to a class group and to the
category taxonomy transcribed verbatim from shadcn-ui/lint. The scanner
reads class sites from html tagged templates, cn() arguments and
class=${} holes with a hand-rolled lexer, and only inside an open tag, so
an entity-escaped code sample in a docs page is never a site. The theme
reader parses --color-* tokens from both @theme and @theme inline.
…ules Each rule is a pure function over a class site so its tests are a string in and an array out. no-raw-colors names only tokens the app theme declares and adds a role suggestion only when unambiguous. no-restyle names the helper variants and sizes the shared projector reads from the app copy, guarded by a drift test against button.ts, and the skill sanctioned one-off passes under allow layout plus rounded.
The orchestrator walks app, components, modules and lib, skips the resolved ui directory by default (a copied primitive owns structural values no variant expresses and is what other files are measured against), reads the theme once and turns no-raw-colors off with a warning when it yields no tokens. The command mirrors webjs check in report shape and 0/1 exit posture, adds a severity marker, --json and --max-warnings, and reports nothing with no lint block so no existing install changes behaviour.
The skill, the /ui page and the styling docs describe the command and the lint block; styling.md names the allow configuration under which its sanctioned icon-button one-off passes, so the rule and the skill agree. The three-arm eval under packages/ui/test/evals runs on demand and is the gate before any surface tells an agent to run the linter.
The wrapper spawns the ui bin with the arguments verbatim, so lint already dispatches; only the usage string and the two command tables named the old six.
|
Design notes: the class-to-group resolver, opacity stripping, and two scanner calls the issue left open The issue settles the taxonomy (shadcn's The Two scanner calls the issue's D2 did not pin down, both decided toward fewer false positives. A recognised The |
vivek7405
left a comment
There was a problem hiding this comment.
Read the whole diff plus the scanner, grammar, rules, orchestrator, command, extractor and the eval harness. The scanner is careful (the tag-region rule, the hole-fragment rule, the mute for helper args, position tracking through escapes) and the tests cover the shapes the description claims. Nothing here blocks the opt-in guarantee.
What I would fix before merging:
- Two prefix-ordering bugs in
groupOf:text-shadow-*andbg-blend-*both resolve to a colour group (the dedicated branches are dead), so anallow: ["effects"]never admits them and the messages call them colours. Suggestions inline. --jsonis not JSON on the two early exits (missing / invalidcomponents.json), andrun.mjsturns any non-JSON lint output intocount: 0, so a broken linter reads as a clean eval. Since the harness is the phase-3 gate, that one matters more than its size.- The no-raw-colors message says "error text" for a
border-/ring-/fill-offender.
Smaller: non-class string literals in a class hole are collected as classes (only layout saves them), commented-out markup inside an html template is still scanned, no-restyle names only the first helper of a site, a redundant ternary on cnNames, and in no-raw-colors ordered / list are rebuilt per token and could sit above the loop.
I did not apply any of these (the review-only rule); happy to fold them in on the branch if you want that as a separate ask.
vivek7405
left a comment
There was a problem hiding this comment.
Review of the linter at f7ddfe88, run inline (no reviewer subagents) at high effort for recall. Ten findings, most severe first, each anchored where it fails; the extractor, scanner, grammar and import-list ones were reproduced by running the code against the kit button.ts and small templates.
Not fixed on the branch: the webjs-ui-lint worktree had uncommitted edits landing from another session while this review ran (several of them address the same lines: the text-shadow order, the HTML-comment region, the message wording, the eval double lint, and a --json error document), so applying fixes there would have collided. The findings stand against the pushed head; whichever session owns the worktree can fold them in.
A longer head is matched before the prefix it starts with, so text-shadow and bg-blend resolve to their own groups. The projector blanks comments before matching braces, so a comment in an app copy of a variant map cannot swallow the value list. A comparison operand or a case label in a class hole is not a class, commented-out markup opens no tag, and a comment inside an import list is not a binding. A negated ignore entry un-ignores what it matches rather than one exact string, --json answers the early exits with an error document, a non-integer --max-warnings is refused, the role sentence names the surface by prefix, and no-restyle names every composed helper. The eval harness throws on a run that yields no document and lints once per round. The command test pins the blog shape inline instead of live line numbers.
Closes #1478
Adds
webjsui lint, an opt-in design-system linter in@webjsdev/ui. It reads the Tailwind classes an app writes insidehtmltemplates,cn()calls andclass=${...}holes, and reports at the exact line where the app drifts off its own theme, with a message built from the app's real tokens and helper variants. Three rules ship (no-raw-colors,no-arbitrary-values,no-restyle), each off unless alintblock incomponents.jsonturns it on, so no existing install changes behaviour.What changed
rawConfigSchemaaccepts an optional, strictlintblock (it is.strict(), so alintkey threw before).extractHelperAxesin the shared projector reads a helper's variant and size values from source, so ano-restylemessage names what the app's copy actually declares. Drift-guarded against the kit button.htmltagged templates,cn()arguments andclass=${...}holes. Aclass=is a site only inside an open tag, which is what keeps the entity-escaped code sample in the file-storage docs page from being reported.shadcn-ui/lintsoallow: ["layout"]means the same thing in both tools.@themeand@theme inline. No tokens turnsno-raw-colorsoff for the run with one warning naming the path.webjsui lintcommand: awebjs check-shaped text report with a severity marker,--json,--max-warnings, and 0/1 exit codes.components/ui/**is skipped by default; a negated ignore entry ("!components/ui/**") widens the scope.packages/ui/test/evals/(before, after with diagnostics, rules-only control), run on demand through theclaudeCLI. It is the gate before any surface tells an agent to run the linter.Against the blog with a
lintblock, all fourtext-red-600feedback lines are reported and the escaped docs sample is not.Deliberately excluded
webjs check, which stays correctness-only.@webjsdev/serveror@webjsdev/mcp.Test plan
npm test --workspace=@webjsdev/ui(275 pass after the review fixes) and rootnpm test(4688 of 4694; the 5 failures are the known linked-worktree set, listener, listener-overhead and three elision assertions, which pass in the primary and in CI)no-raw-colorsat line 29 and clears withtext-destructive(lint-command.test.js, proven atcdea21cc, re-proven at5194c88cwith the shape pinned inline)webjs checkpasses in gallery, examples/blog and website;webjs doctorin website has 0 failures/uiand/docs/stylingat 200, 12 preloads each, none broken, both carrying the new contentnode:fsandnode:path, already used throughoutpackages/ui/src)Note for a linked worktree:
webjs ui lintthrough the CLI wrapper resolves@webjsdev/uiinto the primary checkout, so it reportsunknown command 'lint'there. The wrapper spawns the ui bin with the arguments verbatim, andnode packages/ui/bin/webjsui.js lintfrom the branch works.Docs
packages/ui/README.md(command row),packages/ui/AGENTS.md(lede, module map, command row, a section on the config, rules, taxonomy, scope and phases).agents/skills/webjs/references/ui-kit.md(the command and its block),references/styling.md(theallow: ["layout", "rounded"]sentence at the sanctioned one-off)website/app/ui/page.ts(command row),website/app/docs/styling/page.ts(a subsection at#lint)AGENTS.mdCLI reference,packages/cli/AGENTS.mdand thewebjs uiusage string listlintcreate.js, MCP, editor plugins: N/A in phase 1 by design (no scaffold wiring until the gate), and the MCPuitool projects the kit, not the linter