@kerfjs/ui 5.0.0-beta.53 → 5.0.0-beta.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog-v2.json +82 -6
  4. package/ai/component-catalog-v2.schema.json +2 -1
  5. package/ai/component-catalog.json +105 -10
  6. package/ai/public-api-signatures-v1.md +204 -18
  7. package/ai/skill.md +23 -17
  8. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  9. package/analyzer/index.mjs +48 -0
  10. package/analyzer/loud-pairs.d.mts +49 -0
  11. package/analyzer/loud-pairs.mjs +357 -0
  12. package/dist/app-tab.d.ts +7 -5
  13. package/dist/app-tab.js +1 -1
  14. package/dist/{chunk-EKYZCRCX.js → chunk-3CGQ7ZNO.js} +54 -22
  15. package/dist/chunk-3CGQ7ZNO.js.map +1 -0
  16. package/dist/{chunk-UKKSMZYT.js → chunk-46263QLK.js} +51 -1
  17. package/dist/chunk-46263QLK.js.map +1 -0
  18. package/dist/{chunk-NDUGJ4TU.js → chunk-5NDQABWK.js} +1 -1
  19. package/dist/{chunk-NDUGJ4TU.js.map → chunk-5NDQABWK.js.map} +1 -1
  20. package/dist/{chunk-CCMKJWLX.js → chunk-NE5JAOQS.js} +7 -7
  21. package/dist/chunk-NE5JAOQS.js.map +1 -0
  22. package/dist/chunk-PSHUSWQH.js +18 -0
  23. package/dist/chunk-PSHUSWQH.js.map +1 -0
  24. package/dist/{chunk-VOCPXGQN.js → chunk-VHADGP3B.js} +9 -8
  25. package/dist/chunk-VHADGP3B.js.map +1 -0
  26. package/dist/chunk-XQV2B4IL.js +14 -0
  27. package/dist/chunk-XQV2B4IL.js.map +1 -0
  28. package/dist/collapsible-panel.d.ts +6 -2
  29. package/dist/collapsible-panel.js +1 -0
  30. package/dist/collapsible-panel.js.map +1 -1
  31. package/dist/floating-toolbar.d.ts +5 -2
  32. package/dist/floating-toolbar.js +1 -1
  33. package/dist/index.js +6 -5
  34. package/dist/resizable-region.d.ts +4 -1
  35. package/dist/resizable-region.js +1 -1
  36. package/dist/split-view.js +1 -1
  37. package/dist/styles/collapsible-panel.css +88 -10
  38. package/dist/styles/floating-toolbar.css +11 -6
  39. package/dist/styles/resizable-region.css +81 -13
  40. package/dist/styles/workbench.css +472 -10
  41. package/dist/wire-resizable-regions.d.ts +2 -1
  42. package/dist/wire-resizable-regions.js +2 -1
  43. package/dist/wire-tab-bars.js +1 -1
  44. package/dist/wire-workbench.d.ts +109 -0
  45. package/dist/wire-workbench.js +355 -0
  46. package/dist/wire-workbench.js.map +1 -0
  47. package/dist/workbench.d.ts +65 -6
  48. package/dist/workbench.js +180 -76
  49. package/dist/workbench.js.map +1 -1
  50. package/docs/accessibility.md +54 -9
  51. package/docs/app-layouts.md +12 -1
  52. package/docs/collapsible-panel.md +59 -1
  53. package/docs/component-selection.md +16 -6
  54. package/docs/document-baseline.md +4 -0
  55. package/docs/layout.md +5 -1
  56. package/docs/recipes.md +5 -3
  57. package/docs/type-contracts.md +2 -1
  58. package/docs/ui-analyzer.md +94 -19
  59. package/docs/ui-doctor.md +6 -0
  60. package/docs/ux-demo.md +1 -1
  61. package/docs/webawesome-theme.md +11 -2
  62. package/docs/workbench.md +302 -7
  63. package/llms.txt +7 -3
  64. package/package.json +5 -1
  65. package/ux-demo/recipes/collapsible-sidebar.tsx +8 -7
  66. package/dist/chunk-CCMKJWLX.js.map +0 -1
  67. package/dist/chunk-EKYZCRCX.js.map +0 -1
  68. package/dist/chunk-UKKSMZYT.js.map +0 -1
  69. package/dist/chunk-VOCPXGQN.js.map +0 -1
@@ -151,14 +151,14 @@ an upstream component or recipe request.
151
151
  | One-pane navigation — `NavStack` | A compact flow pushes into details while keeping earlier views mounted, or a single-pane screen needs the same stable layout contract. | Do not use it when list and detail should remain visible together; use `SplitView`. Do not use it alone for co-equal top-level destinations; use one stack per `TabScaffold` tab. | Keep the ordered view array controlled, put view-specific bottom chrome on each view (or use the component fallback), import its CSS explicitly, and call `wireNavStack` for back delegation, content-slide/chrome-cross-fade transitions, and focus move/restore. Mark a preferred initial target with `data-nav-focus` when DOM order is insufficient. | View keys/content/titles, push and pop actions, preferred initial focus targets, and per-view or persistent bottom toolbar content. | `@kerfjs/ui/nav-stack` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
152
152
  | Responsive list-detail — `SplitView` | A primary list and related detail belong together on roomy devices but become a one-pane drill-down on compact devices. | Do not use it for a single linear flow or a multi-panel editor shell. | Provide controlled selection/detail state and device class; opt into resize wiring only when the separator is user adjustable. | Selection, detail content, compact back action, device policy, and optional persisted width. | `@kerfjs/ui/split-view` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
153
153
  | Compact top-level destinations — `TabScaffold` | A handset or portrait-tablet app has two to five co-equal destinations whose scenes and nested navigation must remain mounted across switches. | Do not use it for document tabs, reorderable work items, or a desktop navigation rail. | Keep the active id controlled, import its CSS explicitly, and call `wireTabScaffold`; commonly place one `NavStack` in each tab scene. | Destination definitions, active id, routing, persistence, and roomy-device replacement. | `@kerfjs/ui/tab-scaffold` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
154
- | Desktop multi-panel workspace — `Workbench` | A desktop-class tool needs a central work area plus independently collapsible navigator, inspector, or console regions. | Do not shrink the full shell onto compact devices or use it for one standalone panel. | Render only the required rails/drawer, keep collapse state controlled, and replace peripheral regions with stacks or overlays below desktop sizes. | Panel presence/content, collapsed states, sizes, persistence, responsive replacement, and controls. | `@kerfjs/ui/workbench` | [Workbench](./workbench.md) |
154
+ | Desktop multi-panel workspace — `Workbench` | A desktop-class tool needs a central work area plus independently collapsible navigator, inspector, or console regions. | Do not shrink the full shell onto compact devices or use it for one standalone panel. | Render only the required rails/drawer, keep collapse state controlled, and replace peripheral regions with stacks or overlays below desktop sizes. Opt a panel into drag/keyboard resizing with `resizable` and call `wireWorkbench` with its size signal. | Panel presence/content, collapsed states, sizes, persistence, responsive replacement, and controls. | `@kerfjs/ui/workbench` | [Workbench](./workbench.md) |
155
155
  | Standalone rail or drawer — `CollapsiblePanel` | One left/right rail or bottom drawer needs controlled collapse without adopting the complete `Workbench` shell. | Do not use it for several coordinated workspace panels or retain an empty icon-only rail while collapsed. | Pair it with `CollapsiblePanelToggle`; call `wireSidebar` for focus transfer, Escape/backdrop dismissal, compact overlay trapping, and persistence integration. | Panel content/size, collapsed state, toggle placement, persistence, and compact policy. | `@kerfjs/ui/collapsible-panel` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
156
156
  | Lowered content surface — `SunkenPanel` | A main work area or nested content group needs one visually lowered surface with a compact inset and vertical stack. | Do not use it merely to add padding, as a replacement for `Pane` anatomy, or around children that already own the same outer surface. | Compose the application-owned content as children, choose the default `rounded` shape or `square` for a flush edge-to-edge area, and pass `ariaLabel` only when the surface is a distinct named region. | Region semantics, accessible name, shape, scroll ownership, child ordering, and content. | `@kerfjs/ui/sunken-panel` | [SunkenPanel](./sunken-panel.md) |
157
157
  | Menu composition | Navigation sections need full-size rows and the same content-item geometry as every other pane. | Do not add sidebar-specific wrapper padding, shrink targets to icon size, nest an interactive trailing control in `ListItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `ListHeader`, `ListItem`, and `ListActionRow` in `.kui-content`; use `ListHeader` toggle mode with real controlled content, and use `.kui-content-item` for other surfaces plus a pane footer for toolbar actions. | Information architecture, disclosure content and state, responsive drawer/shell behavior, and token overrides. | `@kerfjs/ui/layout.css` | [Pane geometry](../README.md#pane-and-content-geometry) |
158
158
  | Resizable application pane — `ResizableRegion`, `clampRegionSize`, `resizeRegionFromPointer` | A controlled split pane needs the Kerf separator, collapse state, pointer plus keyboard resizing, or a product-specific decorative grip. | Do not use it for static equal columns; use `Grid`. Use application CSS grid for static asymmetric or intrinsic tracks. Prefer it over Web Awesome `wa-split-panel` unless that component's distinct API is required. Keep `handleIcon` noninteractive. | Call `wireResizableRegions` from `@kerfjs/ui/wire-resizable-regions` once and retain its disposer. | Size signal, min/max policy, collapse policy, persistence, and optional decorative handle icon. | `@kerfjs/ui/resizable-region` | [ResizableRegion contract](./accessibility.md#resizableregion) |
159
159
  | One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, compact/segmented/icon-only presentation, label truncation, safe domain metadata, or a product-specific close glyph. | Do not render it alone or use it for a small settings choice; compose in `TabBar`, or use `SegmentedControl`. Keep `closeIcon` noninteractive. | Compose in `TabBar`; let `wireTabBars` manage interaction. Put only domain `data-*` values in `rootAttributes`; use typed appearance props instead of descendant CSS. | Tab identity, order, selection, close policy, content, domain metadata values, and outer placement. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
160
160
  | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, reorder, fill a segmented inspector strip, sit beside a trailing action, or keep that adjacent action distinct from a far-edge end action. | Do not use it for a compact local view toggle; use `SegmentedControl`. Do not use it for a long choice list; use `Select`. Prefer it over Web Awesome `wa-tab-group`, `wa-tab`, and `wa-tab-panel` for Kerf app tabs. | Call `wireTabBars` once, retain the disposer, and apply `onReorder` synchronously; `reorderTabs` is the default array helper. Combine `trailingPlacement="adjacent"` with `end` for split actions; TabBar owns the shrinking/scrolling tab geometry and keeps both actions visible. Choose typed presentation/allocation props while the parent owns outer placement. | Ordered tabs, selection, panels, routing, closing, persistence, action content, and outer placement. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
161
- | Panel, dialog, or page heading — `Toolbar` + `ToolbarText` | A panel, dialog, or page needs an extra-large title, optional icon, grouped trailing actions, and optional supporting copy. | Do not add a private heading wrapper or bespoke layout. The toolbar supplies structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect title/supporting-copy ids to the host, group controls, and delegate actions. **For a page or view title set `headingLevel` (usually `1`)** on `ToolbarText`; omit it for a dialog title referenced by `aria-labelledby`. | Open state, focus lifecycle, dismissal, controls, ids, copy, and heading level. | `@kerfjs/ui/toolbar` + `@kerfjs/ui/toolbar-text` | [Toolbar headings](./accessibility.md#toolbar-headings) |
161
+ | Panel, dialog, or page heading — `Toolbar` + `ToolbarText` | A panel, dialog, or page needs an extra-large title, optional icon, grouped trailing actions, and optional supporting copy. | Do not add a private heading wrapper or bespoke layout. The toolbar supplies structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. In a narrow peripheral rail or drawer, where xlarge would truncate at the panel's minimum size, use the default `ToolbarText` size as pane identity. | Connect title/supporting-copy ids to the host, group controls, and delegate actions. **For a page or view title set `headingLevel` (usually `1`)** on `ToolbarText`; omit it for a dialog title referenced by `aria-labelledby`. | Open state, focus lifecycle, dismissal, controls, ids, copy, and heading level. | `@kerfjs/ui/toolbar` + `@kerfjs/ui/toolbar-text` | [Toolbar headings](./accessibility.md#toolbar-headings) |
162
162
  | Dialog or popup geometry — `DialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets, or dropdown menus need list-compatible inset ownership. | Do not use these wrappers as generic layout or to replace native dialog/dropdown behavior. Avoid consumer `::part()` overrides and do not stack a dialog body inset around list-owned child geometry. | Wrap one `wa-dialog` or `wa-dropdown`; choose the typed geometry while retaining native labeling, open state, focus, dismissal, and menu semantics. Compose a dialog body as a `List` by default, use `bodyInset="none"` when its children own list geometry, and wrap bare prose in `ListInsetText`. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
163
163
  | Key/value facts — `ValueTable`, `ValueTableRow` | Read-only labels and values form a semantic definition list, optionally with a leading icon. | Do not use it for editable form fields or a row/column data grid; use native form or table semantics. | Compose typed `ValueTableRow` entries; pass `icon` when a 24px leading icon adds useful context. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
164
164
  | Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | None; pass a label when the spinner conveys status. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
@@ -186,12 +186,18 @@ an upstream component or recipe request.
186
186
  - A `ResizableRegion` overlay automatically clamps both its track and fixed-size
187
187
  animated content to the responsive overlay maximum. Set the policy and maximum
188
188
  on the component instead of adding application descendant width/height fixes.
189
+ It paints `--kui-color-surface` itself (an inline region stays transparent),
190
+ so do not add a background to make an overlay opaque.
189
191
  - Configure application panels through the shared `separator`, `collapseMotion`,
190
192
  `contentOverflow`, `presentation`, `restoreControl`, and `restorePosition`
191
193
  props on `ResizableRegion`, `Workbench` panels, and `CollapsiblePanel`. These
192
194
  cover separator suppression, one-reflow collapse with composited motion,
193
195
  bottom-drawer popups, compact overlays or replacements, and safe-area restore
194
- placement without descendant `.kui-*` overrides. `wireSidebar` also accepts a
196
+ placement without descendant `.kui-*` overrides. A `Workbench` rail's
197
+ or bottom drawer's `responsiveOverlayAt` (like `ResizableRegion`'s
198
+ `responsiveFillAt`) switches it to an overlay below a container breakpoint
199
+ without a device-class check.
200
+ `wireSidebar` also accepts a
195
201
  hidden compact replacement and keeps compact overlays exclusive by default.
196
202
  - `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
197
203
 
@@ -207,8 +213,12 @@ toolbar geometry, hover/pressed treatment, and grouping. `SegmentedControl`,
207
213
  **inside** a `ToolbarControlGroup`, not loose in the zone. For a page, panel, or
208
214
  dialog heading, put an extra-large `ToolbarText` directly in the leading zone,
209
215
  optionally preceded by a grouped icon, and group trailing actions. Keep supporting
210
- copy below as app-owned content. A bottom divider is enabled by default. Set
211
- `dividerSides=""` for none, or
216
+ copy below as app-owned content. Size the title to its track: a narrow
217
+ peripheral rail or drawer (a navigator, inspector, or console beside a work
218
+ area that carries the extra-large title, often with its own close control in
219
+ the header) labels itself with the default size, because extra-large truncates
220
+ there — "Inspector" becomes "Ins…" in a 160px rail. A bottom divider is
221
+ enabled by default. Set `dividerSides=""` for none, or
212
222
  use the canonical physical-edge combinations (`t`, `r`, `b`, `l`, in that
213
223
  order—for example `tr` or `trbl`) when the toolbar owns other separator edges.
214
224
 
@@ -216,7 +226,7 @@ Common toolbar patterns:
216
226
 
217
227
  | Want | Put in the zone | Notes |
218
228
  | ----------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
219
- | Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
229
+ | Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title that fits) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
220
230
  | One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
221
231
  | An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
222
232
  | A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once; use `focusRingOwner="group"` so focus follows the group's geometry |
@@ -25,6 +25,10 @@ height shell. Ordinary document-flow pages can import the baseline without the
25
25
  class. Do not put `.kui-app-root` on several sibling containers: each requests
26
26
  the full available document height.
27
27
 
28
+ `kui-app-root` is the public class of the component catalog's
29
+ `document-baseline` entry, so `kerf-ui-doctor` and the
30
+ `kerfjs/ui-public-boundaries` ESLint rule accept it (no KUI-L101 finding).
31
+
28
32
  The stylesheet is intentionally absent from `@kerfjs/ui/styles.css` and every
29
33
  component browser import because its global selectors would otherwise change
30
34
  consumer documents implicitly. Import it once and load application overrides
package/docs/layout.md CHANGED
@@ -153,7 +153,11 @@ document content, not toolbar identity: use `Text` with the `h2`–`h6` variant
153
153
  the outline calls for (inside a `List` with `controlInsets`, its text lines up
154
154
  with content items). Use `ListHeader` when the section is a list or menu group
155
155
  that needs a count, badge, action, or disclosure, and keep extra-large
156
- `ToolbarText` for the panel, dialog, or page title.
156
+ `ToolbarText` for the panel, dialog, or page title. A narrow peripheral rail or
157
+ drawer is the exception that proves the rule: beside a work area that already
158
+ carries the extra-large title, its header names the pane with the default size,
159
+ because extra-large would truncate there ("Inspector" becomes "Ins…" in a 160px
160
+ rail) and pane identity should stay quiet.
157
161
 
158
162
  Presentation is independent of the native element: use `tone="quiet"` for
159
163
  supporting copy, `tone="danger"` for error or validation copy, `size="compact"`
package/docs/recipes.md CHANGED
@@ -166,9 +166,11 @@ standalone `CollapsiblePanel`s (`@kerfjs/ui/collapsible-panel`) driven by
166
166
  standard per-side glyph: a collapse toggle lives inside each panel, and the
167
167
  panel's expand toggle shows only while it is collapsed, so exactly one control
168
168
  owns each action and a collapsed panel is still reachable. The rail's expand
169
- toggle sits in the always-visible main header; the drawer's floats in a
170
- `FloatingToolbar` at the bottom edge the drawer opens from, next to where the
171
- action happens. `wireSidebar` owns the toggle delegation, moves focus into a
169
+ toggle sits in the always-visible main header; the drawer's is a
170
+ `FloatingToolbar` passed as the drawer's own `restoreControl`, which the panel
171
+ floats in its column's safe-area-aware bottom-end corner, next to where the
172
+ drawer opens — do not hand-roll a conditional floating control beside the
173
+ panel. `wireSidebar` owns the toggle delegation, moves focus into a
172
174
  panel on open, restores it on close (to the trigger, or to the panel's expand toggle when
173
175
  the trigger sits inside the collapsed panel), and — when a `deviceClass()` reports
174
176
  `compact` — presents the panels as a dismissable **overlay** (backdrop, Escape
@@ -69,7 +69,8 @@ ordinary source/type gates:
69
69
  `FloatingToolbar`, `Pane`, `ResizableRegion`, `SegmentedControl`, `StateBanner`,
70
70
  `SunkenPanel`, `ToolbarText`, `CollapsiblePanel`, `Catalog`, and the wire helpers;
71
71
  - required controlled-state callbacks on `wireResizableRegions`, `wireTabBars`,
72
- and `wireTabScaffold`;
72
+ and `wireTabScaffold`, and the required app-owned size signals on
73
+ `wireWorkbench`;
73
74
  - mutually exclusive count/badge metadata on `ListHeader`; and
74
75
  - protected application metadata boundaries on `AppTab`, `ListItem`,
75
76
  `ListActionRow`, `ListHeader`, `Pane`, `CatalogExample`, and
@@ -55,25 +55,26 @@ policy, or analysis inputs to the containing application.
55
55
 
56
56
  ## Diagnostics and exit behavior
57
57
 
58
- | Rule | Kind | Meaning |
59
- | ---------- | ------ | ----------------------------------------------------------------- |
60
- | `KUI-L001` | error | A stylesheet reaches into a private or unknown `.kui-*` selector. |
61
- | `KUI-L002` | error | A stylesheet references an unknown or private `--kui-*` token. |
62
- | `KUI-L003` | error | One element combines public classes that claim the same geometry. |
63
- | `KUI-L004` | review | Nested literal classes both add a content inset. |
64
- | `KUI-L005` | review | Consumer CSS forces a public component's dimensions. |
65
- | `KUI-L006` | review | Literal spacing falls outside Kerf's approved scale. |
66
- | `KUI-L007` | error | A declared scroll owner is nested inside another scroll owner. |
67
- | `KUI-L008` | review | A dynamic class expression cannot be classified soundly. |
68
- | `KUI-L009` | error | A stylesheet cannot be parsed. |
69
- | `KUI-L010` | error | A selector reaches a private Kerf descendant from a public root. |
70
- | `KUI-L011` | error | A `::part()` target is not cataloged as a public extension point. |
71
- | `KUI-L012` | error | CSS assigns a Kerf token without a public configuration contract. |
72
- | `KUI-L013` | error | A CSS-adjacent prop uses an unknown shorthand or raw literal. |
73
- | `KUI-L014` | error | A typed helper produces the wrong property grammar. |
74
- | `KUI-L015` | error | An expression-only helper is passed without a composer. |
75
- | `KUI-L016` | error | A removed declaration-list escape hatch is used. |
76
- | `KUI-L017` | review | A valid but exceptional off-scale shorthand needs justification. |
58
+ | Rule | Kind | Meaning |
59
+ | ---------- | ------ | ------------------------------------------------------------------------------------- |
60
+ | `KUI-L001` | error | A stylesheet reaches into a private or unknown `.kui-*` selector. |
61
+ | `KUI-L002` | error | A stylesheet references an unknown or private `--kui-*` token. |
62
+ | `KUI-L003` | error | One element combines public classes that claim the same geometry. |
63
+ | `KUI-L004` | review | Nested literal classes both add a content inset. |
64
+ | `KUI-L005` | review | Consumer CSS forces a public component's dimensions. |
65
+ | `KUI-L006` | review | Literal spacing falls outside Kerf's approved scale. |
66
+ | `KUI-L007` | error | A declared scroll owner is nested inside another scroll owner. |
67
+ | `KUI-L008` | review | A dynamic class expression cannot be classified soundly. |
68
+ | `KUI-L009` | error | A stylesheet cannot be parsed. |
69
+ | `KUI-L010` | error | A selector reaches a private Kerf descendant from a public root. |
70
+ | `KUI-L011` | error | A `::part()` target is not cataloged as a public extension point. |
71
+ | `KUI-L012` | error | CSS assigns a Kerf token without a public configuration contract. |
72
+ | `KUI-L013` | error | A CSS-adjacent prop uses an unknown shorthand or raw literal. |
73
+ | `KUI-L014` | error | A typed helper produces the wrong property grammar. |
74
+ | `KUI-L015` | error | An expression-only helper is passed without a composer. |
75
+ | `KUI-L016` | error | A removed declaration-list escape hatch is used. |
76
+ | `KUI-L017` | review | A valid but exceptional off-scale shorthand needs justification. |
77
+ | `KUI-L018` | review | A loud fill override lacks a governing `on-loud`, or the literal pair is below 4.5:1. |
77
78
 
78
79
  Errors are provable contract violations and make the command exit 1. Review
79
80
  findings are deliberately heuristic and do not fail by default; pass
@@ -102,6 +103,80 @@ uses that same metadata for direct component calls and JSX, including nested
102
103
  paths such as `choices[].color`; consumer catalogs receive identical checks.
103
104
  Dynamic values remain a type-system responsibility rather than being guessed.
104
105
 
106
+ ## Loud fill / on-loud pairing (`KUI-L018`)
107
+
108
+ The shipped Web Awesome theme pairs every `--wa-color-{tone}-fill-loud` with a
109
+ `--wa-color-{tone}-on-loud` foreground that clears WCAG AA (4.5:1), for the
110
+ `neutral`, `brand`, `success`, `warning`, `danger`, and `pop` tones. A
111
+ stylesheet that overrides a loud fill without also setting that tone's
112
+ `on-loud` keeps the theme's foreground on an unknown fill, so the guarantee no
113
+ longer holds. `KUI-L018` reports such a fill and names each scope that lacks a
114
+ pair, and it reports a pair that is present but measurably fails.
115
+
116
+ **Which `on-loud` governs a fill.** Each individual selector of the fill
117
+ (after resolving CSS nesting and splitting selector lists) inside its chain of
118
+ at-rules is one scope. An `on-loud` of the same tone governs it when declared:
119
+
120
+ - in the same scope, in the same or a separate rule, in either order; or
121
+ - in an ancestor scope: a document-root selector (`:root`, `html`, `:host`,
122
+ `*`), or the leading part of a descendant or child chain (`.shell` governs
123
+ `.shell .panel` and `.frame > .tile`, but not `.shell-alt .panel` or
124
+ `.shell.dense`); and
125
+ - under no at-rule, or the same at-rule chain (an unconditional `on-loud`
126
+ applies inside `@media`, but one under `@media` or `@supports` is not proven
127
+ to apply to an unconditional fill).
128
+
129
+ That `on-loud` may live in the same stylesheet or in another one the same
130
+ entry loads: a source module that imports both stylesheets, or a stylesheet
131
+ that reaches both through `@import`. A stylesheet no entry loads with the fill
132
+ does not count. When several candidates apply, the same scope wins (this
133
+ stylesheet first), then the nearest ancestor.
134
+
135
+ **Contrast, when it can be proven.** When the fill and its governing `on-loud`
136
+ are both literal opaque colors (hex, `rgb()`/`rgba()`, `hsl()`/`hsla()`,
137
+ `white`/`black`, or a `light-dark()` of two of them), the analyzer computes the
138
+ WCAG contrast ratio in each color scheme and reports a pair below 4.5:1, with
139
+ the ratio and scheme in its evidence. An unpaired literal fill is accepted when
140
+ it clears 4.5:1 in both schemes against the theme's default `on-loud` for its
141
+ tone (`light-dark(#fff, #111113)` for `brand` and `danger`, `#1d1d1f` for
142
+ `success` and `warning`); `neutral` follows a surface token and `pop` has no
143
+ Web Awesome default, so their unpaired fills are always reported. Values it
144
+ cannot prove (`var()`, `color-mix()`, translucent colors, keywords) are not
145
+ measured: a present pair is accepted, and an unpaired fill is reported.
146
+
147
+ ```css
148
+ /* KUI-L018: brand fill moves, its foreground does not. */
149
+ .billing {
150
+ --wa-color-brand-fill-loud: #7540a8;
151
+ }
152
+
153
+ /* Paired: both in the same scope. */
154
+ .billing {
155
+ --wa-color-brand-fill-loud: #7540a8;
156
+ --wa-color-brand-on-loud: #ffffff;
157
+ }
158
+
159
+ /* Paired through an ancestor scope (6.87:1). */
160
+ :root {
161
+ --wa-color-brand-on-loud: #ffffff;
162
+ }
163
+ .billing {
164
+ --wa-color-brand-fill-loud: #7540a8;
165
+ }
166
+
167
+ /* KUI-L018: present, but white on this fill measures 2.14:1. */
168
+ .billing {
169
+ --wa-color-brand-fill-loud: #7fb3ff;
170
+ --wa-color-brand-on-loud: #ffffff;
171
+ }
172
+ ```
173
+
174
+ It is a review finding rather than an error because the analyzer cannot see
175
+ every cascade: an `on-loud` set inline, by script, or by a stylesheet it does
176
+ not analyze may still govern the fill. Set the `on-loud` explicitly beside the
177
+ fill, which also documents the pair; use `--fail-on-review` to gate it in CI.
178
+ See [the Web Awesome theme customization notes](./webawesome-theme.md#customization).
179
+
105
180
  The JSON report schema is exported as
106
181
  `@kerfjs/ui/analyzer/report.schema.json`.
107
182
 
package/docs/ui-doctor.md CHANGED
@@ -24,6 +24,12 @@ exceptional but valid spacing choice in the review queue. The ESLint and
24
24
  analyzer stages share these ids, so the normalized report can be consumed as
25
25
  one repair loop without tool-specific translations.
26
26
 
27
+ The analyzer stage also reads consumer stylesheets for theme overrides:
28
+ `KUI-L018` is a review finding for a `--wa-color-{tone}-fill-loud` override
29
+ that lacks a governing `--wa-color-{tone}-on-loud` (in the same or an ancestor
30
+ scope, in any stylesheet the same entry loads), or whose literal pair measures
31
+ below 4.5:1 (see [the analyzer rule](./ui-analyzer.md#loud-fill--on-loud-pairing-kui-l018)).
32
+
27
33
  The browser evaluator is different: it runs the application and is disabled by default. It only runs when configuration supplies `browser.url` or the command receives `--browser-url`. Start and authorize the target application separately.
28
34
 
29
35
  An unavailable or failed stage does not prevent independent stages from reporting. Its final exit is still a configuration failure, so a partial run cannot appear clean.
package/docs/ux-demo.md CHANGED
@@ -18,7 +18,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
18
18
 
19
19
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
20
20
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
21
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
21
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding (a new tab opens `pending` with a spinner and stays selectable while its panel shows a `Skeleton` heading until a simulated load completes), closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
22
22
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
23
23
  - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
24
24
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
@@ -292,19 +292,28 @@ smallest subtree that needs a different identity:
292
292
  ```css
293
293
  :root {
294
294
  --wa-color-brand-fill-loud: #7540a8;
295
+ --wa-color-brand-on-loud: #ffffff;
295
296
  --wa-color-focus: #7540a8;
296
297
  --wa-form-control-border-radius: 0.5rem;
297
298
  }
298
299
 
299
300
  .billing-workspace {
300
301
  --wa-color-success-fill-loud: #10a86b;
302
+ --wa-color-success-on-loud: #1d1d1f;
301
303
  }
302
304
  ```
303
305
 
304
306
  The shipped theme pairs every `--wa-color-{variant}-fill-loud` with an
305
307
  `--wa-color-{variant}-on-loud` that clears WCAG AA (4.5:1) in both color
306
- schemes. When you override a loud fill, override its `on-loud` alongside it if
307
- the pair no longer clears 4.5:1 (for example, white text on a bright fill).
308
+ schemes. When you override a loud fill, set its `on-loud` alongside it, in the
309
+ same selector and at-rule scope, to a foreground that clears 4.5:1 on the new
310
+ fill (white text on a bright fill usually does not: `#10a86b` above reaches
311
+ only 3.1:1 with white, 5.5:1 with `#1d1d1f`). `kerf-ui-analyze` and
312
+ `kerf-ui-doctor` report a loud fill override without a governing `on-loud`, or
313
+ a literal pair that measures below 4.5:1, as review finding
314
+ [`KUI-L018`](./ui-analyzer.md#loud-fill--on-loud-pairing-kui-l018); it cannot
315
+ measure a `var()` or `color-mix()` value, so choosing a readable one is still
316
+ yours.
308
317
 
309
318
  Use `.wa-light`, `.wa-dark`, and `.wa-invert` for explicit appearance scopes.
310
319
  Component-specific custom properties and documented `::part()` selectors from