kerfjs 4.4.0 → 5.0.0-beta.3

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 (73) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/LICENSE +1 -1
  3. package/README.md +34 -35
  4. package/ai/cursorrules +46 -1
  5. package/ai/manifest.json +62 -5
  6. package/ai/skill.md +53 -2
  7. package/dist/actions.d.ts +1 -1
  8. package/dist/actions.js +4 -4
  9. package/dist/actions.js.map +1 -1
  10. package/dist/array-signal.js +5 -5
  11. package/dist/async.js +17 -10
  12. package/dist/async.js.map +1 -1
  13. package/dist/attach.d.ts +11 -8
  14. package/dist/attach.js +52 -4
  15. package/dist/attach.js.map +1 -1
  16. package/dist/chunk-GY4XV2UV.js +1 -1
  17. package/dist/{chunk-VVDJLWMP.js → chunk-HW7KSM2Y.js} +2 -2
  18. package/dist/chunk-HW7KSM2Y.js.map +1 -0
  19. package/dist/{chunk-KEZTD6H4.js → chunk-KPXIOG2C.js} +3 -3
  20. package/dist/{chunk-KEZTD6H4.js.map → chunk-KPXIOG2C.js.map} +1 -1
  21. package/dist/{chunk-SAYPJ6XR.js → chunk-KZJXHFIB.js} +10 -6
  22. package/dist/chunk-KZJXHFIB.js.map +1 -0
  23. package/dist/{chunk-MRYM3O3V.js → chunk-LVH3GC6B.js} +11 -8
  24. package/dist/chunk-LVH3GC6B.js.map +1 -0
  25. package/dist/chunk-QIP723L4.js +1 -1
  26. package/dist/{chunk-SRWQKB33.js → chunk-SVATPF5R.js} +97 -74
  27. package/dist/chunk-SVATPF5R.js.map +1 -0
  28. package/dist/{chunk-3APBEVHF.js → chunk-U6FK33SG.js} +3 -3
  29. package/dist/{chunk-3APBEVHF.js.map → chunk-U6FK33SG.js.map} +1 -1
  30. package/dist/{chunk-U32TFTGZ.js → chunk-UZJ6I4T6.js} +3 -3
  31. package/dist/chunk-UZJ6I4T6.js.map +1 -0
  32. package/dist/{chunk-YHH7OUFA.js → chunk-V46JKE44.js} +3 -3
  33. package/dist/chunk-V46JKE44.js.map +1 -0
  34. package/dist/{chunk-SUPUPSBE.js → chunk-ZDCJZCNO.js} +9 -8
  35. package/dist/chunk-ZDCJZCNO.js.map +1 -0
  36. package/dist/dev.d.ts +9 -6
  37. package/dist/dev.js +5 -5
  38. package/dist/dev.js.map +1 -1
  39. package/dist/html.d.ts +1 -1
  40. package/dist/html.js +5 -5
  41. package/dist/html.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +11 -11
  44. package/dist/jsx-runtime.js +5 -5
  45. package/dist/list.d.ts +1 -1
  46. package/dist/list.js +273 -211
  47. package/dist/list.js.map +1 -1
  48. package/dist/overlay.d.ts +190 -280
  49. package/dist/overlay.js +401 -361
  50. package/dist/overlay.js.map +1 -1
  51. package/dist/remount.d.ts +5 -3
  52. package/dist/remount.js +29 -10
  53. package/dist/remount.js.map +1 -1
  54. package/dist/router.d.ts +1 -1
  55. package/dist/router.js +44 -22
  56. package/dist/router.js.map +1 -1
  57. package/dist/scope.d.ts +4 -3
  58. package/dist/scope.js +10 -10
  59. package/dist/scope.js.map +1 -1
  60. package/dist/testing.js +4 -4
  61. package/dist/timing.d.ts +3 -2
  62. package/dist/timing.js +5 -5
  63. package/dist/timing.js.map +1 -1
  64. package/llms.txt +7 -4
  65. package/package.json +12 -10
  66. package/dist/chunk-MRYM3O3V.js.map +0 -1
  67. package/dist/chunk-SAYPJ6XR.js.map +0 -1
  68. package/dist/chunk-SRWQKB33.js.map +0 -1
  69. package/dist/chunk-SUPUPSBE.js.map +0 -1
  70. package/dist/chunk-U32TFTGZ.js.map +0 -1
  71. package/dist/chunk-VVDJLWMP.js.map +0 -1
  72. package/dist/chunk-YHH7OUFA.js.map +0 -1
  73. /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,122 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ - Fixed the complete kanban example's mobile layout by stacking its columns and header controls without horizontal overflow while preserving the wider board layout.
10
+ - Fixed the complete dashboard example's mobile layout so its status header, chart, and table fit the viewport without horizontal overflow.
11
+ - Decomposed `bindList` into focused keyed-row and virtualization controllers while keeping `list.ts` as the public orchestration entry; snapshot and granular updates now share one item-replacement contract.
12
+ - Corrected the canonical, published, and AI-facing dev-warning documentation to reflect the hook-only production boundary: kerf never gates diagnostics on `NODE_ENV`; omitting `kerfjs/dev` makes them unreachable, while installed opt-in warnings check only their own switch.
13
+ - Added the missing always-on row-key warning to the published diagnostics guide and made the documentation gate require every canonical diagnostic section on the public site.
14
+ - Restored the site-wide Playwright visual QA command with dynamic discovery of every built HTML surface (including `404.html`), desktop/tablet/mobile full-page captures, root- and URL-aware route filtering, collision-safe evidence names, and automated page-health checks for overflow, settled images, and collapsed content.
15
+ - Kept `observeRemovals()` scopes alive when their nodes move between parents or reorder within the observed root; automatic disposal now waits for permanent removal from that root.
16
+ - Made `remountOn()` disposal idempotent and ownership-safe: the first call tears down its mounted subtree and stops the key watcher, while repeated calls leave any later external content in the parent untouched.
17
+ - Made `throttle().cancel()` effective from inside both leading and trailing callbacks: the callback can now reset the active rate window immediately instead of having a new cooldown installed after it returns.
18
+ - Removed the overlay barrel/helper cycle by extracting the lifecycle core and splitting `confirm`, `prompt`, `form`, and `choice` into focused internal modules, without changing the public `kerfjs/overlay` API.
19
+ - Fixed `attach()` for nodes prepared before insertion: setup still runs immediately, teardown waits through the initially-disconnected phase, then fires once after the node has connected and is later removed (including direct insertion into an already-connected shadow root, ancestor removal, same-batch light-DOM insertion/removal, and removal through a shadow host).
20
+ - Made `bindList` reject duplicate keys before changing the DOM, with errors that name the key and both indices; rejected `arraySignal` batches are drained and the next valid state snapshot-recovers before granular updates resume.
21
+ - Made `bindList` recover after a granular row render throws: the failing call still reports the error, while the next source update reconciles the authoritative snapshot before granular patches resume, including when an earlier patch in the same batch already changed the DOM.
22
+ - Fixed stores created before `kerfjs/dev` installation never emitting the opt-in narrow-set warning. Store actions now resolve the warning hook at `set()` call time and allocate per-store dedup state only when diagnostics are present.
23
+ - Added a deterministic `bindList` transition matrix covering empty/refill recovery, granular-to-snapshot-to-granular sequences, and mixed batched structural changes with node-identity assertions.
24
+ - Made concurrent fallback overlays arbitrate dismissal from the top down, so one Escape, backdrop, or outside click closes only the active modal or non-modal surface.
25
+ - Made `prompt()` and `form()` reject malformed bring-your-own dialog markup immediately: missing required input markers now close the incomplete overlay and throw an error naming the exact marker (and form field) instead of failing later on interaction.
26
+ - Covered `each()` count-drift recovery through the public API: after a granular insert drains its patches and row rendering fails, a subsequent update now has a regression test proving the snapshot path repairs the DOM.
27
+ - Fixed tooltips hiding when just one of their pointer/focus triggers left; pointer and focus presence are now tracked independently, so either interaction keeps the tooltip open until both end.
28
+ - Strengthened the `bindList` feature index with exact existing guards for same-key rebuilds, granular patch sequences, and teardown semantics.
29
+ - Extracted pure granular-list index-shift and cache-key-drift stages from the `each()` transition coordinator for direct testing.
30
+ - Normalized internal helper module filenames to the repository's kebab-case or primary-export convention and updated all live imports and documentation paths.
31
+ - Made list-binding rationale comments self-contained and named the bounded template/URL diagnostic excerpt limits.
32
+ - Split `mount()`'s render effect into named synchronous static-render and list-reconcile/commit phases without changing lifecycle order.
33
+ - Replaced terse router and overlay-position internals with descriptive route-segment, event, rectangle, and viewport names.
34
+ - Preserved edited stale AI-assistant configs as forks by validating their versioned canonical section against shipped historical hashes before offering an autofix.
35
+ - Required every complete example app to have its own feature-coverage row mapped to a smoke test inside that app's Playwright suite.
36
+ - Made router parameter decoding fail closed to no-match on malformed percent escapes instead of throwing `URIError`, including named and wildcard captures.
37
+ - Pointed `@kerfjs/ui` package metadata at the existing published component-packages documentation route and added a source-backed metadata regression test.
38
+ - Fixed the complete router example's broken documentation links and taught the docs-example gate to validate published routes and heading fragments.
39
+ - Added full-pipeline coverage for missing-row-key diagnostics across initial, snapshot, granular, and in-place list reconciliation, including per-binding deduplication and production silence.
40
+ - Extended the feature-completeness gate to inventory router-subpath exports and deduplicate values re-exported across public surfaces.
41
+ - Kept `kerfjs/ai-assistant-configs` filesystem checks read-only during plain ESLint runs while retaining explicit `eslint --fix` installation and stale-file updates.
42
+ - Required history-router bases to match an exact path or path-segment boundary, so `/app` no longer strips or intercepts `/apple`.
43
+ - Added the missing MIT `LICENSE` to generated `create-kerf-component` packages, including package-name token replacement and publish-contents coverage.
44
+ - Made dangerous-URL attribute-name matching ASCII-case-insensitive so mixed-case spellings cannot bypass static or bound screening.
45
+ - Normalized synchronous `resource().run()` fetcher throws into the same stale-guarded failed state and resolving promise used for asynchronous rejections.
46
+ - Escaped plain-string `toast()` content as text while preserving trusted `SafeHtml` and render-function markup, closing a stored-markup injection path.
47
+ - Rejected non-finite and fractional indices in every indexed `arraySignal` mutator before changing source state or emitting a patch; even equal `move()` indices are now validated before the no-op path.
48
+ - Made `DisclosureArrow` animate configurable directions over the shortest rotation path, including the left-to-up demo's natural 90-degree clockwise turn; closed-to-open 180-degree ties use counterclockwise rotation.
49
+ - Reworked `DialogHeader` as a true top toolbar: its 24px icon in a 34px circle and first title line align with automatically grouped actions, while an optional subtitle remains below; added localized action-group labels and compatibility for existing pre-grouped actions.
50
+ - Gave every `ValueTableRow` 8px of root-scaled top and bottom padding while preserving its semantic inline inset and icon-aware separator alignment.
51
+ - Fixed the composer-form recipe's Reset action so upgraded Web Awesome input and textarea controls clear their live displayed values together with the controlled Kerf state, while retaining the `Draft reset` announcement.
52
+ - Removed the unsolicited command-palette catalog demo and production recipe while retaining the earlier, independent application-local adapter example for missing-concept guidance.
53
+ - Kept the `ResizableRegion` UX specimen readable by moving committed-width status below the controlled pane at narrow widths, preserving its 200% root-scaled split, and retaining local scroll access to the handle at its maximum size.
54
+ - Reworked the composer-form recipe into one coherent visible form surface with exactly three transparent 8/1/8 content sections, 24px major rhythm, and a conditional `StateBanner` as its only nested semantic surface.
55
+ - Made `MenuHeader` fill its available inline width, align separate actions at the logical end in both LTR and RTL, and use an overridable 18px visible action glyph without shrinking the 44px target.
56
+ - Added first-class `MenuHeader` count semantics with a required localized `countLabel`, zero-safe neutral pill presentation, accessible heading/disclosure naming, legacy non-count badge exclusivity, and runtime filtering for invalid counts and protected presence flags.
57
+ - Made `MenuHeader` toggle mode supply the production 18px `DisclosureArrow` by default while retaining custom `actionIcon` replacement, removed competing raw SVG sizing/rotation, and made the menu-composition Tools disclosure control real content instead of showing a false Projects chevron.
58
+ - Repaired the `DisclosureArrow` UX specimen by replacing its ambiguous sorting glyph with a right-facing arrow, putting both examples in independently controlled native buttons with stable names and `aria-expanded`, and demonstrating custom directions through delegated pointer and keyboard activation across narrow and 200%-zoom layouts.
59
+ - Set `DisclosureArrow` to an 18px root-scaled default, removed the catalog-only 24px enlargement, retained `--kui-disclosure-arrow-size` for scoped consumer overrides, and kept Kerf `Select` on its independent `.5` Web Awesome expand-glyph scale.
60
+ - Added a site-scoped CI dependency audit that includes the complete static-site build tree and fails on high or critical advisories while keeping low/moderate development-tool findings informational.
61
+ - Aligned the UX catalog's header-composition dialog with the shared 8px inline layout gutter while preserving its 16px vertical separation.
62
+ - Added visible `View demo source` and `Read guidance` affordances to every UI catalog detail, plus `View component source` for first-party components, backed by deterministic repository-relative paths for the main renderer, individual recipe files, Web Awesome specimens, and canonical browser-import implementations. Web Awesome entries explicitly label local documentation as Kerf integration guidance; all links use deploy-safe GitHub URLs and a responsive accessible layout.
63
+ - Added runtime-safe `AppTab.rootAttributes` for domain `data-*` metadata plus decorative `AppTab.closeIcon` and `ResizableRegion.handleIcon` slots, preserving existing tab and resize wiring ownership while blocking case-folded component/action/drag/drop collisions.
64
+ - Added `MenuActionRow`, a full-width navigation row with sibling primary and trailing native-button actions, independent names and disabled states, controlled selection semantics, safe metadata/popover extension slots, and responsive 44px targets. `MenuItem.trailing` is now explicitly documented as dormant content.
65
+ - Added safe typed MenuItem/MenuHeader extension slots for application `data-*` metadata and native popover trigger relationships while protecting component-owned action, selection, disclosure, accessible-name, disabled, icon, and native-button semantics.
66
+ - Fixed keyboard deletion of a controlled `TokenSearchField` chip so `wireTokenSearchFields` restores focus and the text-relative caret after the application rerenders the editor.
67
+ - Aligned `@kerfjs/ui` CSS guidance and AI regression scoring with the catalog's exact public anatomy: documented public-class composition selectors are supported, private tag/id/attribute/unlisted-class descendants remain rejected, and historical measured runs replay under their recorded stricter oracle.
68
+ - Added typed `ValueTableRow` composition with a first-class optional icon hook. Value-table separators now align 8px from both edges for iconless rows and 40px from the left plus 8px from the right for rows with a 24px leading icon.
69
+ - Added first-class collapsible `TokenSearchField` support: an empty closed field becomes one iconic search action, controlled activation animates to the complete searchbox, and text or tokens keep it expanded after blur. It works standalone or when composed inside `ToolbarControlGroup`; the UX demo demonstrates that common toolbar composition at wide and narrow sizes.
70
+ - Restored the public documentation site to its Astro + Starlight implementation while the Kerf UI redesign is reconsidered, and removed the in-progress `@kerfjs/ui` page and promotional links from public navigation.
71
+ - Fixed `TokenSearchField` first-line alignment so the leading icon, editable text, clear action, and trailing content share the field's vertical center while remaining pinned when text wraps.
72
+ - Replaced sidebar-specific and wrapper-inset UI geometry with one shared pane/content model across sidebars, main areas, inspectors, and dialogs. Unpadded panes now compose optional toolbars, one scrolling content stack, and optional footers; content children own consistent 8px margin, 1px transparent-or-visible border, 8px padding, 12px or 22px radii, and 24px major separation. Toolbar groups retain 44px geometry even with transparent chrome, and `MenuHeader` now supports a badge while separating dormant title content from its action.
73
+ - Added pixel-first `remify(<px>)` authoring for `@kerfjs/ui` styles. The build converts values exactly against a 16px baseline into ordinary `rem`, package CSS exports now deliver compiled `dist/styles/` files, and the UX catalog applies the same transform during Vite development so CSS edits remain hot-reloaded.
74
+ - Added a side-effect-free `@kerfjs/ui/webawesome` TypeScript declaration subpath for all 70 catalog-supported Web Awesome elements, synchronized against the installed custom-elements manifest and covered by a downstream package-export compile fixture.
75
+ - Added a deterministic internal `@kerfjs/ui` AI-regression foundation with seven task-shaped prompts, frozen versioned context conditions, AST-aware structural scoring, generated public declaration signatures, equivalent supported import scoring in suite v2, and an opt-in non-executing TypeScript compile-evidence sidecar with exact response/config/package/declaration hashes. Adversarial fixtures catch component substitution, duplicated primitives, and spacing/layout ownership regressions without invoking a live model in CI.
76
+ - Added seven lazy, production-backed `@kerfjs/ui` composition recipes for application shells, sidebars, workspace headers, master-detail dialogs, composer forms, list-state lifecycles, and mixed-control toolbars, with stable catalog routes, canonical AI metadata, drift gates, responsive/keyboard/state coverage, and a copyable stable-root adapter that wires delegated actions, form/dialog events, resize commits, and idempotent disposal outside the catalog.
77
+ - Added a shipped, schema-described `@kerfjs/ui` machine-readable component catalog with deterministic typed UX projection and drift gates for public exports, package/CSS/registration paths, relationships, Web Awesome manifest coverage, AI guidance, routes, CSS hooks, and documentation links.
78
+ - Added a canonical `@kerfjs/ui/layout.css` vocabulary for responsive page, pane, section, control, metadata, surface, dialog, and scroll ownership, with semantic variables, compact/narrow behavior, consumer and AI guidance, normalized catalog compositions, and wide/intermediate/narrow/200%-zoom geometry coverage.
79
+ - Added a need-first `@kerfjs/ui` component-selection matrix and automated drift gate so AI tools choose among reuse, composition, thin application adapters, custom semantics, and overlapping Web Awesome components without inferring contracts from implementation CSS. A typed application-local command-palette example now shows how a missing package concept still uses canonical layout, one inset owner, and related-control grouping while keeping semantics honest and proposing recurring behavior upstream.
80
+ - Fixed `@kerfjs/ui` `Select` option icons disappearing after a Kerf rerender. Icon slots now have stable per-select/per-choice keys and preserve Web Awesome-owned slot state, while custom selected content is keyed by the controlled value so it updates cleanly.
81
+ - Added a shared `@kerfjs/ui` sidebar spacing composition with distinct 10px interaction and 20px content rails, 24px icon slots plus 10px gaps, and 44px row/header/toolbar targets. Menu headers, icon-bearing and iconless rows, bordered surfaces, and `.kui-sidebar-toolbar` now share public geometry tokens, consumer/AI guidance, responsive and RTL specimens, and browser assertions.
82
+ - Added a reusable controlled `TokenSearchField` to `@kerfjs/ui`, extracting Hot Sheet 2's free-text plus ordered atomic filter-chip field with accessible edit/remove/clear actions, DOM read and caret helpers, tree-shakeable styles, and full catalog coverage.
83
+ - Reorganized the largest catch-all unit suites into behavior-focused files while preserving all 1,554 existing tests, assertion counts, transition matrices, coverage, and feature-index mappings.
84
+ - Expanded the `@kerfjs/ui` pull-request browser gate from Chromium-only coverage to the full Chromium, Firefox, and WebKit suite, with Playwright binary caching keyed to the UI lockfile.
85
+ - Added a strict npm install-script policy for the site: only the reviewed `esbuild` and `sharp` binary installers run, the linked root package's Husky-only `prepare` is denied, and a preinstall checker detects lockfile installer drift.
86
+ - Updated the root and UI browser-test harnesses for Node 26: Playwright 1.63 removes the deprecated ESM loader registration, and normalized color variables keep web-server and worker output free of `NO_COLOR`/`FORCE_COLOR` conflict warnings.
87
+ - Cleaned generated root and `@kerfjs/ui` ESM shims so downstream bundlers no longer report ignored bare chunk imports, and deduplicated source-map directives without weakening package tree shaking.
88
+ - Added explicit Web Awesome component-selection guidance and aligned UX descriptions: prefer Kerf Select, SegmentedControl, TabBar, LucideIcon, and ResizableRegion for common app patterns; consider Popup for custom anchoring; reserve specialized ecosystem alternatives for concrete requirements. ResizableRegion now includes Hot Sheet 2's overridable 1px separator plus hover/focus grip.
89
+ - Made Web Awesome Tooltip and Popover arrowless by default in the optional Kerf theme, with public per-theme, scoped, and per-instance overrides plus focused UX and browser coverage.
90
+ - Documented Web Awesome Markdown's trusted-input-only security boundary, client-only rendering, and shared mutable Marked configuration, with an explicitly labeled UX specimen and a regression assertion for the warning.
91
+ - Clarified that Web Awesome Badge and Tag are the tree-shakeable generic primitives: status badges use the pill treatment, tags remain rounded rectangles, and domain adapters stay application-owned when they add mappings or mutation behavior.
92
+ - Unified Accordion, Details, Breadcrumb, and Kerf Select chevron scale through an overridable shared disclosure-icon token.
93
+ - Matched Web Awesome Carousel navigation to Kerf's compact disclosure geometry, using overridable 16px arrows and 7px visible page dots with accessible macOS-sized hit targets.
94
+ - Expanded the Web Awesome Animation UX specimen into an interactive settings panel with preset, easing, timing, playback, transport, lifecycle, and reduced-motion behavior.
95
+ - Fixed the Web Awesome Toast UX specimen to use the component's programmatic stack API, with visible browser coverage and an explicit note about Hot Sheet 2's current custom toast implementation.
96
+ - Made the Web Awesome intersection, mutation, and resize observer UX specimens interactive, with deliberate triggers, visible live-event results, and browser coverage.
97
+ - Updated the UI package's Vitest toolchain to 4.1.11 and resolved the remaining development-only npm audit advisories without changing its runtime dependency surface.
98
+ - Added a first-class controlled `SegmentedControl` to `@kerfjs/ui`, with Hot Sheet 2-aligned toolbar, rounded-rectangle, pill, equal-width, small, and disabled presentations; native pressed-button semantics; scoped palette overrides; tree-shakeable component CSS; and a complete UX-catalog route.
99
+ - Aligned `@kerfjs/ui` defaults with Hot Sheet 2, added overridable semantic state palettes, completed ToolbarControlGroup and StateBanner catalog variants, simplified related-component navigation, corrected MenuHeader action alignment, and added a controlled horizontally scrolling TabBar with pointer and keyboard reordering plus proximity-based edge autoscroll.
100
+ - Updated `@kerfjs/ui` to develop and test against Lucide 1.43.0 and Web Awesome 3.12.0, with matching peer baselines.
101
+ - Added an opt-in `@kerfjs/ui/webawesome.css` theme for Web Awesome's free components. It carries the Hot Sheet 2-aligned semantic palette, form and panel geometry, focus, tooltip, radius, and shadow choices without registering component JavaScript; the UX catalog now lists all 70 free Web Awesome 3.12 components under a collapsible ecosystem section, gives each a focused themed route and dependency links, and retains the aggregate light, dark, narrow, and scoped-override gallery.
102
+ - Fixed trapped overlays losing sequential focus in WebKit after the second control. Implicitly focusable descendants now receive `tabindex="0"` inside the trap, while authored tabindex values (including `-1`) remain intact.
103
+ - Fixed Firefox contenteditable carets jumping to the list container during keyed `moveBefore()` reorders. `each()` and `bindList` now restore exact contenteditable Selection boundary nodes and offsets after every move pass, even when the browser keeps `activeElement` unchanged.
104
+ - Repaired lockstep version metadata for the ESLint plugin and component scaffold, and added a release-time sync plus repository gate so package manifests, locks, plugin metadata, and published examples cannot drift again.
105
+ - Added the lockstep `@kerfjs/ui` package: accessible toolbar, menu, tab, header, resize, select, and feedback primitives; semantic CSS tokens and browser component subpaths that automatically include only reachable component styles; CSS-free root/SSR entries and an optional complete stylesheet; opt-in Web Awesome registration; AI-oriented docs; a production-backed UX catalog; bundle/unit/three-engine browser gates; and a least-privilege release workflow.
106
+ - The virtual-list demo animation opens on the full app again (header, the "N in the DOM" badge, and the list) before the scroll; the list now scrolls inside its own box while the header stays put (previously the whole frame was a full-bleed list strip), holds at row 0 before gliding instead of starting a few rows down, and the loop stays in phase on every pass. Captured with domotion-svg 0.28.2.
107
+
108
+ ## [4.4.1] - 2026-08-26
109
+
110
+
111
+
112
+ - Overhauled the README landing page: new one-line tagline, npm/size/license/TypeScript-types badges, a hoisted "Quick start" (install + `tsconfig`) right under the hook, and a jump nav (Quick start · Why kerf · Quick tour · Docs). The counter example now shows `delegate()` wiring the click handler.
113
+ - Reworked the homepage hero to lead with the value proposition ("Reactive UI that touches only the bytes that changed"), with "Introducing Kerf" demoted to an eyebrow, a right-sized logo, tighter vertical spacing, and "Get started" as the single filled primary action ("View examples" now a secondary/outline button).
114
+ - Redesigned the complete-apps index as a card grid — each app appears once with an animated preview, a one-line summary, and "Run live" / "Source" buttons.
115
+ - Every complete-app page now shows "Run live" and "View source" as buttons above the demo instead of small inline text links.
116
+ - Rewrote the site's docs pages as hand-authored consumer content (cleaner headings, no internal section numbers) rather than verbatim copies of the internal design docs.
117
+ - Renamed the migration guides' "Side-by-side code" heading to "Section by section" (the sections stack code blocks rather than showing true columns), across all framework pages.
118
+
119
+
120
+ - The getting-started and Markdown-editor demo animations now open on a non-blank frame — real code and a rendered heading are visible immediately instead of an empty pane.
121
+ - Added a horizontal-scroll shadow affordance to wide tables so off-screen columns are discoverable on narrow viewports.
122
+ - Shortened the longest demo alt text on the router and virtual-list pages for more concise screen-reader output.
123
+ - Moved framework version labels out of the performance table's framework column onto a methodology line below, keeping the column a clean label.
124
+
9
125
  ## [4.4.0] - 2026-08-23
10
126
 
11
127
 
package/LICENSE CHANGED
@@ -54,7 +54,7 @@ Steele-Idem, which is also distributed under the MIT License:
54
54
 
55
55
  ---
56
56
 
57
- `src/attrSelector.ts`'s `cssEscapeIdent` adapts the CSS identifier escaping of
57
+ `src/attr.ts`'s `cssEscapeIdent` adapts the CSS identifier escaping of
58
58
  the [CSS.escape polyfill](https://github.com/mathiasbynens/CSS.escape) by
59
59
  Mathias Bynens, which is also distributed under the MIT License:
60
60
 
package/README.md CHANGED
@@ -4,61 +4,72 @@
4
4
 
5
5
  <h1 align="center">Kerf</h1>
6
6
 
7
- <p align="center"><em>The smallest cut.</em></p>
7
+ <p align="center"><em>A tiny reactive UI framework. The smallest cut.</em></p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://brianwestphal.github.io/kerf/"><strong>brianwestphal.github.io/kerf</strong></a> — docs · examples · live demo
11
11
  </p>
12
12
 
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/kerfjs"><img src="https://img.shields.io/npm/v/kerfjs.svg" alt="npm version" /></a>
15
+ <img src="https://img.shields.io/badge/min%2Bgzip-~12%20KB-brightgreen.svg" alt="~12 KB minified and gzipped" />
16
+ <img src="https://img.shields.io/npm/l/kerfjs.svg" alt="MIT license" />
17
+ <img src="https://img.shields.io/badge/types-included-3178c6.svg" alt="TypeScript types included" />
18
+ </p>
19
+
13
20
  ---
14
21
 
15
- > Introducing Kerf.
16
- > The smallest cut.
17
- >
18
22
  > ~12 KB. No virtual DOM. No compiler. No magic.
19
23
  > Reactive UI that touches only the bytes that changed.
20
24
 
21
25
  ```ts
22
- import { signal, mount } from 'kerfjs';
26
+ import { signal, mount, delegate } from 'kerfjs';
23
27
 
24
28
  const count = signal(0);
29
+ const app = document.getElementById('app')!;
25
30
 
26
- mount(document.getElementById('app')!, () => (
31
+ mount(app, () => (
27
32
  <div>
28
33
  <button data-action="inc">+</button>
29
34
  <span>{count.value}</span>
30
35
  </div>
31
36
  ));
37
+
38
+ delegate(app, 'click', '[data-action="inc"]', () => count.value++);
32
39
  ```
33
40
 
34
41
  That's it. Your JSX renders to HTML strings, kerf's native diff applies the minimum DOM mutations to make the live tree match, and signals re-run the render only when something they read actually changed.
35
42
 
36
43
  Here's the whole development loop — write a component, run the dev server, click around, edit, watch the browser pick it up:
37
44
 
38
- [![Animated coding session: a counter component is typed line by line into an editor, npm run dev starts in a terminal and the localhost link is clicked, the running app is clicked in a browser, then back in the editor a computed class is added — selecting "btn" and typing a bound {cls} hole — and the browser shows the button change color at the fifth click](https://brianwestphal.github.io/kerf/demos/getting-started.svg)](https://brianwestphal.github.io/kerf/getting-started/)
39
-
40
- ## Why Kerf
45
+ [![Animated demo: coding a kerf counter in an editor, running the dev server, then hot-reloading a class change in the browser](https://brianwestphal.github.io/kerf/demos/getting-started.svg)](https://brianwestphal.github.io/kerf/getting-started/)
46
+ **[Quick start](#quick-start) · [Why kerf](#why-kerf) · [Quick tour](#quick-tour) · [Docs & examples](https://brianwestphal.github.io/kerf/)**
41
47
 
42
- 1. **Small bundle.** ~12 KB minified + gzipped including `@preact/signals-core` (~13 KB with `arraySignal`). One runtime dependency. No virtual DOM, no scheduler, no concurrent-mode machinery. On the official [krausest js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html) — where kerf is a listed entry, measured on the same reference machine as every competitor ([local mirror](./bench/results.md)) — kerf is in the same cluster as Vue, vanjs, and Lit on most operations; Solid's compiler leads the update-path benchmarks (notably `partial update`), which kerf doesn't try to match by design — no compiler.
48
+ ## Quick start
43
49
 
44
- 2. **No virtual DOM, no compiler.** JSX → HTML strings → native diff. DevTools shows the real DOM because it *is* the DOM.
45
-
46
- 3. **Values bind, structure re-renders.** Hand a signal *itself* into a JSX hole — `class={selectedId}` or `{status}` — and kerf binds that one node directly: when the signal changes, only that attribute or text node updates, with no render re-run and no list reconcile. A selection flip on a 10,000-row table touches exactly one class. Taken to its logical end: a mount whose render reads no `.value` at all runs **exactly once, forever** — every subsequent update flows through the per-hole bindings. Read `.value` in the render only when the *structure* depends on it (conditionals, list shape).
50
+ ```bash
51
+ npm install kerfjs
52
+ ```
47
53
 
48
- 4. **Focus, selection, listeners survive re-renders — even mid-list.** The reconciler morphs instead of rebuilding, so caret position, selection range, IME composition, and delegated listeners survive every re-render. Keyed lists get the same treatment: same-identity rows are updated *in place* rather than recreated, so a row reorder or a single-cell edit no longer blows away focus, scroll, or an in-flight animation the way node replacement does.
54
+ ```jsonc
55
+ // tsconfig.json — point JSX at kerf
56
+ { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "kerfjs" } }
57
+ ```
49
58
 
50
- 5. **Safe by default.** Text and attribute values are HTML-escaped automatically, URL attributes are scheme-screened (`javascript:` / script-carrying `data:` dropped), inline `on*` handlers are rejected outright, and the same screening covers the fine-grained bound path — so untrusted data stays inert even when kerf is dropped into someone else's page. The URL screen fails loudly at your desk (throws in development) and degrades safely in the field (warns and drops in production). `raw()` is the explicit, auditable opt-out.
59
+ Write plain `.tsx` and build with your existing esbuild / Vite / tsup no extra plugin. New here? Read the [5-minute orientation](https://github.com/brianwestphal/kerf/blob/main/docs/orientation.md), or open a [complete example](https://brianwestphal.github.io/kerf/examples/complete/).
60
+ ## Why Kerf
51
61
 
52
- 6. **JSX typed against HTML, not against React.** Tags and attributes are checked at compile time `<diiv>` and `<input typo />` don't build. The attribute types are derived from the HTML standard rather than another framework's property table, and that distinction has teeth: `draggable` and `spellcheck` are *enumerated* attributes that take the strings `"true"` / `"false"`, so kerf rejects `draggable={true}` rather than quietly emitting markup that means the opposite. Custom elements and web components slot in with one declaration merge.
62
+ 1. **~12 KB, one dependency.** ~12 KB minified + gzipped including `@preact/signals-core` (~13 KB with `arraySignal`). No virtual DOM, no scheduler, no concurrent-mode machinery. On the official [krausest benchmark](https://krausest.github.io/js-framework-benchmark/current.html) kerf sits in the same cluster as Vue, Lit, and vanjs; Solid's compiler leads the update-path benchmarks, which kerf doesn't try to match by design no compiler.
53
63
 
54
- 7. **Small public API.** ~18 exports from the main barrel (plus `arraySignal`, the `html` tagged template, and the companion-utility subpaths below each opt-in, none in the core). No hooks, no lifecycle, no per-instance state. Components are plain functions that return JSX.
64
+ 2. **No virtual DOM, no compiler.** JSX HTML strings native diff. DevTools shows the real DOM because it *is* the DOM.
55
65
 
56
- 8. **Batteries on their own subpaths.** Nine optional, tree-shakeable subpaths cover the patterns every real app otherwise hand-rolls **`kerfjs/list`** (a keyed list with per-row fine-grained mounts and fixed / app-declared / measured-height viewport **virtualization**, plus a `content-visibility` mode that keeps every row find-in-page-able), **`kerfjs/router`** (a "postcard **router**": route matching, `navigate`, auto `<a>` link interception, and a keyed outlet the *core* stays router-free, this is opt-in), **`kerfjs/overlay`** (modals, `confirm` / `prompt` / `form` / `choice`, anchored popovers + tooltips, toasts — with opt-in native **top-layer** backing that stacks above any `z-index`), **`kerfjs/async`** (`resource` async-state with a built-in stale-response guard + SWR cache), **`kerfjs/scope`** (dispose-scopes that tie teardown to a DOM node's lifetime), plus `timing`, `remount`, `attach`, and `actions`. None of them grows the ~12 KB core until you import it.
66
+ 3. **Values bind, structure re-renders.** Hand a signal *itself* into a JSX hole `class={selectedId}` — and kerf binds that one node: on change, only that attribute updates, with no render re-run and no list reconcile. Moving selection between rows in a 10,000-row table touches at most the old and new row classes. ([more →](#fine-grained-updates-bind-a-signal-into-a-hole))
57
67
 
58
- 9. **Plain TS, plain JSX, plain ESM.** Drops into anything using esbuild / Vite / tsup. No plugin chain. And with the `html` tagged template (`import { html } from 'kerfjs/html'` identical runtime semantics to JSX), a CDN / importmap project needs no build step at all.
68
+ 4. **Focus, selection, and listeners survive re-renders even mid-list.** The reconciler morphs instead of rebuilding, so caret position, IME composition, scroll, and delegated listeners survive every update; keyed rows are patched in place rather than recreated.
59
69
 
60
- 10. **Grown-up tooling around a tiny core.** An [ESLint plugin](https://brianwestphal.github.io/kerf/docs/eslint-plugin/) that enforces the hard rules at edit time, an opt-in family of `KERF_DEV_WARN_*` runtime warnings that catch the classic mistakes in development (with zero production cost), a `create-kerf-component` scaffold for publishable component packages, drop-in AI-assistant configs, and side-by-side migration guides for a dozen-plus frameworks — none of which grows the core runtime past ~12 KB.
70
+ 5. **Safe by default.** Text and attributes are HTML-escaped automatically, URL attributes are scheme-screened (`javascript:` dropped), and inline `on*` handlers are rejected outright so untrusted data stays inert. `raw()` is the explicit, auditable opt-out.
61
71
 
72
+ **Plus, nothing you don't ask for:** JSX typed against the HTML standard (not React's props) · a ~18-export API with no hooks, lifecycle, or per-instance state · **nine** tree-shakeable companion subpaths (`router`, `list`, `overlay`, `async`, …) that stay out of the core until imported · an [ESLint plugin](https://brianwestphal.github.io/kerf/docs/eslint-plugin/) + opt-in dev warnings + `create-kerf-component` scaffold · plain TS/JSX/ESM that drops into esbuild / Vite / tsup — or **no** build at all via the `html` tagged template.
62
73
  ## When to use Kerf
63
74
 
64
75
  - **Hybrid desktop apps (Tauri / Electron)** — small bundle, predictable diff, debuggable runtime; ideal for the embedded webview.
@@ -147,7 +158,7 @@ mount(root, () => (
147
158
  status.value = 'saving'; // updates the class + the text node directly — no re-render
148
159
  ```
149
160
 
150
- The headline use is external state driving one spot: a `selectedId` flipping a single row's class inside a 10,000-row `each()` list touches exactly that one node, no reconcile. Works in static content and inside `each()` rows (a row's binding is torn down with the row); outside a `mount()` (SSR / `SafeHtml.toString()`) a bound signal just snapshots its current value.
161
+ The headline use is external state driving a hot spot: a `selectedId` moving between rows inside a 10,000-row `each()` list updates at most the old and new row class attributes, with no reconcile. Works in static content and inside `each()` rows (a row's binding is torn down with the row); outside a `mount()` (SSR / `SafeHtml.toString()`) a bound signal just snapshots its current value.
151
162
 
152
163
  This is kerf's guiding idiom — *values bind, structure re-renders*: pass the signal itself wherever a hole is just a value, and read `.value` in the render function only where the JSX structure depends on it. A render that reads no `.value` runs exactly once; from then on every update is a direct write to the node it concerns. See [`docs/2-reactivity.md`](./docs/2-reactivity.md) §2.9.
153
164
 
@@ -247,21 +258,9 @@ mount(app, () => <div><nav>{/* <a href> links, auto-intercepted */}</nav>{router
247
258
 
248
259
  Each subpath adds nothing to the main barrel until it's imported. See [`docs/8-api-reference.md`](./docs/8-api-reference.md) for the full list (`list`, `router`, `overlay`, `scope`, `async`, `timing`, `remount`, `attach`, `actions`).
249
260
 
250
- ## Install
251
-
252
- ```bash
253
- npm install kerfjs
254
- ```
261
+ ## Optional tooling
255
262
 
256
- ```jsonc
257
- // tsconfig.json
258
- {
259
- "compilerOptions": {
260
- "jsx": "react-jsx",
261
- "jsxImportSource": "kerfjs"
262
- }
263
- }
264
- ```
263
+ Install and JSX setup are in [Quick start](#quick-start) above. These companion packages are opt-in.
265
264
 
266
265
  ### Optional: `eslint-plugin-kerfjs`
267
266
 
package/ai/cursorrules CHANGED
@@ -1,4 +1,4 @@
1
- <!-- kerf-skill-version: 1.14.2 -->
1
+ <!-- kerf-skill-version: 1.20.0 -->
2
2
  # kerf.cursorrules — rules for building apps with kerf
3
3
  #
4
4
  # Drop this file into your project as `.cursorrules` (Cursor will pick it
@@ -17,6 +17,51 @@ You are writing a UI in kerf — a ~12 KB reactive framework (~13 KB with `array
17
17
  - **A component package must NEVER import `kerfjs/dev`.** The hooks are process-global, so installing them is the consuming app's decision — a library that does it forces the diagnostics (and the chunk) on every consumer. Put the import in your demo page or test harness instead.
18
18
  - Recommended: also install `eslint-plugin-kerfjs` (`npm install --save-dev eslint-plugin-kerfjs`) and add `kerfjs.configs.recommended` to the project's eslint config. It enforces five of the hard rules below (no inline JSX event handlers, require `data-key` in `each()`, capture `delegate()` disposers, no nested `mount()`, prefer module JSX augmentation) at edit time so violations surface as IDE squiggles before any code runs.
19
19
 
20
+ ## Optional first-party UI
21
+
22
+ Install `@kerfjs/ui` for shared toolbars, menu rows, controlled tab bars, headers/value tables,
23
+ resizable regions, selects, banners, empty states, and loading indicators.
24
+ Import visual components from explicit subpaths so a CSS-aware browser bundler
25
+ includes only their reachable styles. The root barrel and
26
+ `@kerfjs/ui/unstyled` are CSS-free; pair the barrel with `styles.css` only when
27
+ the complete layer is intentional. Reuse these primitives before creating
28
+ parallel local ones; keep domain state, commands, routing, and tab/menu policy
29
+ in the app.
30
+
31
+ Use MenuItem/MenuActionRow/MenuHeader `rootAttributes` only for application
32
+ `data-*` metadata. MenuActionRow `trailingActionAttributes` and MenuHeader
33
+ `triggerAttributes` additionally support native popover target/action and
34
+ `aria-controls`/`aria-haspopup`. Do not use these slots to override action,
35
+ item identity, selection, disclosure, naming, disabled, icon, or role
36
+ semantics; one `role="menuitem"` does not make a complete menu widget.
37
+ `MenuItem.trailing` is dormant. Use `MenuActionRow` for sibling primary and
38
+ trailing native buttons, and keep its `label`, `icon`, and
39
+ `trailingActionIcon` SafeHtml slots free of controls.
40
+
41
+ Wire emitted `data-action` hooks at the mount root and retain disposers.
42
+ `wireResizableRegions()` and `wireTabBars()` are eventful helpers and return
43
+ disposers. Compose `AppTab` inside controlled `TabBar`; `wireTabBars()` supplies
44
+ horizontal edge autoscroll while dragging. Apply reorder reports
45
+ with `reorderTabs()`, and keep order, selection, close policy, routing, panels,
46
+ and persistence in the host. Icons/spinners are decorative unless
47
+ labeled, and assertive banners are only for urgent interruption.
48
+
49
+ The opinionated semantic ramps match Hot Sheet 2 and Web Awesome. Override
50
+ `--kui-color-*` globally or component properties such as
51
+ `--kui-state-banner-background` at a tone or instance boundary; do not replace
52
+ private descendant selectors.
53
+
54
+ For Web Awesome's broader free component set, import the CSS-only
55
+ `@kerfjs/ui/webawesome.css` theme once, then import only each Web Awesome
56
+ component module the app renders. The theme registers no component JavaScript
57
+ and remains overridable through later or scoped `--wa-*` values.
58
+
59
+ `Select` is pure markup until the app explicitly imports
60
+ `@kerfjs/ui/select/register`. Do that once in the application entry that uses
61
+ Select; never hide registration inside a component. Web Awesome is an optional
62
+ peer and stays out of bundles that use neither Select nor the Web Awesome theme. More detail ships at
63
+ `node_modules/@kerfjs/ui/ai/skill.md`.
64
+
20
65
  ## Public API — one import path
21
66
 
22
67
  ```ts
package/ai/manifest.json CHANGED
@@ -1,21 +1,78 @@
1
1
  {
2
- "kerfjsVersion": "4.4.0",
2
+ "kerfjsVersion": "5.0.0-beta.3",
3
3
  "files": [
4
4
  {
5
5
  "name": "skill",
6
6
  "source": "kerf.claude-skill.md",
7
7
  "bundle": "ai/skill.md",
8
8
  "dest": ".claude/skills/kerf-app/SKILL.md",
9
- "version": "1.14.2",
10
- "sha256": "3a9e4dd8d4ecfcf963e8d16f98ea98800a3c243f11bbb829134d8f7792a8d06d"
9
+ "version": "1.21.0",
10
+ "sha256": "265b747eeb9c3d445c60d66a1109eec42705075b7cd154d107bd79d543c1e7e5",
11
+ "history": {
12
+ "1.0.0": "bef982cb743fd4c73e666ee5cf774112e57c93ebb13b24bbacbf08577db0930a",
13
+ "1.1.0": "f2aadb21e33dd389028951ae1ecbd9f8ba68648b3c5beba43fb4dfa2ab29f195",
14
+ "1.1.1": "f163d33ce1007dd24551503398160881377eedad1fb4f6d37d9d9b8c8100f143",
15
+ "1.2.0": "76550ec18da8e063123bfabc575d9ad3a43583d41a498c6e4b4be03c17dbd188",
16
+ "1.2.1": "290e57bfc9b4af0630c586770e61063742d5f5fac3d75406f5acbc2e5322440f",
17
+ "1.3.0": "789cfd9bbe0fa8a01b779595912b7fef569b3c021be412d35f453476e576bac6",
18
+ "1.4.0": "2c42d476174847cfca1aa941c1026195b6a1786074fa9c2bd0b5a287639543b7",
19
+ "1.5.0": "c4fae8806907e79507ac095a2e6fed1761ddf61e5c9bfa5ec28719897ea56613",
20
+ "1.6.0": "797b48ef0736b9113df9e300ab47109c83c5cdd582f110d0cde7f388a37f4a26",
21
+ "1.7.0": "033c6e538d3a48f36c52a1ba154c6c7782f6c7cca9399adee0fbd519cfaf5919",
22
+ "1.8.0": "76b8a88bc7aaf9d50e6f3eee7b57a976b3279127f5d4970599aef1f839a06655",
23
+ "1.8.1": "32b8239399d94b014927563c3ff4251df97c36dc31478d832dba5a4309c221fb",
24
+ "1.9.0": "3225206e0339a8f6e2f9c728b76cf22728983f5810d614eab6224f16c8f9c6d4",
25
+ "1.10.0": "4b8e73a44fba060cb2fc4c01ed87f3853ac13906ee759e982c5c140025dfd493",
26
+ "1.11.0": "4d7f6099cd47f76a9e85cfe953886eaeb650eeb11a540cbac45104362b32eda5",
27
+ "1.12.0": "7acf72fd1b2781054520cc5af66cd73ee2f44b43c643e981f1f7a92ead60f747",
28
+ "1.13.0": "6f9a7d00ff6ad4799c51b52da613dfc5c11f09abaf9b6a3b9ca960f05f30b927",
29
+ "1.14.0": "15786c4e2b5f5d51dcd17d973f29aef8348faaa1410e40d86259be599fb459c9",
30
+ "1.14.1": "15573973dabf6d53f67e51696c276732fb1d17a59bb843300e88200389305b1f",
31
+ "1.14.2": "3a9e4dd8d4ecfcf963e8d16f98ea98800a3c243f11bbb829134d8f7792a8d06d",
32
+ "1.15.0": "72db3f328ce4457bbecd31bfab6ee69b2d423d9bee7db8e2a01e7aa3ae369af9",
33
+ "1.16.0": "0fa5d8ef354059fe7e20843dcd0c6f38234f3018d47856503bf47ddff4541231",
34
+ "1.17.0": "e0e1f370a97bc94aa33ed7723ebcf0059a9c7613f1083e3b77f0c62aecc108a7",
35
+ "1.18.0": "d2d092b1e4d4d71012f9ec36921b3f5c1421f0a8b2ced50351d56daa9e35f79b",
36
+ "1.19.0": "bbbe87446cc4e1be250d27152adfd385431f4498cde320534f65f10f853ab7f9",
37
+ "1.20.0": "aa88ccdbf969624bbc6cefadba76ada1f090fae436597a7bab564e24c467287f",
38
+ "1.21.0": "265b747eeb9c3d445c60d66a1109eec42705075b7cd154d107bd79d543c1e7e5"
39
+ }
11
40
  },
12
41
  {
13
42
  "name": "cursorrules",
14
43
  "source": "kerf.cursorrules",
15
44
  "bundle": "ai/cursorrules",
16
45
  "dest": ".cursorrules",
17
- "version": "1.14.2",
18
- "sha256": "09d9f0290fe84d3244e1655c8284c8431b47cdbe220f7161f38bc96fc8ef8ba3"
46
+ "version": "1.20.0",
47
+ "sha256": "c4dac40630fc456c6c7e6ba9d1b75215bc76bd76e6f31ba36d3bc9ab867d46e0",
48
+ "history": {
49
+ "1.0.0": "98f7faff2b7585ef9cc511add42ea5d9bf0cbae8307b066af090e9281136d2ab",
50
+ "1.1.0": "a2ec8c221dc694c98b81009cd2d99f9159a45fda94585de2f54471b227099eda",
51
+ "1.1.1": "f28b963171b7c2cbc36823d7220080730cda85a41f105b90e81790c1188e6186",
52
+ "1.2.0": "dcf2fc03f5dfca3b9bfea8cfefc65ac8ce31e1c601c613e91af69b6195c5e267",
53
+ "1.2.1": "2fcd5e9e0fd889febdab19bcc7e415c342b10ae81e937852b8280014b8d374e0",
54
+ "1.3.0": "2524a42cc3b488f8cc3cea736cf5b8d3e57d868c85260342ec50905243aa65f1",
55
+ "1.4.0": "f2c0fb328d487cc4a64ccd0b671c3385f1a25074c8c5e502c507ee799cd65e80",
56
+ "1.5.0": "2c012cbffa2fd9f96a3b8cdca48b55e584530ee6e5be73c922d3bb49470b6c31",
57
+ "1.6.0": "9370876e58ac634936ca321bf5313bb339659cb7c7c1b38a9b10f2bf4b03836d",
58
+ "1.7.0": "8ce556caf8fa95f783f5423bf882a735fc3ce6b7eb83e823aaa0417b3272af15",
59
+ "1.8.0": "f91402df009941b4198ea659498f458d2433843d47a3b524b0ca5e2d3d2fc949",
60
+ "1.8.1": "eed385b8edf3b96e88d8f00ea45ee4146004a74d3f0e495b870a27c137d0ec44",
61
+ "1.9.0": "c3242e1aa8c5ca51fca3e2e3c3df8e55dffb5d6c7b5bc8d677949d48ff5563cc",
62
+ "1.10.0": "7adefcc5ab66fcc3e5ae10625d7c762e86ac02ca297648aa58b0b201b35b04f8",
63
+ "1.11.0": "506c3338ec1c2268912f5eb9530a6af04ad937061281b5ea84529fc8b10486ff",
64
+ "1.12.0": "48ff3d0f483a9fcd7f57ca20c17c0aadfce0e4a0b39d9e96e964ef9260f782d3",
65
+ "1.13.0": "3a6bf9b1c74e0be00053faddc2af3c979ba5bb26c11db93bfcb8b1ecbe66284d",
66
+ "1.14.0": "b83de9cff13789cdebf572f2e98d3d61fdd4f31c7eb237a0a4999b6a1dfe4d9b",
67
+ "1.14.1": "9d2cc9da69e974a56d79393ad3b3a9d76c068fbd46b392621790a82fc0772d00",
68
+ "1.14.2": "09d9f0290fe84d3244e1655c8284c8431b47cdbe220f7161f38bc96fc8ef8ba3",
69
+ "1.15.0": "eece0b3075ceeeec5f6a59393a82c105d28ad1a34374412fbf6a4e0c23d7e8b9",
70
+ "1.16.0": "49794d543ab1f9112f9f6a1dcdc7aad4347b8965e1b4003bb7884086a02411c3",
71
+ "1.17.0": "6ad51fb0eec468f584346d1d7b4d1c2eca2cb52b04891b38ba44e93b57eb9e1c",
72
+ "1.18.0": "de3eea1bc181da629694d19a0817d252a868e4704437fbdd9e923856b8a0890a",
73
+ "1.19.0": "c5b91e449f6feccaa36a04cb7f8550d3e414eb2795ea6c5dccdf6071206b3261",
74
+ "1.20.0": "c4dac40630fc456c6c7e6ba9d1b75215bc76bd76e6f31ba36d3bc9ab867d46e0"
75
+ }
19
76
  }
20
77
  ]
21
78
  }
package/ai/skill.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: kerf-app
3
- description: Build UIs in the kerf reactive framework (https://github.com/brianwestphal/kerf). Use this skill whenever the user is writing or modifying code that imports `kerfjs`, asks to add a feature to a kerf app, or asks "how do I do X in kerf?". Use it proactively the moment you spot a kerf import in the file you're editing.
4
- kerf-skill-version: 1.14.2
3
+ description: Build UIs in the kerf reactive framework and its @kerfjs/ui component package (https://github.com/brianwestphal/kerf). Use this skill whenever the user is writing or modifying code that imports `kerfjs` or `@kerfjs/ui`, asks to add a feature to a kerf app, or asks "how do I do X in kerf?". Use it proactively the moment you spot a kerf import in the file you're editing.
4
+ kerf-skill-version: 1.21.0
5
5
  ---
6
6
 
7
7
  # Building apps with kerf
@@ -22,6 +22,57 @@ kerf is a ~12 KB reactive UI framework (~13 KB with `arraySignal`): signals + DO
22
22
  - **A component package must NEVER import `kerfjs/dev`.** The hooks are process-global, so installing them is the consuming app's decision — a library that does it forces the diagnostics (and the chunk) on every consumer. Put the import in your demo page or test harness instead.
23
23
  - Recommended companion: `npm install --save-dev eslint-plugin-kerfjs` and add `kerfjs.configs.recommended` to the project's eslint config. Enforces five of the hard rules below (no inline JSX event handlers, require `data-key` in `each()`, capture `delegate()` disposers, no nested `mount()`, prefer module JSX augmentation) at edit time — useful as a self-correction signal when authoring kerf code.
24
24
 
25
+ ## Optional first-party UI
26
+
27
+ Install `@kerfjs/ui` when the app needs shared toolbars, menu rows, controlled tab bars,
28
+ headers/value tables, resizable regions, selects, banners, empty states, or
29
+ loading indicators. Import visual components from explicit subpaths so a
30
+ CSS-aware browser bundler includes only their reachable styles. The root barrel
31
+ and `@kerfjs/ui/unstyled` are CSS-free; pair the barrel with `styles.css` only
32
+ when the complete layer is intentional. Prefer a component before inventing a
33
+ parallel local primitive, but keep domain state, routing, commands, and tab/menu
34
+ policy in the app.
35
+
36
+ Use MenuItem/MenuActionRow/MenuHeader/AppTab `rootAttributes` only for application
37
+ `data-*` metadata. MenuActionRow `trailingActionAttributes` and MenuHeader
38
+ `triggerAttributes` additionally support native popover target/action and
39
+ `aria-controls`/`aria-haspopup`. Do not use these slots to override action,
40
+ item identity, selection, disclosure, naming, disabled, icon, or role
41
+ semantics; one `role="menuitem"` does not make a complete menu widget.
42
+ `MenuItem.trailing` is dormant. Use `MenuActionRow` for sibling primary and
43
+ trailing native buttons, and keep its `label`, `icon`, and
44
+ `trailingActionIcon` SafeHtml slots free of controls.
45
+ AppTab protects its component/action/identity/selection and transient drag/drop
46
+ attributes at runtime. Keep `AppTab.closeIcon` and
47
+ `ResizableRegion.handleIcon` decorative and free of controls; the existing
48
+ named controls and wiring retain interaction and disposal ownership.
49
+
50
+ Components emit stable `data-action` hooks; wire them at the mount root and
51
+ retain every disposer. `wireResizableRegions()` and `wireTabBars()` are the
52
+ explicit behavioral helpers and return disposers. Compose `AppTab` inside a
53
+ controlled `TabBar`; `wireTabBars()` supplies horizontal edge autoscroll during
54
+ dragging. Apply reorder reports with `reorderTabs()`, and keep order,
55
+ selection, close policy, routing, panels, and persistence in the host.
56
+ Icons and spinners are decorative unless labeled; use assertive banners only
57
+ for urgent interruption.
58
+
59
+ The opinionated semantic ramps match Hot Sheet 2 and Web Awesome. Override
60
+ `--kui-color-*` globally or component properties such as
61
+ `--kui-state-banner-background` at a tone or instance boundary; do not replace
62
+ private descendant selectors.
63
+
64
+ For Web Awesome's broader free component set, import the CSS-only
65
+ `@kerfjs/ui/webawesome.css` theme once, then import only each Web Awesome
66
+ component module the app renders. The theme registers no component JavaScript
67
+ and remains overridable through later or scoped `--wa-*` values.
68
+
69
+ `Select` renders pure Web Awesome markup. Import
70
+ `@kerfjs/ui/select/register` once in an application entry that uses it; never
71
+ hide that registration inside another component. Web Awesome is an optional
72
+ peer and must remain absent from bundles that use neither Select nor the Web
73
+ Awesome theme. The package's
74
+ full AI contract is at `node_modules/@kerfjs/ui/ai/skill.md`.
75
+
25
76
  ## Public API — one import path
26
77
 
27
78
  ```ts
package/dist/actions.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AttrSpec } from './attrSelector-Cmu2ZoGO.js';
1
+ import { A as AttrSpec } from './attr-Cmu2ZoGO.js';
2
2
  import { D as DelegateOptions } from './delegate-CL9VTZFb.js';
3
3
 
4
4
  /**
package/dist/actions.js CHANGED
@@ -1,6 +1,6 @@
1
- import { attr } from './chunk-U32TFTGZ.js';
2
- import { delegate } from './chunk-KEZTD6H4.js';
3
- import './chunk-VVDJLWMP.js';
1
+ import { attr } from './chunk-UZJ6I4T6.js';
2
+ import { delegate } from './chunk-KPXIOG2C.js';
3
+
4
4
 
5
5
  // src/actions.ts
6
6
  var DEFAULT_ACTION_ATTR = "data-action";
@@ -22,5 +22,5 @@ function delegateActions(root, eventType, table, options) {
22
22
  }
23
23
 
24
24
  export { action, delegateActions };
25
- //# sourceMappingURL=actions.js.map
25
+
26
26
  //# sourceMappingURL=actions.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";;;;;AAmCA,IAAM,mBAAA,GAAsB,aAAA;AAerB,SAAS,OAAyB,KAAA,EAAsC;AAC7E,EAAA,OAAO,IAAA,CAAK,qBAAqB,KAAK,CAAA;AACxC;AAwBO,SAAS,eAAA,CACd,IAAA,EACA,SAAA,EACA,KAAA,EACA,OAAA,EACY;AACZ,EAAA,MAAM,QAAA,GAAW,SAAS,IAAA,IAAQ,mBAAA;AAClC,EAAA,OAAO,QAAA;AAAA,IACL,IAAA;AAAA,IACA,SAAA;AAAA,IACA,IAAI,QAAQ,CAAA,CAAA,CAAA;AAAA,IACZ,CAAC,OAAO,EAAA,KAAO;AAEb,MAAA,MAAM,OAAA,GAAU,KAAA,CAAM,EAAA,CAAG,YAAA,CAAa,QAAQ,CAAW,CAAA;AACzD,MAAA,IAAI,OAAA,KAAY,MAAA,EAAW,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,IAC9C,CAAA;AAAA,IACA;AAAA,GACF;AACF","file":"actions.js","sourcesContent":["/**\n * `kerfjs/actions` — the delegated action-table helper.\n *\n * The most-reinvented idiom across real kerf apps: one table of `data-action`\n * attribute specs used as the single source of truth for BOTH the JSX attribute\n * and the delegate selector, plus a hand-rolled `switch (dataset.action)`\n * dispatcher. This subpath blesses it as two thin helpers over the existing\n * `attr()` + `delegate()` — it does NOT replace them.\n *\n * import { action, delegateActions } from 'kerfjs/actions';\n *\n * const A = {\n * select: action('select-file'),\n * remove: action('remove-file'),\n * };\n *\n * // JSX — spread the attr (rename-safe; no hardcoded attribute name):\n * // <button {...A.select.attrs} data-id={id}>…</button>\n *\n * // Wire the whole table with ONE delegated listener; returns a disposer:\n * const dispose = delegateActions(root, 'click', {\n * [A.select.value]: (_e, el) => selectFile(el.getAttribute('data-id')),\n * [A.remove.value]: (_e, el) => removeFile(el.getAttribute('data-id')),\n * });\n *\n * Contract: `delegateActions` returns a `() => void` disposer and holds no\n * per-instance state — the same shape as `delegate()`, which it builds on (so\n * it inherits the single-listener dispatch and the capture auto-promotion for\n * well-known non-bubbling event types). One event type per call, mirroring\n * `delegate()`; collect the disposers for a root that needs several.\n */\nimport { attr, type AttrSpec } from './attrSelector.js';\nimport { delegate, type DelegateOptions } from './delegate.js';\n\n/** The attribute an action table keys on by default. */\nconst DEFAULT_ACTION_ATTR = 'data-action';\n\n/**\n * A handler in a {@link delegateActions} table. Receives the DOM event and the\n * matched element (walk-up `closest()` match by default) — the same shape as a\n * `delegate()` handler.\n */\nexport type ActionHandler<E extends Element = Element> = (event: Event, el: E) => void;\n\n/**\n * `action(value)` — an {@link AttrSpec} on `data-action`. A thin specialization\n * of `attr('data-action', value)`: spread its `.attrs` in JSX and use its\n * `.value` as the handler-table key, so the action name lives in exactly one\n * place and can't drift between the markup and the dispatcher.\n */\nexport function action<V extends string>(value: V): AttrSpec<'data-action', V> {\n return attr(DEFAULT_ACTION_ATTR, value);\n}\n\n/** Options for {@link delegateActions}. Extends {@link DelegateOptions}. */\nexport interface DelegateActionsOptions extends DelegateOptions {\n /**\n * The attribute the table keys on. Default `'data-action'`. Override it only\n * if you also author the specs with `attr(yourName, …)` instead of `action()`.\n */\n attr?: string;\n}\n\n/**\n * Wire a whole table of action handlers with ONE delegated listener.\n *\n * On `eventType`, the nearest element carrying the action attribute (walk-up\n * `closest()` by default; pass `{ match: 'direct' }` for an exact-element match)\n * is looked up in `table` by its attribute value, and the matching handler\n * runs. An element whose action is absent from the table is ignored — the same\n * behavior as a `switch (dataset.action)` with no matching `case`.\n *\n * Returns a `() => void` disposer. One event type per call (the smallest\n * surface, mirroring `delegate()`); collect the disposers when a root needs\n * several event types.\n */\nexport function delegateActions<E extends Element = Element>(\n root: HTMLElement,\n eventType: string,\n table: Readonly<Record<string, ActionHandler<E>>>,\n options?: DelegateActionsOptions,\n): () => void {\n const attrName = options?.attr ?? DEFAULT_ACTION_ATTR;\n return delegate<E>(\n root,\n eventType,\n `[${attrName}]`,\n (event, el) => {\n // `el` matched `[${attrName}]`, so the attribute is always present.\n const handler = table[el.getAttribute(attrName) as string];\n if (handler !== undefined) handler(event, el);\n },\n options,\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";;;;;AAmCA,IAAM,mBAAA,GAAsB,aAAA;AAerB,SAAS,OAAyB,KAAA,EAAsC;AAC7E,EAAA,OAAO,IAAA,CAAK,qBAAqB,KAAK,CAAA;AACxC;AAwBO,SAAS,eAAA,CACd,IAAA,EACA,SAAA,EACA,KAAA,EACA,OAAA,EACY;AACZ,EAAA,MAAM,QAAA,GAAW,SAAS,IAAA,IAAQ,mBAAA;AAClC,EAAA,OAAO,QAAA;AAAA,IACL,IAAA;AAAA,IACA,SAAA;AAAA,IACA,IAAI,QAAQ,CAAA,CAAA,CAAA;AAAA,IACZ,CAAC,OAAO,EAAA,KAAO;AAEb,MAAA,MAAM,OAAA,GAAU,KAAA,CAAM,EAAA,CAAG,YAAA,CAAa,QAAQ,CAAW,CAAA;AACzD,MAAA,IAAI,OAAA,KAAY,MAAA,EAAW,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,IAC9C,CAAA;AAAA,IACA;AAAA,GACF;AACF","file":"actions.js","sourcesContent":["/**\n * `kerfjs/actions` — the delegated action-table helper.\n *\n * The most-reinvented idiom across real kerf apps: one table of `data-action`\n * attribute specs used as the single source of truth for BOTH the JSX attribute\n * and the delegate selector, plus a hand-rolled `switch (dataset.action)`\n * dispatcher. This subpath blesses it as two thin helpers over the existing\n * `attr()` + `delegate()` — it does NOT replace them.\n *\n * import { action, delegateActions } from 'kerfjs/actions';\n *\n * const A = {\n * select: action('select-file'),\n * remove: action('remove-file'),\n * };\n *\n * // JSX — spread the attr (rename-safe; no hardcoded attribute name):\n * // <button {...A.select.attrs} data-id={id}>…</button>\n *\n * // Wire the whole table with ONE delegated listener; returns a disposer:\n * const dispose = delegateActions(root, 'click', {\n * [A.select.value]: (_e, el) => selectFile(el.getAttribute('data-id')),\n * [A.remove.value]: (_e, el) => removeFile(el.getAttribute('data-id')),\n * });\n *\n * Contract: `delegateActions` returns a `() => void` disposer and holds no\n * per-instance state — the same shape as `delegate()`, which it builds on (so\n * it inherits the single-listener dispatch and the capture auto-promotion for\n * well-known non-bubbling event types). One event type per call, mirroring\n * `delegate()`; collect the disposers for a root that needs several.\n */\nimport { attr, type AttrSpec } from './attr.js';\nimport { delegate, type DelegateOptions } from './delegate.js';\n\n/** The attribute an action table keys on by default. */\nconst DEFAULT_ACTION_ATTR = 'data-action';\n\n/**\n * A handler in a {@link delegateActions} table. Receives the DOM event and the\n * matched element (walk-up `closest()` match by default) — the same shape as a\n * `delegate()` handler.\n */\nexport type ActionHandler<E extends Element = Element> = (event: Event, el: E) => void;\n\n/**\n * `action(value)` — an {@link AttrSpec} on `data-action`. A thin specialization\n * of `attr('data-action', value)`: spread its `.attrs` in JSX and use its\n * `.value` as the handler-table key, so the action name lives in exactly one\n * place and can't drift between the markup and the dispatcher.\n */\nexport function action<V extends string>(value: V): AttrSpec<'data-action', V> {\n return attr(DEFAULT_ACTION_ATTR, value);\n}\n\n/** Options for {@link delegateActions}. Extends {@link DelegateOptions}. */\nexport interface DelegateActionsOptions extends DelegateOptions {\n /**\n * The attribute the table keys on. Default `'data-action'`. Override it only\n * if you also author the specs with `attr(yourName, …)` instead of `action()`.\n */\n attr?: string;\n}\n\n/**\n * Wire a whole table of action handlers with ONE delegated listener.\n *\n * On `eventType`, the nearest element carrying the action attribute (walk-up\n * `closest()` by default; pass `{ match: 'direct' }` for an exact-element match)\n * is looked up in `table` by its attribute value, and the matching handler\n * runs. An element whose action is absent from the table is ignored — the same\n * behavior as a `switch (dataset.action)` with no matching `case`.\n *\n * Returns a `() => void` disposer. One event type per call (the smallest\n * surface, mirroring `delegate()`); collect the disposers when a root needs\n * several event types.\n */\nexport function delegateActions<E extends Element = Element>(\n root: HTMLElement,\n eventType: string,\n table: Readonly<Record<string, ActionHandler<E>>>,\n options?: DelegateActionsOptions,\n): () => void {\n const attrName = options?.attr ?? DEFAULT_ACTION_ATTR;\n return delegate<E>(\n root,\n eventType,\n `[${attrName}]`,\n (event, el) => {\n // `el` matched `[${attrName}]`, so the attribute is always present.\n const handler = table[el.getAttribute(attrName) as string];\n if (handler !== undefined) handler(event, el);\n },\n options,\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
- export { ARRAY_SIGNAL_BRAND, ArraySignal, arraySignal } from './chunk-MRYM3O3V.js';
2
- import './chunk-QIP723L4.js';
3
- import './chunk-3APBEVHF.js';
4
- import './chunk-VVDJLWMP.js';
5
- //# sourceMappingURL=array-signal.js.map
1
+ export { ARRAY_SIGNAL_BRAND, ArraySignal, arraySignal } from './chunk-LVH3GC6B.js';
2
+
3
+
4
+
5
+
6
6
  //# sourceMappingURL=array-signal.js.map