Skip to content

feat: make the UI kit and AI design guidance first class - #1414

Merged
vivek7405 merged 14 commits into
mainfrom
feat/ui-design-first-class
Aug 15, 2026
Merged

feat: make the UI kit and AI design guidance first class#1414
vivek7405 merged 14 commits into
mainfrom
feat/ui-design-first-class

Conversation

@vivek7405

@vivek7405 vivek7405 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Closes #1116

Makes the WebJs UI kit and the AI design guidance first class, in one PR rather than the eight the plan of record splits it into, at the owner's request.

Two halves, and the order matters. The artifacts (a real token layer, six missing primitives, design intent on every helper header, two worked exemplars) are justified on their own and land first. The guidance (a design reference in the skill) is the unproven half: frontier models already carry hierarchy and spacing principles, so whether a loaded reference changes generated output is a question, not an assumption. It lands behind the artifacts, and an owner-run gate measures its marginal effect on top of them.

The gate is a pre-merge stop

Phase 6 of the plan is a hard stop by design. In a single PR it becomes a stop before merge: everything is implemented, and the before-and-after comparison runs against this branch before it merges. The protocol, what each outcome means, and what the harness cannot see are in a comment on this PR. scripts/eval-design/RUNBOOK.md is executable from a few commands with nothing reconstructed from the issue.

If the after side does not beat the before side, the guidance does not ship: revert the two reference files, their routing rows in SKILL.md and AGENTS.md, and the two cheat-sheet rows, then merge the rest. That is a documented outcome, not a failure of the PR.

A latent contrast bug, corrected (and an earlier version of this claim was wrong)

--destructive-foreground measured 1.65:1 against --destructive in dark mode, well under the 4.5:1 floor, and 4.35:1 in light. Both now clear it.

The first version of this PR called that a live shipped bug and pointed button.ts at the corrected token. The review round proved both halves wrong. No registry component read --destructive-foreground at all, so the bad pair was latent, never rendered. And the button paints dark:bg-destructive/60, a composite rather than a solid fill, against which the corrected token measures 2.49:1 where the text-white it would have replaced measures 6.48:1. So the change would have introduced the regression it advertised as a fix.

The token correction stays, because it makes the pair safe for app code that writes bg-destructive text-destructive-foreground over a solid fill. button.ts keeps text-white, with the measurement recorded beside it, and a test pins button and badge against the token so the two cannot drift apart.

Every colour value in this PR is backed by a measured contrast ratio rather than a guess, computed from Tailwind v4's real oklch ramps.

What shipped, by phase

  • 1. The design-quality scorer. scripts/eval-design.mjs emits nine numbers per app, no model in the loop, so two runs agree. Three prompts committed verbatim with a test-asserted ban on design vocabulary, because a brief that asks for a polished screen measures the brief.
  • 2. The token layer. Semantic roles (success / warning / info, destructive folded into the same shape), four tokens each. Role-named elevation shadow-e1 through shadow-e4. In dark the lift comes from the surface being lighter than the page, which --card and --popover already express; the shadow only separates the edge.
  • 3. Six primitives. empty-state, stat, page-header, field-group, description-list, timeline.
  • 4. Design blocks on all 38 component headers, which webjsui add copies into the user's own repo.
  • 5. The design reference, design.md plus design-depth.md, routed from both tables.
  • 6. The gate, as a pre-merge stop.
  • 7. Two exemplars, a dashboard and a settings form, both scoring zero on the rubric.
  • 8. Delivery surfaces, docs site, packages/ui/AGENTS.md, root AGENTS.md, gallery:clear.

Two landmines worth knowing about

cn() would have silently broken elevation. A bare shadow name the size alternation does not list falls through to the shadow-color group, so shadow-e1 and a real shadow colour would have evicted each other with no error. The alternation learned e1 through e4 first, in all three copies of the classifier.

Tailwind inlines --shadow-* values. It substitutes the value into the utility rather than emitting a var() reference, so redeclaring --shadow-e1 under .dark does nothing at all. The shadow colour rides a var inside the value, which is what makes the dark inversion work.

Test plan

  • test/scripts/eval-design.test.mjs, 32 assertions, with a per-line counterfactual so a silently dead rubric line cannot hide behind a failing sibling. Verified by neutering one line and watching only its own assertion fail.
  • packages/ui/test/* plus repo-health, scripts and scaffolds: 541 passing, including the raw-palette and elevation regression tests, the cn() classifier test (counterfactual verified at e7ac7d62), and the structural header test across all 38 components.
  • test/repo-health/design-reference-routed.test.mjs, asserting the reference exists AND is routed from both tables, since an unrouted reference is a file no agent opens.
  • Full npm test. Two failures, both proven pre-existing: test/bun/listener.test.mjs fails identically with origin/main's copy of the file (a known linked-worktree failure), and the gallery:clear failure was mine and is fixed.
  • webjs check clean on gallery and website.
  • Dogfood: website boots 200 on /, /docs/styling, /ui, /ui/button with no broken preloads; gallery boots 200 on /, /examples/dashboard, /examples/settings, /examples/todo; all six new /ui/<name> pages render.
  • The MCP ui tool reports 38 components and serves all six new headers. Verified by reading the inventory back rather than assuming, since it derives from the registry.
  • Browser layer: N/A. All six primitives are Tier-1 class helpers returning strings, with no custom element, no state and no DOM interaction. The existing Tier-2 browser suites are untouched.
  • Bun parity: N/A. The surface is CSS tokens, class-helper strings, markdown and a Node-only dev script. Nothing touches the serializer, the listener or request path, SSR or action dispatch, streams, node:crypto, or the TS stripper.

The audit, fixed rather than reported

The gallery scored 72 rubric hits when this started. It now scores zero on all nine lines.

About a third of those were the scorer being wrong, not the payload, and fixing the instrument mattered more than fixing the code: had I churned the demos to satisfy a broken rule, I would have made them worse. Six false-positive classes, all fixed in scripts/eval-design.mjs:

Miscounted Why
Sitemap: ${url}, `hint:${key}` Rule 9 scanned whole files rather than html templates
title="Theme: ${x}" The > in an @click=${() => …} read as a closing tag
global-error.ts hex values It renders its own document with no stylesheet to token against
Four pages "missing" an <h1> The heading comes from pageHeading(), invisible to a static scan
text-[color:var(--c)] A colour counted as an arbitrary font size
(#492) in a metadata description An issue reference counted as a hex colour

The rest were real and are fixed: 17 off-scale widths, 36 off-scale type sizes, two lists with no empty branch (the frames filter genuinely matches nothing; gallery:clear genuinely empties the home), and five label-colon-value captions, which design.md itself says are usually unnecessary.

Not in this PR, and not filed

website scores 179 and examples/blog 90 on the same rubric. Neither was in the audit's scope, and the website's hits are mostly literal colours in a deliberately hand-tuned marketing palette. Applying the kit's rubric there is a redesign, not a cleanup, so it is recorded here for a separate decision rather than folded in or filed as an issue.

Review

Six rounds plus a final whole-diff pass and a fix-check. Nineteen defects found and fixed, including two in fixes for earlier defects. The pattern worth knowing: the code was mostly sound and the claims about the code kept being wrong, which is why several fixes are corrections to comments and docs rather than to behaviour. Two findings would have shipped visibly broken output (six unstyled component previews; a destructive button pushed to 2.49:1 in dark by a change advertised as a contrast fix).

The design gate needs an instrument before it can measure anything, and the
instrument has to be on main so both sides of the comparison are scored by
byte-identical code. This adds it alone, ahead of the guidance it exists to
evaluate.

scripts/eval-design.mjs walks a generated app and emits nine numbers, one per
rubric line, with no model in the loop, so two runs over the same input agree.
It reports numbers and never a judgment, which is what lets a reviewer re-run
it against either side and compare.

The three prompts are committed verbatim and carry no design vocabulary. A
brief that asks for a polished screen measures the brief rather than the
guidance, so the ban is asserted by a test rather than left as a convention.

The dirty fixture trips all nine lines exactly once so each can be asserted
individually. Without that, a silently dead line would hide behind a failing
sibling and the harness would report a clean instrument that measures nothing.
@vivek7405 vivek7405 self-assigned this Aug 14, 2026
The kit had one semantic colour, --destructive, and no elevation scale. A
component that needed to say success reached past the token layer for
text-emerald-500, which is exactly what sonner.ts did, so the missing roles
were the root cause of the raw-palette drift rather than a separate problem.

Four tokens per role, a solid fill plus the text on it and a tinted surface
plus the text on it, for success, warning and info, with destructive folded
into the same shape. Not an 11-step ramp: no further step has a role a
component asks for, and Tailwind's opacity modifier already covers borders.

Elevation is Tailwind's own geometry renamed by role, e1 raised through e4
top. The shadow colour has to be a var INSIDE the value, because Tailwind
inlines a --shadow-* token into the utility rather than emitting a var
reference, so a .dark redeclaration of the shadow itself would do nothing.

Fixes a live contrast bug on the way. --destructive-foreground measured
1.65:1 against --destructive in dark mode, so the destructive button shipped
with a label very nearly invisible against its own fill. Light was 4.35:1,
also under the floor. Both now clear 4.5:1, which also lets button.ts use the
token instead of the hardcoded text-white it was working around.

The cn() classifier had to learn e1 through e4 first. A bare shadow name the
size scale does not list falls to the colour catch-all, so an elevation
utility and a real shadow colour would have silently evicted each other.
…n-list, timeline

Six primitives the kit was missing, each replacing a shape an app otherwise
hand-rolls badly. All Tier-1 class helpers: none holds state, so none
registers a custom element.

empty-state is the highest-leverage one, because the defect it prevents is
invisible in development. A list rendered against seeded data looks finished
and renders as blank space for every user who has not created anything yet.

description-list is the direct replacement for printing a field name and its
value at the same weight on one line, which is the single most common
generated-screen defect and is what makes a detail panel unscannable.

stat inverts the label and the value deliberately, since a reader scans a
dashboard for numbers. Its delta colours go through the semantic roles added
in the previous commit rather than a raw green or red.

field-group adds the grouping and addon shapes only. lib/utils.ts already
owns the single-field helpers and this must not shadow them, which a test
asserts, because fieldErrorClass (the reserved space) and errorClass (the
text that goes in it) read alike and are different things.

The helpers use block bodies rather than concise arrow bodies. The
module-scope purity scanner reads a top-level arrow-body cn() call as module-scope
work, and the alternative was six more entries on a known-false-positive
allowlist that should only ever shrink.
@vivek7405
vivek7405 force-pushed the feat/ui-design-first-class branch from af1d92c to 1f24005 Compare August 14, 2026 17:28
A reference file only helps an agent that loads it. The component header is
read where the decision actually gets made, by whoever is placing the
component, and webjsui add copies it into the user's own repo, so the intent
travels with the code rather than staying in this repo.

Every component gains a Design block beside its existing A11y block, saying
what the component is for, what it is not for, and where its variants sit in
the hierarchy. The button block states the action pyramid at the one call
site where it is most often broken. The alert block says why two alerts on a
screen mean neither reads as urgent. The skeleton block says a skeleton whose
shape does not match its content is worse than a spinner.

The structural test asserts both blocks exist and that the Design one is long
enough to be intent rather than a placeholder. It cannot judge the prose, but
it can stop a later component shipping with none.
The skill taught agents how to apply styles mechanically and taught them
nothing about what a screen should look like before the class list is written.
styling.md answers how do I make this work in WebJs, and nothing answered what
should this look like.

Two files, because one loses either way. design.md is the core, under the same
budget as styling.md so it can load alongside it on every UI task: hierarchy,
the action pyramid, spacing and grouping, type, colour, elevation, empty
states, then the WebJs-shaped part. design-depth.md is on demand: palette
construction from an accent, the per-family variant checklist, the finishing
pass, and the three archetypes.

The WebJs-shaped section is the half that is not in any design book. An empty
state must be in the first paint because pages do not hydrate. A suspense
fallback is a design surface rather than a spinner dump, since it flushes on
the first byte and is often on screen longest. Polish on a display-only
component is free because elision means the browser never downloads it.

Both routing tables get a row, and they are not the same table: SKILL.md
routes topic then reference, root AGENTS.md routes reference then topic. The
test asserts the file exists AND is routed from both, because an unrouted
reference is a file no agent opens, and the gate would then measure the
routing rather than the content.
Code teaches harder than prose. Every demo in the gallery taught one framework
feature and none taught what a screen should look like, so an agent reading
the gallery learned mechanics and nothing else. These two are the archetypes
where flat output is worst, and each is commented with the reason behind the
decision rather than the rule.

The dashboard carries one primary action, the label-under-value inversion, an
empty branch written beside its list, and state through the semantic roles. It
draws its own bar chart in seven divs rather than shipping a chart library for
something a page can render server-side.

The settings form is grouped into fieldsets with legends, uses a tighter gap
between a label and its field than between fields, and reserves the error
space so a validation message cannot shift the control the reader was about to
click. It binds a server action, so it submits with JS off.

Both score zero on the design rubric, which is the test that they teach what
they claim to. The data has awkward shapes in it on purpose, a depot name long
enough to wrap and a zero in the series, because a layout built around tidy
data breaks the first time real data arrives.

Two fixes fell out of building them. The timeline connector used a magic
offset that its own rubric line flags, and now centres on the scale instead.
The empty-state rubric line counted a map over a literal array, which cannot
be empty at runtime, so it flagged a fixed set of six checkboxes. Its own
definition was always a list over a query result; the first implementation
could not tell the difference.

The settings exemplar also tripped invariant 9 on its first boot, a backtick
inside an html template body, in a comment. The comment now says so.
…n reference

The docs site's styling page covered the mechanics and stopped there, which is
the same gap the skill had. It now documents the semantic roles, why writing a
palette family instead makes an app un-rethemable, the role-named elevation
scale, and the reason the shadow colour has to ride a var inside the value
rather than being redeclared per theme.

packages/ui/AGENTS.md gains the token layer as its own section and the six
primitives in the inventory, with the field-group caveat that fieldErrorClass
is the reserved space and errorClass is the text, since the two read alike.

Root AGENTS.md points at both reference files from the styling section and
states the rule that state goes through a role rather than a palette family.

gallery:clear learned the two new modules. Without that the command left a
scaffolded app with modules it claims to have stripped, which the scaffold
test caught.

The MCP ui tool and the marketing site's /ui pages needed no change: both
derive from the registry, so the six primitives and their headers arrive
there on their own. Verified rather than assumed, by rendering all six pages
and reading the tool's inventory back.
@vivek7405

Copy link
Copy Markdown
Collaborator Author

Gate: the guidance half needs your before-and-after run before this merges

The artifacts are done and stand on their own. The guidance is the half nobody has measured, and this is the stop for measuring it. I cannot run it myself: the party checking the result must not be the party that wrote the thing being checked.

What to run. scripts/eval-design/RUNBOOK.md, which is executable from a few commands and reconstructs nothing from the issue. Cut two worktrees (this branch, and this branch with the two reference files and their routing rows reverted), scaffold an app per prompt per side, run each of the three committed prompts in a fresh headless session, score with node scripts/eval-design.mjs <app>, screenshot one pair per prompt in light, dark, and one JS-disabled load.

Three runs per prompt per side is the floor, and post every run rather than the best one. Both sides carry the harness, the tokens, the primitives and the headers, so the only difference between them is references/design.md plus its two routing rows. That is deliberate: it makes the number the guidance's MARGINAL effect on top of artifacts an agent already has, which is the number the decision actually needs.

What each outcome means.

The after side wins on the rubric lines, regresses none, and the screenshots agree: the guidance ships and this merges whole.

The after side does not beat the before side: the guidance does not ship. Revert .agents/skills/webjs/references/design.md, design-depth.md, their rows in SKILL.md and AGENTS.md, and the two cheat-sheet rows, then merge the rest. The artifacts keep their value either way, and a null result is the hypothesis answered rather than a failure of the PR. Do not iterate on the wording hunting for a positive number, which is measuring until you like the answer.

The after runs provably never loaded the file: that measures the routing, not the content. Fix the routing and rerun the after side once. It is the only rerun the gate allows.

One thing the harness cannot see. The nine rubric lines are static scans, so they catch a raw palette colour, an arbitrary spacing value, a missing empty branch and two primary buttons on a page. They cannot tell you whether the screen reads well. That is what the screenshot pairs are for, and your own read of them is the half of the result no number covers.

@vivek7405

Copy link
Copy Markdown
Collaborator Author

Where the implementation departs from the plan, and why

Four places. Each was a decision the plan settled and the code could not follow as written.

Elevation reuses sm / md / lg / xl, not xs / md / lg / xl. The plan mapped e1 to --shadow-xs and argued in the same paragraph that every level is two-part with an ambient and a contact component, which is the property the dark inversion relies on. Those two cannot both hold: --shadow-xs is single-part. Since the two-part argument is the load-bearing half, e1 takes --shadow-sm's geometry, which is also the conventional card-at-rest shadow. The other three are unchanged.

The theme file is not on light-dark(). The plan says PR #1217 declared every theme colour once through light-dark() and instructs new tokens to follow that shape. The file still has separate :root and .dark value blocks, so the new tokens follow the file. Worth knowing before anyone writes a token against the plan's description of it.

The six helpers use block bodies rather than concise arrow bodies. The module-scope purity scanner reads a top-level => cn(...) as module-scope work. Four existing components sit on a known-false-positive allowlist for exactly this, and the test that pins it says the list should only ever shrink. Adding six more entries to a list documented as shrink-only is the wrong direction, and the test's own comment names wrapping in braces as the alternative, so that is what these do.

Rule 7 of the rubric now excludes a map over a literal array. Its written definition was always a list "over a query result", and the first implementation could not tell a query result from a hardcoded array, so it flagged the settings exemplar's fixed set of six notification checkboxes. A list that cannot be empty at runtime needs no empty branch, and counting it would train an author to add an empty state that can never render.

One thing I did not do. Scoring the whole gallery turns up 72 hits in pre-existing feature-demo payload. The plan asked for an audit of the home page, the root layout and the todo example, and the finding is that those three are clean on hierarchy and the action pyramid, with only off-scale type left. I recorded that rather than rewriting demos whose job is to teach framework features, but it is a real number and someone should decide about it separately.

…ertion

Seven review findings, all real.

The two exemplars did not compile. They called buttonClass with an outline
variant, which the registry has and the gallery's own customised button does
not, and the settings action imported ActionResult from @webjsdev/core, which
documents the envelope but exports no such type. The type is declared locally
now, since an exemplar that does not compile teaches worse than one that
repeats a shape.

The settings form rendered four empty fields. It bound .value on a native
input, and a .prop hole is dropped at SSR on anything without a hyphen in its
tag, so with a page that never hydrates the value was never set on either
side. It also meant a 422 lost everything typed, which is precisely what the
file claims to demonstrate. It is a plain value= attribute now, with a comment
saying why, because this is the trap the exemplar exists to not teach.

The website's Tailwind theme carried none of the new tokens, and it @sources
the mirrored registry, so every shadow-e1 through shadow-e4, text-success,
text-info and text-warning the kit now emits compiled to nothing on the
deployed /ui gallery. Verified by building the stylesheet: zero rules before,
one each after. The same file also still carried the old
--destructive-foreground, so the 1.65:1 dark-mode contrast bug this branch
fixes in the registry was live on webjs.dev, and swapping button.ts from
text-white to the token would have shipped it there.

The header test's block terminator could not match A11y ( , because the
character class it used rejects digits. Wherever the Design block sat above
the A11y one the terminator found nothing, the block swallowed the rest of the
header, and the length assertion passed on A11y prose rather than on any
design content. The terminator is a literal set now, with a guard that fails
if the extraction ever absorbs the A11y section again.

Also corrects a comment in the gallery layout that the line beneath it
falsified, and the exemplar headers that still described the old variant.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through the whole diff. The design work holds up and the token layer is the right shape, but the two exemplars did not compile and the website half of the change was missing entirely, so I would not have merged this as it stood.

The exemplars are the part that worries me most, because they are the thing an agent copies. One called a button variant the gallery's own button does not have, one imported a type core does not export, and the settings form bound .value on a native input, which SSR drops. That last one meant it rendered four empty fields and lost everything typed on a validation failure, which is the exact behaviour the file's own header claims to demonstrate.

The website findings are worse than they look. website/public/input.css @sources the mirrored registry but never learned any of the new tokens, so every new utility the kit emits compiled to nothing on the deployed gallery. It also still carried the old --destructive-foreground, which means the 1.65:1 contrast bug this branch advertises as fixed was live on webjs.dev, and swapping button.ts to the token would have shipped it there rather than fixing it.

The header test needed a look too: its block terminator could not match A11y (, so for a good chunk of the kit the assertion was passing on A11y prose instead of on design content.

Note the inline anchors point at the fixed lines rather than the broken ones, since the repairs are already on the branch.

Comment thread gallery/app/examples/dashboard/page.ts
Comment thread gallery/modules/settings/actions/save-settings.server.ts Outdated
Comment thread gallery/app/examples/settings/page.ts Outdated
Comment thread website/public/input.css
Comment thread website/public/input.css
Comment thread packages/ui/test/component-headers.test.js
Comment thread gallery/app/layout.ts Outdated
…d fill

The delta round found that the contrast change regressed the very component
it claimed to fix, and that the claim behind it was wrong.

--destructive-foreground is built for a SOLID --destructive fill, where the
dark value measures 5.58:1. button.ts does not paint a solid fill in dark:
dark:bg-destructive/60 composites red-400 at 60% over the page, and against
that the token measures 2.49:1 while the text-white it replaced measures
6.48:1. So the token was exactly wrong on the one component it looked most
correct on, and badge.ts kept text-white on the identical shape, so the two
disagreed. button.ts goes back to text-white with the measurement written
beside it, and a test pins both components against the token.

The claim was wrong too. No registry component read --destructive-foreground
before this branch, so the 1.65:1 pair was latent rather than shipped. It was
this branch that would have put it on screen. The token correction is still
right, since it makes the pair safe for app code writing bg-destructive with
text-destructive-foreground, but it fixes a latent bug rather than a live one,
and both theme files now say so.

ActionResult is exported, from @webjsdev/server rather than core, and eight
sibling actions in the same app already import it that way. The local
re-declaration is gone. It was checked against core alone and the comment
asserting the type did not exist was simply false.

Also: the new page-header component's example still showed the outline variant
the gallery's button does not have, so a paste of it would not compile.
aria-describedby was emitted empty on every field with no hint and no error,
which is an IDREF list resolving to nothing rather than an absent attribute,
so the template branches now. The gallery layout comment I rewrote replaced an
accurate note with an inaccurate one, since nothing in that app renders text on
a destructive fill. And --color-success was declared twice in one @theme block.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second round, scoped to the fix commit and its blast radius. It caught something I got badly wrong the first time, so this one is worth reading even though the diff is small.

The contrast change regressed the exact component it advertised as fixed. --destructive-foreground is built for a solid --destructive fill, but button.ts paints dark:bg-destructive/60 in dark, a composite rather than a solid. Against what that button actually renders, the token measures 2.49:1 and the text-white I removed measures 6.48:1. So pointing the button at the token pushed it from comfortably passing to well under the floor, on the one preview where anyone would look. badge.ts kept text-white on the identical shape, so the two disagreed as well.

The claim behind it was wrong too, and this is the part I want on the record: no registry component read --destructive-foreground before this branch, so the 1.65:1 pair was latent, never rendered. The PR presented it as a live shipped bug. Correcting the token is still right, because it makes the pair safe for app code that writes bg-destructive text-destructive-foreground, but it fixes a latent defect rather than a live one and the PR body said otherwise.

ActionResult is exported after all, from @webjsdev/server rather than core, and eight sibling actions in the same app already import it that way. I checked core alone and wrote a comment asserting the type did not exist.

The rest are smaller: the new page-header's example still showed a variant the gallery's button does not have, aria-describedby was emitted empty on fields with no hint and no error, the layout comment I rewrote traded an accurate note for an inaccurate one, and --color-success ended up declared twice in one theme block.

Comment thread packages/ui/packages/registry/components/button.ts
Comment thread gallery/modules/settings/actions/save-settings.server.ts
Comment thread gallery/components/ui/page-header.ts
Comment thread gallery/app/examples/settings/page.ts
Comment thread gallery/app/layout.ts
Comment thread gallery/public/input.css
…e near it

The previous commit retracted a false claim about half a token pair and left
the other half asserting the opposite. The dark comment said the bad value was
latent and never rendered; the light comment, describing the same never-read
token seventy lines above, still said the kit shipped it. Neither value was
ever rendered, since button.ts and badge.ts carry text-white in both modes, and
both comments now say so. The website theme had the same split and stated it
explicitly, scoping the latency to the dark pair, which asserted by contrast
that the light one was live.

The test added with that commit was worse than the bug it guarded. Its primary
finder matched no line in either file, so every assertion ran through a looser
fallback that scanned raw source including comments, and it passed only because
no single comment line happened to carry both a bg-destructive and a text-
utility. A comment mentioning the two together, which the theme file three
directories away already contains, would have failed a correct variant; one
worded the other way would have passed a real regression. It now reads the
comment-blanked source like every other test in the file and anchors on the
VARIANTS key, so it can only ever match the real variant. Both directions are
counterfactualled: the regression fails it, and adding the trap comment does
not.

The text-white exception was recorded only in a source comment while three doc
surfaces this branch adds teach the pairing rule it breaks. All three now carry
it, phrased as the general rule rather than the special case: a -foreground
token is measured against its own solid fill, so diluting that fill with an
opacity modifier means re-measuring against the composite. The raw-palette scan
cannot see text-white, since it matches only palette families with a numeric
step, which is why the pin is a test rather than a convention.

design.md also taught the registry's variant set as if it were universal. The
kit is copied into a user's repo and edited there, so it now says to check the
local button rather than assume the names.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Third round, scoped to the previous fix commit. It found that my correction was half-applied and that the test I added to lock it in was unsound.

The retraction only landed on the dark half. The dark comment said the bad value was latent and never rendered; the light comment, describing the same never-read token seventy lines above, still said the kit shipped it. The website theme had the same split and made it explicit by scoping the latency to the dark pair, which asserts by contrast that the light one was live. Neither was: button and badge carry text-white in both modes, so nothing ever rendered either value.

The test was the worse half. Its primary finder matched no line in either file, so every assertion fell through to a looser one that scanned raw source including comments. It passed only because no single comment line happened to carry both a bg-destructive and a text- utility, and the theme file three directories away already contains that exact sentence. A comment worded one way would have failed a correct variant, and worded the other way would have passed a real regression. So the guard against the regression was itself decided by prose near the code rather than by the code.

Last one is a doc gap rather than a defect: the text-white exception lived only in a source comment, while three surfaces this branch adds teach the pairing rule it breaks.

Comment thread packages/ui/packages/registry/themes/index.css
Comment thread website/public/input.css
Comment thread packages/ui/test/no-raw-palette.test.js
Comment thread .agents/skills/webjs/references/design.md Outdated
The light-mode comment on --destructive-foreground was self-contradictory on
its own terms. It called button.ts and badge.ts "the only two consumers" of a
token whose whole point in this branch is that neither reads it. Read as
consumers of --destructive instead it was still wrong, since fourteen
components read that one. It now states the fact the way the dark comment and
the website theme already do: no registry component read the token, so neither
value was ever rendered.

The claim that text-white is "the one place in the kit a non-token colour is
correct" was also wrong. dialog.ts and alert-dialog.ts both carry
backdrop:bg-black/50, because a scrim is black in both themes and has no token
to be, and it is invisible to the raw-palette scan for exactly the reason the
same paragraph cites two sentences later. The skill reference framed it the
same way, directly under a rule that says never a hex, which is where an app
author meets the scrim question first.

Both surfaces now say the actual rule: a colour is a token whenever the theme
should be able to move it, which is almost always, rather than a literal never
being right.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fourth round. Two factual claims I added in the previous commit were wrong, both in prose rather than code.

The light-mode comment was self-contradictory on its own terms: it called button and badge "the only two consumers" of a token whose whole point in this branch is that neither reads it. Read as consumers of --destructive instead it is still wrong, since fourteen components read that. The dark comment and the website theme both already stated the fact correctly, so the one I "fixed" was the only one of the three still saying something untrue.

And text-white is not the one place a literal colour is correct here. dialog.ts and alert-dialog.ts carry backdrop:bg-black/50, because a scrim is black in both themes and has no token to be. That sentence also undercut its own justification, since the scrim is invisible to the raw-palette scan for exactly the reason the same paragraph gives two sentences later for why the pin has to be a test. The skill reference framed it the same way, sitting directly under a rule that says never a hex, which is where an app author meets the scrim question first.

Both now state the actual rule: a colour is a token whenever the theme should be able to move it, which is almost always, rather than a literal never being right.

Comment thread packages/ui/packages/registry/themes/index.css
Comment thread packages/ui/AGENTS.md
Comment thread .agents/skills/webjs/references/design.md

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline anchors for the round above, which posted without them.

Comment thread packages/ui/packages/registry/themes/index.css
Comment thread packages/ui/AGENTS.md
Comment thread .agents/skills/webjs/references/design.md

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction to the review above it: that one says it carries inline anchors and it does not. GitHub accepted both attempts and attached no comments, so the three findings are path-anchored here instead.

packages/ui/packages/registry/themes/index.css, the light --destructive-foreground comment. It called button and badge "the only two consumers" of a token neither of them reads, which is the fact the surrounding change exists to establish. Read as a claim about --destructive consumers it is wrong too, since fourteen components read that one.

packages/ui/AGENTS.md, the token-layer section. It called text-white the one place a non-token colour is correct in the kit. dialog.ts and alert-dialog.ts both carry backdrop:bg-black/50, and that literal is invisible to the raw-palette scan for exactly the reason the same paragraph gives two sentences later for why the text-white pin has to be a test.

.agents/skills/webjs/references/design.md, the colour section. Same overstatement, and it sat directly under "Every colour is a token. Never a hex", which is where an app author meets the scrim question first.

All three are fixed in the commit this review points at. Both surfaces now state the rule as: a colour is a token whenever the theme should be able to move it, which is almost always, rather than a literal never being right.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fifth round, scoped to the previous fix commit and its blast radius. Nothing to report: the claims that commit now makes check out against the code, and no other surface still states the old version of either. First round on this branch that produced no fixes.

That buys the final pass over the whole diff, which is running now.

…itives

The final whole-diff review found that the dark-mode elevation story this
branch documents in four places was not implemented. --surface-1/2/3 were
declared in all four theme files, mapped, and read by nothing at all, while
design.md, packages/ui/AGENTS.md, the docs site and the theme comment each
asserted that the tokens handle the dark inversion for you. What actually
shipped was the opposite: a dark shadow five to six times stronger than the
light one, which design.md itself calls reading as a hole.

The lift was already there. --card and --popover sit at 0.205 against a 0.145
--background, so a raised surface is lighter than the page through tokens the
theme has always had. The surface tokens added nothing and are gone rather than
left as dead configuration, the dark shadow is moderated to 0.3 and 0.4, and
all four surfaces now describe the mechanism that exists.

The six new primitives were missing from V1_COMPONENTS, so none of them was
covered by the guards for a complete, parser-safe, extractable example, while
uiInventory, webjsui view and the MCP ui tool all served them. One would have
failed the moment it was covered: empty-state's example carried an elided
comment, which is exactly what the guard bans. They were also absent from the
website's example map, so all six rendered the no-live-preview fallback on the
public gallery. The earlier claim that all six pages render was true and did
not mean what it read as.

The settings exemplar lost its six notification toggles on every submit, in a
file whose own header says losing typed input is what makes people abandon a
form. An unchecked box submits nothing, so the action now reads the present
keys back and the page restores them. They ride as one comma-joined field
because the envelope types values as a string map, which is what a form
actually submits.

The runbook could not be run as written. It cut both sides from main, which
after this branch moved the gate pre-merge is the ref that carries no guidance
at all, and its worktree command would fail against a ref already checked out.
It now takes the ref as a variable, and its revert step names all three routing
surfaces rather than two, since the cheat-sheet rows are paired against the
gallery demos by a repo-health test.

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final pass over the whole diff. Six findings, and the first one is the kind I would want caught before merge rather than after.

The dark-mode elevation story was documented in four places and not implemented. --surface-1/2/3 were declared in all four theme files, mapped into @theme, and read by nothing whatsoever, while design.md, packages/ui/AGENTS.md, the docs styling page and the theme comment each stated that the tokens handle the dark inversion for you. What shipped was the opposite of the claim: a dark shadow five to six times stronger than the light one, which design.md itself describes as reading like a hole. The lift was already there through --card and --popover sitting above --background, so the tokens added nothing. They are gone, the dark shadow is moderated, and the four surfaces describe what exists.

Seven form controls moved up an elevation level unverified. shadow-xs to shadow-e1 roughly doubles the light shadow and adds a second layer, and the role scale has no level below raised, so the rename could not preserve the input-versus-card distinction. Moderating the dark values takes the worst of it out; the consolidation itself is deliberate and is now stated rather than implied.

The six new primitives were outside every manifest guard. Absent from V1_COMPONENTS, so nothing checked their examples were complete and extractable, while uiInventory, webjsui view and the MCP ui tool all served them. empty-state would have failed the moment it was covered, since its example carried an elided comment, which is the specific thing that guard bans.

And all six rendered the no-live-preview fallback on the public gallery, because none had an entry in the website example map. My test plan said all six /ui/<name> pages render, which was true and did not mean what it read as.

The settings exemplar lost its six toggles on every submit, in a file whose header says losing typed input is what makes people abandon a form. An unchecked box submits nothing, so nothing read them back.

The runbook could not be run. It cut both sides from main, which is the ref carrying no guidance now that the gate is pre-merge, and its worktree command fails against an already-checked-out ref.

All six fixed in da38b248. Verified: 541 tests green, both stylesheets rebuilt with the surface tokens gone and shadow-e1 still emitting, all six /ui pages now serving a real preview, and the settings form rendering its default toggles checked.

The fix-check found that the six previews added last commit rendered with
EMPTY class attributes. evalHole resolves each hole against a HELPERS map and
fails soft to an empty string, none of the 27 new helpers was in that map, and
the only assertion covering them was that the page returns 200. So they shipped
as unstyled markup inside a styled shell, which is the defect the commit
claimed to fix. The helpers are imported and registered, and a new test asserts
a preview pane carries real classes rather than that its route answers.

Also from the fix-check: the packages/ui prose lost the sentence naming the
cn() classifier, leaving two pronouns with no antecedent; the dark elevation
values diverged across the three theme copies while a comment asserted they are
identical; the gallery layout comment still explained the surface tokens the
previous commit deleted; root AGENTS.md was the last surface still stating the
retracted lighter-surface claim; and NOTIFICATION_KEYS was a non-function export
from a 'use server' file, which is invariant 2, duplicated by hand in the page
it existed to serve. The list moved to a browser-safe types module both sides
import, so renaming a field can no longer break the restore silently.

The deferred gallery audit is done rather than recorded. It went from 72 hits to
zero, and roughly a third of that was the scorer being wrong rather than the
payload. Five false-positive classes are fixed: rule 9 scanned whole files
rather than html templates, so a robots.txt line and a cache key counted as
screen output; its attribute check treated the > in an arrow function as a
closing tag; global-error and global-not-found were charged for literal colours
when they render their own document with no stylesheet to token against; the
heading rule flagged zero h1 on pages whose heading comes from a helper; and an
arbitrary text colour counted as an arbitrary font size.

The rest was real: seventeen off-scale widths, thirty-six off-scale type sizes,
two lists with no empty branch (the frames filter can match nothing, and
gallery:clear empties the home), and five label-colon-value captions, which the
design reference itself says are usually unnecessary.

Invariant 9 caught me a second time on the way through, a backtick inside an
html template body in a CSS comment this time.
…n the inventory

Four findings from a review pass, all real.

design-depth.md told agents to run `webjsui init --accent <name>`. There is no
such option: init takes --base-color, which picks the NEUTRAL ramp, and the
accent is set by editing the theme block the kit copies into your repo. The
recommended workflow failed on the first command. It now describes what exists,
and says why a shipped accent default would be wrong rather than treating the
absence as a gap.

references/ui-kit.md claims to be the inventory and listed none of the six new
primitives, so an agent loading that reference would not discover them and would
hand-roll an empty state instead. All six are listed, with a line each on the
job they do, since the inventory is where the discovery happens.

The scorer counted an issue reference as a literal colour. Blanking comments was
not enough, because the same reference appears in a metadata description string,
so `(#492)` in the blog scored as a hex. A hex now only counts in a colour
POSITION, after a colon or a comma, which is where a CSS declaration and a
gradient stop put one. The blog goes from thirteen literal-colour hits to two,
and both survivors are real.

The scaffold gallery test covered only the todo example, so the dashboard and
settings exemplars were unverified in both directions: that a generated app
carries them, and that gallery:clear strips them. That second half is exactly
the regression this PR already shipped once. Removing settings from the clear
list now fails the test.
@vivek7405
vivek7405 marked this pull request as ready for review August 15, 2026 04:28
@vivek7405
vivek7405 merged commit 09f8134 into main Aug 15, 2026
10 checks passed
@vivek7405
vivek7405 deleted the feat/ui-design-first-class branch August 15, 2026 04:35
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Reverts 09f8134 in full. The tree is byte-identical to the commit before it,
produced by git revert rather than by hand so nothing is left behind.

The token layer was the part that looked most defensible and it is the part
that settles this. @webjsdev/ui exists for shadcn parity, and measured against
the shadcn clone every token change in that PR moved away from it. shadcn
ships no --success, --warning or --info, so those twelve tokens were an
invention. It uses Tailwind's stock shadow scale at the call site, so renaming
seventeen usages to a shadow-e1 through shadow-e4 role scale was churn against
parity, and it forced a cn() classifier change in three copies to support a
name shadcn does not have.

The destructive contrast fix is the sharpest case. shadcn's own values are
byte-identical to what the kit already had, light oklch(0.97 0.01 17) and dark
oklch(0.58 0.22 27), and shadcn's button never reads the token: it carries
text-white, exactly as this kit did. So the pair that PR called a live bug is
upstream parity, latent by design on both sides, and the change would have put
a real regression on the button until review caught it.

The rest goes for reasons that were visible earlier and that I did not act on.
The six primitives each composed from helpers the kit already had. The two
example screens were single static screens listed beside a real composed app.
The design reference was explicitly the unproven half and its owner-run gate
never ran, so it shipped measuring nothing, and the evaluation harness exists
only to run that gate. The header blocks carry the same unproven premise into
every user's repo. The gallery payload rescaling edited working demos to
satisfy a rubric that PR introduced.

One genuine parity gap surfaced on the way and is NOT addressed here, since
adding it inside a revert would be the same mistake again: shadcn ships
--surface, --surface-foreground, --code, --code-foreground and --selection,
and this registry theme has none of them.
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Reverts 09f8134 in full. The tree is byte-identical to the commit before it,
produced by git revert rather than by hand so nothing is left behind.

The token layer was the part that looked most defensible and it is the part
that settles this. @webjsdev/ui exists for shadcn parity, and measured against
the shadcn clone every token change in that PR moved away from it. shadcn
ships no --success, --warning or --info, so those twelve tokens were an
invention. It uses Tailwind's stock shadow scale at the call site, so renaming
seventeen usages to a shadow-e1 through shadow-e4 role scale was churn against
parity, and it forced a cn() classifier change in three copies to support a
name shadcn does not have.

The destructive contrast fix is the sharpest case. shadcn's own values are
byte-identical to what the kit already had, light oklch(0.97 0.01 17) and dark
oklch(0.58 0.22 27), and shadcn's button never reads the token: it carries
text-white, exactly as this kit did. So the pair that PR called a live bug is
upstream parity, latent by design on both sides, and the change would have put
a real regression on the button until review caught it.

The rest goes for reasons that were visible earlier and that I did not act on.
The six primitives each composed from helpers the kit already had. The two
example screens were single static screens listed beside a real composed app.
The design reference was explicitly the unproven half and its owner-run gate
never ran, so it shipped measuring nothing, and the evaluation harness exists
only to run that gate. The header blocks carry the same unproven premise into
every user's repo. The gallery payload rescaling edited working demos to
satisfy a rubric that PR introduced.

One genuine parity gap surfaced on the way and is NOT addressed here, since
adding it inside a revert would be the same mistake again: shadcn ships
--surface, --surface-foreground, --code, --code-foreground and --selection,
and this registry theme has none of them.
@vivek7405

Copy link
Copy Markdown
Collaborator Author

Reverted in full by #1417

This is reverted on main as of 948d8555. The tree is byte-identical to the commit before it.

The token layer is what settled it. @webjsdev/ui exists for shadcn parity, and measured against the shadcn source every token change here moved away from it: shadcn ships no --success, --warning or --info, and it uses Tailwind's stock shadow scale at the call site rather than a role-named one, so the shadow-e1 through shadow-e4 rename was churn that also forced a cn() classifier change in three copies to support a name shadcn does not have.

The destructive contrast fix is the sharpest case and the one worth remembering. shadcn's own values are byte-identical to what this kit already had, light oklch(0.97 0.01 17) and dark oklch(0.58 0.22 27), and shadcn's button carries text-white rather than reading the token, exactly as this kit did. So the 1.65:1 pair this PR called a live shipped bug is upstream parity, latent by design on both sides. Pointing the button at the corrected token would have dropped it to 2.49:1 in dark, which is a real regression introduced by a fix for a defect nobody had.

The rest went for reasons that were visible while it was being built. The six primitives each composed from helpers already in lib/utils.ts, to the point that fieldGroupClass() was stackClass({ gap: 4 }) and inputGroupTextClass() was byte-identical to hintClass(). The two example screens were single static screens over derived in-memory data, listed beside a real composed app with a database. The design reference was the explicitly unproven half and its owner-run gate never ran, so it shipped measuring nothing, and the evaluation harness exists only to run that gate. The header blocks carried the same unproven premise into every consumer's repo through webjsui add. The gallery rescaling edited working demos to satisfy a rubric this PR introduced.

What is worth keeping from it, for whoever picks this up later. Nothing in the diff, but two findings.

The first is a real parity gap in the opposite direction: shadcn ships --surface, --surface-foreground, --code, --code-foreground and --selection, and this registry theme has none of them. That is a small parity-correcting addition, and it is deliberately not part of the revert.

The second is the process lesson. The plan in #1116 was treated as authority rather than as a proposal, so each phase was built because the plan listed it rather than because it earned its place. Parity should have been the first question asked of the token layer, not the last.

vivek7405 added a commit that referenced this pull request Aug 15, 2026
Clears every package carrying unreleased user-facing work since 0.7.50.

core adds refreshPage, which re-renders the current url on the server and
applies it in place with no reload, no history entry and no scroll, and
prefetches a link that drives a frame in that frame's own dimension so the
click costs no round trip. The fix records a same-document history entry
before the swap rather than after, which is what made an iOS swipe-back
preview the page the user was already on.

server stops a dev edit to a page or layout reloading the browser, so hydrated
state and scroll survive a save, and marks a sliced frame response so the
client can tell a real subtree from a whole document. The fixes render an
error boundary inside its own layout chain, which makes navigating into a
failing page a soft navigation with the chrome intact rather than a full page
load, coalesce a burst of dev reload signals so rapid edits stop leaving the
page unstyled, and root the vendor specifier scan in the module graph, where
21 of the 24 specifiers the old whole-directory scan found in website were
phantom.

ui takes the a11y fix alone, measured against Chrome's computed accessibility
tree: two nested dialog nodes collapse to one, and an ordinary toast stops
resolving under two live roots.

Three packages are deliberately absent. cli, mcp and intellisense carry no
user-facing change in the range. cli's only such commit was #1414, which #1417
reverted in full, so what remains is the doctor barrel split, an internal move
with an unchanged export surface. mcp picked up three doc-string edits from
the same refactor, naming the new sibling-directory paths its `source` tool can
read, and the paths it already documents still resolve because they are
barrels. intellisense picked up nothing at all.

Raises packages/server's declared @webjsdev/core range from ^0.7.50 to
^0.7.51. The dev refresh reads refreshPage through a runtime feature check
rather than a static import, so nothing breaks without it, but the release PR
is the only place that bump is legal and core carries the earliest date in the
batch so it publishes first.

The generated notes were curated before committing. The generator reads
conventional-commit prefixes and cannot see a revert, so it wrote #1414 into
ui's entry for work no longer on main; that entry is removed. Each remaining
excerpt was the first lines of a squashed commit message, which for the
multi-commit squashes opened on a test or docs sub-commit rather than on the
change itself.
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Clears every package carrying unreleased user-facing work since 0.7.50.

core adds refreshPage, which re-renders the current url on the server and
applies it in place with no reload, no history entry and no scroll, and
prefetches a link that drives a frame in that frame's own dimension so the
click costs no round trip. The fix records a same-document history entry
before the swap rather than after, which is what made an iOS swipe-back
preview the page the user was already on.

server stops a dev edit to a page or layout reloading the browser, so hydrated
state and scroll survive a save, and marks a sliced frame response so the
client can tell a real subtree from a whole document. The fixes render an
error boundary inside its own layout chain, coalesce a burst of dev reload
signals so rapid edits stop leaving the page unstyled, and root the vendor
specifier scan in the module graph.

ui takes the a11y fix alone.

Three packages are deliberately absent. cli, mcp and intellisense carry no
user-facing change in the range. cli's only such commit was #1414, which #1417
reverted in full, so what remains is the doctor barrel split, an internal move
with an unchanged export surface. mcp picked up three doc-string edits from
the same refactor, naming the new sibling-directory paths its `source` tool can
read, and the paths it already documents still resolve because they are
barrels. intellisense picked up nothing at all.

Raises packages/server's declared @webjsdev/core range from ^0.7.50 to
^0.7.51. The dev refresh reads refreshPage through a runtime feature check
rather than a static import, so nothing breaks without it, but the release PR
is the only place that bump is legal and core carries the earliest date in the
batch so it publishes first.

core and server carry the generated notes verbatim. ui is edited twice, both
because the generator reads conventional-commit prefixes and cannot see
anything else. It wrote #1414 into the entry for work #1417 has since reverted,
which is removed along with the Features section it emptied; and its excerpt is
the first lines of a squashed message, which for #1412 opened on the CDP
measurement instrument rather than on the fix, so the fix sub-commit takes its
place.
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Clears every package carrying unreleased user-facing work since 0.7.50.

core adds refreshPage, which re-renders the current url on the server and
applies it in place with no reload, no history entry and no scroll, and
prefetches a link that drives a frame in that frame's own dimension so the
click costs no round trip. The fix records a same-document history entry
before the swap rather than after, which is what made an iOS swipe-back
preview the page the user was already on.

server stops a dev edit to a page or layout reloading the browser, so hydrated
state and scroll survive a save, and marks a sliced frame response so the
client can tell a real subtree from a whole document. The fixes render an
error boundary inside its own layout chain, coalesce a burst of dev reload
signals so rapid edits stop leaving the page unstyled, and root the vendor
specifier scan in the module graph.

ui takes the a11y fix alone.

Three packages are deliberately absent. cli, mcp and intellisense carry no
user-facing change in the range. cli's only such commit was #1414, which #1417
reverted in full, so what remains is the doctor barrel split, an internal move
with an unchanged export surface. mcp picked up three doc-string edits from
the same refactor, naming the new sibling-directory paths its `source` tool can
read, and the paths it already documents still resolve because they are
barrels. intellisense picked up nothing at all.

Raises packages/server's declared @webjsdev/core range from ^0.7.50 to
^0.7.51. The dev refresh reads refreshPage through a runtime feature check
rather than a static import, so nothing breaks without it, but the release PR
is the only place that bump is legal and core carries the earliest date in the
batch so it publishes first.

core and server carry the generated notes verbatim. ui is edited twice, both
because the generator reads conventional-commit prefixes and cannot see
anything else. It wrote #1414 into the entry for work #1417 has since reverted,
which is removed along with the Features section it emptied; and its excerpt is
the first lines of a squashed message, which for #1412 opened on the CDP
measurement instrument rather than on the fix, so the fix sub-commit takes its
place.
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Ships the scaffold audit fix from #1419, so that `npm create webjs` stops
emitting apps that report 5 high-severity advisories on their first
install. The fix only reaches users through a published `@webjsdev/cli`,
since `create-webjs` resolves it from the registry.

The changelog is hand-written rather than generated. The generator
matches conventional subjects in the package tree, and the unreleased
log for `@webjsdev/cli` also carries the `feat:` subject from #1414,
whose work was fully reverted by #1417. That revert landed with a
`revert:` prefix the generator does not match, so an auto-generated
entry would have advertised a feature that is not in the code.
@vivek7405 vivek7405 mentioned this pull request Aug 15, 2026
4 tasks
vivek7405 added a commit that referenced this pull request Aug 15, 2026
Ships the scaffold audit fix from #1419, so that `npm create webjs` stops
emitting apps that report 5 high-severity advisories on their first
install. The fix only reaches users through a published `@webjsdev/cli`,
since `create-webjs` resolves it from the registry.

The changelog is hand-written rather than generated. The generator
matches conventional subjects in the package tree, and the unreleased
log for `@webjsdev/cli` also carries the `feat:` subject from #1414,
whose work was fully reverted by #1417. That revert landed with a
`revert:` prefix the generator does not match, so an auto-generated
entry would have advertised a feature that is not in the code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make WebJs UI and the AI design guidance first class, grounded in Refactoring UI

1 participant