Files
WRNexusJS/docs/ui-library-audit-2026-08-08.md
T
ClintchizandClaude Opus 5 69020b2555
Quality / quality (ubuntu-latest) (push) Failing after 10m7s
Quality / quality (windows-latest) (push) Canceled after 0s
docs: make the component sections executable in one pass
Expands 3.1 and 3.2 so the work can be done without re-deriving anything.

3.1 now records what 0.8.6 already fixed, separated into the ten components
that were miswired and the five that gained outputs they had been firing
undeclared, with the caveat that Map's three were converted but never confirmed
in a browser. For the 22 that remain it adds the finding that changes the
decision: all nine are pure scaffolds with no state, functions or handlers, and
five of them duplicate a component that already works -- FileUpload against
FileInput and FileUploadProgress, Toast and ToastNotifications against Toaster,
AdvancedDatePicker against DatePicker, AdvancedRangeSlider against RangeSlider.
Superseding those is a migration entry rather than new code, and leaves Chart,
TreeView, Confetti and CopyMarkup as the only ones needing to be built.

3.2 corrects the scaffold count from 23 to 28; the earlier figure used a looser
rule. Nine of the 28 are the 3.1 components, so the two items must be planned
together, and several of the rest are primitives that need only their styles
moved out of ui.css rather than any behaviour.

Also corrects the dead-output component count from 11 to 9 in both documents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:34:22 +05:30

119 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI library audit — 2026-08-08
A measured pass over `@wrnexus/ui` after the navigation and layout groups were
rebuilt. Numbers come from the source and from a browser, not from estimates.
## Fixed in this pass
**Ten theme tokens were referenced and never defined.** An undefined custom
property does not warn — it resolves to nothing — so the styles that used them
silently did nothing:
| Token | Effect while undefined |
| ----------------------------- | --------------------------------------------------------- |
| `--wire-color-focus` | Focus rings drew with no colour, in 14 components |
| `--wire-color-surface-soft` | Every soft surface rendered transparent, in 27 components |
| `--wire-color-on-danger` | Text on danger fills had no colour |
| `--wire-color-surface-subtle` | Subtle panels rendered transparent |
| `--wire-color-input-*` (6) | Input backgrounds, borders and placeholders unstyled |
| `*-hover`, `*-contrast` | Missing for info, success, warning and danger |
They are derived in the theme now, so they follow the palette and the accent.
Verified in a browser: 38 tokens referenced, **0 undefined** (was 10).
Two mistakes worth recording, because both nearly became wrong conclusions:
- A first version of the check read the theme _source_ and reported a dozen
false positives, because most tokens are generated per palette rather than
written as literals. The check now reads the **rendered** theme CSS.
- The semantic token spread sat _after_ the primary and secondary palette
entries, so it would have overridden them. It now sits before.
## Correction: outputs, and what was actually wrong
The first version of this audit called the LayoutSplitter output failure
"narrow and unexplained" and treated 32 dead outputs as 16 components needing a
rebuild. Both were wrong, and the real cause was found by binding a page
handler to a component in a browser and watching what arrived.
**Two distinct faults were hiding behind one symptom.**
_Emitting the wrong way._ An output only reaches a parent `@binding` when the
component calls `output.<name>()`. The runtime resolves parent handlers from a
registry that only `invokeComponentOutput` reads, so a component that builds
its own `CustomEvent` — even a bubbling one, dispatched on its own root —
emits into nothing. Eighteen components did exactly that, so their outputs were
declared, documented, apparently fired, and never delivered. Converting them
took ten dead outputs off the list and cost no new machinery.
_Case._ HTML lowercases attribute names, so a parent's `@sizeChange` registers
under `sizechange` while the component emits `sizeChange`. The lookup missed,
fell through to a DOM dispatch, and the binding was never invoked — silently,
with no error at either end. **All 17 camelCase outputs in the library were
undeliverable**, including `DataTable.pageChange` and `DataTable.rowClick`,
`Map.markerClick`, `ChatBubble.messageClick` and `LayoutSplitter.sizeChange`.
The runtime now falls back to a case-insensitive lookup, and a test in
`packages/csr` fails without that fallback.
The lesson worth keeping: "outputs work elsewhere" was an assumption, not a
measurement. The components that worked happened to use all-lowercase names and
`output.*`; that coincidence made a framework-wide bug look like one broken
component.
## Outstanding, with numbers
**22 outputs across 9 components have no emitter of any kind** (was 32 across
16; see the correction above). The component advertises an output, a caller
binds to it, and nothing ever fires. Native event names are excluded — the
runtime binds a DOM-listener fallback on component tags, so `click` and `input`
do arrive.
| Component | Dead outputs |
| ------------------- | ----------------------------------------- |
| FileUpload | upload, progress, success, cancel, remove |
| ToastNotifications | add, dismiss, clear, action |
| AdvancedDatePicker | open, close, clear |
| AdvancedRangeSlider | start, end |
| Chart | dataPointClick, legendToggle |
| Confetti | start, complete |
| TreeView | expand, collapse |
| Toast | dismiss |
| CopyMarkup | success |
A test pins this at 22 as a ceiling that only moves down.
**28 components are still scaffolds** — no style block, no functions and no
handlers. Nine of them also declare outputs nothing emits. (An earlier figure of
23 here used a looser rule; see §3.2 of the remediation plan.)
These are the same shape as the Table scaffold that was removed and the
LayoutSplitter scaffold that was rebuilt.
**66 of 108 components have no local style block**, so they depend on
`ui.css`. That matters for delivery: a migrated component ships its CSS only
when it renders, while `ui.css` is served whole to every page — 175,848 bytes,
**25,908 gzipped**. Migrating the remainder would shrink it toward nothing.
**10 components still use Tailwind utilities**: FeatureIconCard, HeroActions,
InputNumber, List, Map, Marquee, SearchBox, TextLink, Timeline, button.
## Not worth doing
**Splitting the runtime into chunks.** Measured rather than assumed:
`reactive.js` is 69,947 bytes minified, roughly 21 kB gzipped, served as its
own file with a one-year immutable cache. Component-specific controllers are
about 18% of it, so splitting them out saves 34 kB gzipped on a first visit
and nothing after that. That is a poor return for lazy-loading complexity.
Hydration is not a bottleneck either: 21 scopes across 4,325 elements rehydrate
in 1.5 ms, and the shared document observer cost 0.9 ms across three seconds of
the busiest activity in the library.
## Known framework defects
- **A component tag nested inside another component slot is dropped**, leaving
only its children.
- **Component props and slot content render once** and do not track page state,
so the controlled-component pattern does not work.
- **i18n text is not server-rendered.** Nav links ship as empty spans and are
filled by the client, so navigation is blank until the runtime loads.