kerfjs 4.4.1 → 4.5.0-beta.1

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 (84) hide show
  1. package/CHANGELOG.md +270 -107
  2. package/LICENSE +1 -1
  3. package/README.md +45 -25
  4. package/ai/cursorrules +130 -48
  5. package/ai/manifest.json +63 -5
  6. package/ai/skill.md +148 -65
  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 +34 -24
  12. package/dist/async.js.map +1 -1
  13. package/dist/attach.d.ts +11 -8
  14. package/dist/attach.js +54 -4
  15. package/dist/attach.js.map +1 -1
  16. package/dist/{chunk-QIP723L4.js → chunk-5WRGJZV6.js} +4 -3
  17. package/dist/chunk-5WRGJZV6.js.map +1 -0
  18. package/dist/{chunk-GY4XV2UV.js → chunk-BDX3R4OM.js} +4 -3
  19. package/dist/chunk-BDX3R4OM.js.map +1 -0
  20. package/dist/{chunk-VVDJLWMP.js → chunk-CEQMZYLR.js} +2 -2
  21. package/dist/chunk-CEQMZYLR.js.map +1 -0
  22. package/dist/{chunk-KEZTD6H4.js → chunk-E5R5GNKE.js} +15 -5
  23. package/dist/chunk-E5R5GNKE.js.map +1 -0
  24. package/dist/{chunk-MRYM3O3V.js → chunk-ELXVRKY2.js} +12 -9
  25. package/dist/chunk-ELXVRKY2.js.map +1 -0
  26. package/dist/{chunk-U32TFTGZ.js → chunk-MK42GLPV.js} +3 -3
  27. package/dist/chunk-MK42GLPV.js.map +1 -0
  28. package/dist/{chunk-SUPUPSBE.js → chunk-QFUNWHKH.js} +42 -20
  29. package/dist/chunk-QFUNWHKH.js.map +1 -0
  30. package/dist/{chunk-SRWQKB33.js → chunk-V757BT6U.js} +206 -111
  31. package/dist/chunk-V757BT6U.js.map +1 -0
  32. package/dist/{chunk-YHH7OUFA.js → chunk-WKIPLNVO.js} +3 -3
  33. package/dist/chunk-WKIPLNVO.js.map +1 -0
  34. package/dist/{chunk-3APBEVHF.js → chunk-Y2FOYPBV.js} +3 -3
  35. package/dist/{chunk-3APBEVHF.js.map → chunk-Y2FOYPBV.js.map} +1 -1
  36. package/dist/{chunk-SAYPJ6XR.js → chunk-ZOIERTUW.js} +10 -6
  37. package/dist/chunk-ZOIERTUW.js.map +1 -0
  38. package/dist/dev.d.ts +9 -6
  39. package/dist/dev.js +66 -19
  40. package/dist/dev.js.map +1 -1
  41. package/dist/html.d.ts +1 -1
  42. package/dist/html.js +8 -7
  43. package/dist/html.js.map +1 -1
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.js +28 -19
  46. package/dist/index.js.map +1 -1
  47. package/dist/jsx-runtime.js +5 -5
  48. package/dist/list.d.ts +1 -1
  49. package/dist/list.js +304 -213
  50. package/dist/list.js.map +1 -1
  51. package/dist/overlay.d.ts +190 -280
  52. package/dist/overlay.js +471 -364
  53. package/dist/overlay.js.map +1 -1
  54. package/dist/remount.d.ts +5 -3
  55. package/dist/remount.js +29 -10
  56. package/dist/remount.js.map +1 -1
  57. package/dist/router.d.ts +1 -1
  58. package/dist/router.js +73 -28
  59. package/dist/router.js.map +1 -1
  60. package/dist/scope.d.ts +4 -3
  61. package/dist/scope.js +11 -10
  62. package/dist/scope.js.map +1 -1
  63. package/dist/testing.js +4 -4
  64. package/dist/timing.d.ts +3 -2
  65. package/dist/timing.js +5 -5
  66. package/dist/timing.js.map +1 -1
  67. package/llms.txt +13 -6
  68. package/package.json +35 -11
  69. package/setup/cli.mjs +90 -0
  70. package/setup/index.d.mts +37 -0
  71. package/setup/index.mjs +1347 -0
  72. package/setup/jsonc.mjs +201 -0
  73. package/setup/state.schema.json +56 -0
  74. package/dist/chunk-GY4XV2UV.js.map +0 -1
  75. package/dist/chunk-KEZTD6H4.js.map +0 -1
  76. package/dist/chunk-MRYM3O3V.js.map +0 -1
  77. package/dist/chunk-QIP723L4.js.map +0 -1
  78. package/dist/chunk-SAYPJ6XR.js.map +0 -1
  79. package/dist/chunk-SRWQKB33.js.map +0 -1
  80. package/dist/chunk-SUPUPSBE.js.map +0 -1
  81. package/dist/chunk-U32TFTGZ.js.map +0 -1
  82. package/dist/chunk-VVDJLWMP.js.map +0 -1
  83. package/dist/chunk-YHH7OUFA.js.map +0 -1
  84. /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
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
@@ -51,25 +51,40 @@ Here's the whole development loop — write a component, run the dev server, cli
51
51
  npm install kerfjs
52
52
  ```
53
53
 
54
+ For an existing project, install the AI guidance, strict TypeScript/ESLint
55
+ feedback, and package scripts with a reviewed dry run:
56
+
57
+ ```bash
58
+ npx kerfjs setup # inspect a value-level plan
59
+ npx kerfjs setup --write --yes # apply it
60
+ ```
61
+
62
+ The command detects core versus `@kerfjs/ui`, supports deterministic npm,
63
+ pnpm, and Yarn workspace selection plus manager-correct offline installs, and
64
+ never adds UI to a core-only project. See
65
+ [`docs/24-ai-first-setup.md`](./docs/24-ai-first-setup.md).
66
+
54
67
  ```jsonc
55
68
  // tsconfig.json — point JSX at kerf
56
69
  { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "kerfjs" } }
57
70
  ```
58
71
 
59
72
  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/).
73
+
60
74
  ## Why Kerf
61
75
 
62
76
  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.
63
77
 
64
- 2. **No virtual DOM, no compiler.** JSX → HTML strings → native diff. DevTools shows the real DOM because it *is* the DOM.
78
+ 2. **No virtual DOM, no compiler.** JSX → HTML strings → native diff. DevTools shows the real DOM because it _is_ the DOM.
65
79
 
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. A selection flip on a 10,000-row table touches exactly one class. ([more →](#fine-grained-updates-bind-a-signal-into-a-hole))
80
+ 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))
67
81
 
68
82
  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.
69
83
 
70
84
  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.
71
85
 
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 + a `create-kerf-component` scaffold · plain TS/JSX/ESM that drops into esbuild / Vite / tsup — or **no** build at all via the `html` tagged template.
86
+ **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.
87
+
73
88
  ## When to use Kerf
74
89
 
75
90
  - **Hybrid desktop apps (Tauri / Electron)** — small bundle, predictable diff, debuggable runtime; ideal for the embedded webview.
@@ -84,7 +99,7 @@ Write plain `.tsx` and build with your existing esbuild / Vite / tsup — no ext
84
99
  - Need a full ecosystem (router + forms + data + SSR streaming) → **Next.js / Remix / SolidStart**.
85
100
  - Building a deeply componentised design-system app → **React / Solid / Svelte**.
86
101
  - Need React Native / cross-platform mobile → **React** (Kerf + Tauri/Electron also covers many of these cases).
87
- - Building a static site → **Astro** (we use it for *this* project's site).
102
+ - Building a static site → **Astro** (we use it for _this_ project's site).
88
103
  - Already invested in a framework where switching cost outweighs the bundle size gain.
89
104
 
90
105
  ## Quick tour
@@ -144,7 +159,7 @@ delegate(root, 'click', REMOVE.selector, (_e, btn) => { /* … */ }); // in dele
144
159
 
145
160
  ### Fine-grained updates: bind a signal into a hole
146
161
 
147
- Inside a `mount()`, hand a signal *itself* (not its `.value`) into an attribute or text position and kerf wires that hole straight to the signal — the render function never re-runs and the list reconciler never walks:
162
+ Inside a `mount()`, hand a signal _itself_ (not its `.value`) into an attribute or text position and kerf wires that hole straight to the signal — the render function never re-runs and the list reconciler never walks:
148
163
 
149
164
  ```ts
150
165
  const status = signal('idle');
@@ -158,9 +173,9 @@ mount(root, () => (
158
173
  status.value = 'saving'; // updates the class + the text node directly — no re-render
159
174
  ```
160
175
 
161
- 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.
176
+ 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.
162
177
 
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.
178
+ 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.
164
179
 
165
180
  ### Long keyed lists: `arraySignal`
166
181
 
@@ -187,11 +202,11 @@ The class lives in its own subpath so apps that don't need it shed ~1 KB. Reads
187
202
  `mount()` wraps `effect()` so the render re-runs on signal changes. Sometimes you have a freshly-built template and an already-populated element and you just want to reconcile them once — no subscription, no re-render loop. That's `morph`:
188
203
 
189
204
  ```ts
190
- import { morph, raw } from 'kerfjs';
205
+ import { morph, raw } from "kerfjs";
191
206
 
192
- morph(liveCard, freshlyBuiltCardEl); // Element template
193
- morph(liveCard, '<article class="card">…</article>'); // raw HTML string
194
- morph(liveCard, raw(htmlFromServer)); // SafeHtml
207
+ morph(liveCard, freshlyBuiltCardEl); // Element template
208
+ morph(liveCard, '<article class="card">…</article>'); // raw HTML string
209
+ morph(liveCard, raw(htmlFromServer)); // SafeHtml
195
210
  ```
196
211
 
197
212
  Same algorithm `mount()` uses internally — `data-morph-skip`, `data-morph-skip-children`, `data-morph-preserve`, focused-input value + selection preservation, the `<details>` / `<dialog>` user-agent-owned `open` rule all carry over. Use it for SSR-fragment hydration, page-refresh diffs, third-party widget remounts. See [`docs/4-render.md`](./docs/4-render.md) §4.4.3.
@@ -202,14 +217,19 @@ Same algorithm `mount()` uses internally — `data-morph-skip`, `data-morph-skip
202
217
 
203
218
  ```html
204
219
  <script type="module">
205
- import { signal, mount, each } from 'https://esm.sh/kerfjs@4';
206
- import { html } from 'https://esm.sh/kerfjs@4/html';
207
-
208
- const items = signal([{ id: 1, label: 'no build step' }]);
209
-
210
- mount(document.getElementById('app'), () => html`
211
- <ul>${each(items.value, (i) => html`<li id="${i.id}">${i.label}</li>`)}</ul>
212
- `);
220
+ import { signal, mount, each } from "https://esm.sh/kerfjs@4";
221
+ import { html } from "https://esm.sh/kerfjs@4/html";
222
+
223
+ const items = signal([{ id: 1, label: "no build step" }]);
224
+
225
+ mount(
226
+ document.getElementById("app"),
227
+ () => html`
228
+ <ul>
229
+ ${each(items.value, (i) => html`<li id="${i.id}">${i.label}</li>`)}
230
+ </ul>
231
+ `,
232
+ );
213
233
  </script>
214
234
  ```
215
235
 
@@ -238,7 +258,7 @@ const list = bindList(scrollEl, messages, {
238
258
  observeRowHeights(list); // one ResizeObserver → kerf anchor-corrects scroll
239
259
  ```
240
260
 
241
- And a whole client-side router in one call — `kerfjs/router`, the "postcard router." A route table, a keyed `outlet()`, and automatic `<a href>` interception; the *core* stays router-free (this is opt-in):
261
+ And a whole client-side router in one call — `kerfjs/router`, the "postcard router." A route table, a keyed `outlet()`, and automatic `<a href>` interception; the _core_ stays router-free (this is opt-in):
242
262
 
243
263
  ```ts
244
264
  import { createRouter } from 'kerfjs/router';
@@ -272,7 +292,7 @@ npm install --save-dev eslint-plugin-kerfjs
272
292
 
273
293
  ```js
274
294
  // eslint.config.js (flat config, ESLint v9+)
275
- import kerfjs from 'eslint-plugin-kerfjs';
295
+ import kerfjs from "eslint-plugin-kerfjs";
276
296
  export default [kerfjs.configs.recommended];
277
297
  ```
278
298
 
@@ -280,7 +300,7 @@ Full docs at [brianwestphal.github.io/kerf/docs/eslint-plugin/](https://brianwes
280
300
 
281
301
  ### Optional: `create-kerf-component`
282
302
 
283
- Building a reusable component package? Scaffold one that already follows kerf's hard packaging rules (kerfjs as a peer dependency and `external` in the build, ESM + `.d.ts`, `jsxImportSource: "kerfjs"`, subpath exports) plus an example component showing per-instance state via a factory and a `wire(root)` delegation disposer:
303
+ Building a reusable component package? Scaffold one that already follows kerf's hard packaging rules (kerfjs as a peer dependency and `external` in the build, ESM + `.d.ts`, `jsxImportSource: "kerfjs"`, subpath exports), includes an example component showing per-instance state via a factory and a `wire(root)` delegation disposer, and generates checked package-qualified AI component metadata from explicit author decisions:
284
304
 
285
305
  ```bash
286
306
  npm create kerf-component@latest my-widgets
@@ -295,15 +315,15 @@ See [`docs/13-component-packages.md`](./docs/13-component-packages.md) for the f
295
315
  - **Migrating:** [coming from another framework?](https://brianwestphal.github.io/kerf/migrating/) — side-by-side TodoMVC translations + per-framework gotchas
296
316
  - **AI guide:** [`docs/ai/usage-guide.md`](./docs/ai/usage-guide.md) — reference for AI tools fetching kerf docs (linked from `llms.txt`)
297
317
  - **ESLint plugin:** [brianwestphal.github.io/kerf/docs/eslint-plugin/](https://brianwestphal.github.io/kerf/docs/eslint-plugin/) — `eslint-plugin-kerfjs`; eight rules (four hard-rule errors + four warns: `require-delegate-disposer`, `prefer-attr-selector`, `no-raw-with-dynamic-arg`, `ai-assistant-configs`) at edit time (source: [`eslint-plugin/`](./eslint-plugin/))
298
- - **Component scaffold:** `npm create kerf-component@latest <dir>` — `create-kerf-component`; generates a publishable component package with the hard packaging rules pre-wired (source: [`create-kerf-component/`](./create-kerf-component/))
318
+ - **Component scaffold:** `npm create kerf-component@latest <dir>` — `create-kerf-component`; generates a publishable component package with packaging rules plus deterministic, drift-checked AI metadata pre-wired (source: [`create-kerf-component/`](./create-kerf-component/))
299
319
  - **Demo:** [live demo](https://brianwestphal.github.io/kerf/demo/) — nine sections exercising every primitive (counter, store-backed cart, focus survival, keyed list, morph-skip, SVG render, Tier-2 capture, `arraySignal` patches, fine-grained signal bindings)
300
320
  - **Repo:** [github.com/brianwestphal/kerf](https://github.com/brianwestphal/kerf)
301
321
 
302
322
  ## Why "kerf"?
303
323
 
304
- A *kerf* is the narrow strip of material a saw blade removes when cutting — the smallest possible cut. The framework's job is the same: apply the smallest possible mutation to update your DOM.
324
+ A _kerf_ is the narrow strip of material a saw blade removes when cutting — the smallest possible cut. The framework's job is the same: apply the smallest possible mutation to update your DOM.
305
325
 
306
- (And yes, ~~kerformance~~ → *performance* jokes were written. They were also rejected.)
326
+ (And yes, ~~kerformance~~ → _performance_ jokes were written. They were also rejected.)
307
327
 
308
328
  ## Status
309
329
 
package/ai/cursorrules CHANGED
@@ -1,8 +1,13 @@
1
- <!-- kerf-skill-version: 1.14.2 -->
1
+ <!-- kerf-skill-version: 1.20.0 -->
2
+
2
3
  # kerf.cursorrules — rules for building apps with kerf
4
+
3
5
  #
6
+
4
7
  # Drop this file into your project as `.cursorrules` (Cursor will pick it
8
+
5
9
  # up automatically) when you're using kerf (https://github.com/brianwestphal/kerf).
10
+
6
11
  # These rules condense `docs/ai/usage-guide.md` into the form Cursor parses.
7
12
 
8
13
  You are writing a UI in kerf — a ~12 KB reactive framework (~13 KB with `arraySignal`): signals + DOM morphing + JSX → HTML strings. No virtual DOM, no compiler, no scheduler.
@@ -17,45 +22,100 @@ You are writing a UI in kerf — a ~12 KB reactive framework (~13 KB with `array
17
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.
18
23
  - 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
24
 
25
+ ## Optional first-party UI
26
+
27
+ Install `@kerfjs/ui` for shared toolbars, menu rows, controlled tab bars, headers/value tables,
28
+ resizable regions, selects, banners, empty states, and loading indicators.
29
+ Import visual components from explicit subpaths so a CSS-aware browser bundler
30
+ includes only their reachable styles. The root barrel and
31
+ `@kerfjs/ui/unstyled` are CSS-free; pair the barrel with `styles.css` only when
32
+ the complete layer is intentional. Reuse these primitives before creating
33
+ parallel local ones; keep domain state, commands, routing, and tab/menu policy
34
+ in the app.
35
+
36
+ Use MenuItem/MenuActionRow/MenuHeader `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
+
46
+ Wire emitted `data-action` hooks at the mount root and retain disposers.
47
+ `wireResizableRegions()` and `wireTabBars()` are eventful helpers and return
48
+ disposers. Compose `AppTab` inside controlled `TabBar`; `wireTabBars()` supplies
49
+ horizontal edge autoscroll while dragging. Apply reorder reports
50
+ with `reorderTabs()`, and keep order, selection, close policy, routing, panels,
51
+ and persistence in the host. Icons/spinners are decorative unless
52
+ labeled, and assertive banners are only for urgent interruption.
53
+
54
+ The opinionated semantic ramps match Hot Sheet 2 and Web Awesome. Override
55
+ `--kui-color-*` globally or component properties such as
56
+ `--kui-state-banner-background` at a tone or instance boundary; do not replace
57
+ private descendant selectors.
58
+
59
+ For Web Awesome's broader free component set, import the CSS-only
60
+ `@kerfjs/ui/webawesome.css` theme once, then import only each Web Awesome
61
+ component module the app renders. The theme registers no component JavaScript
62
+ and remains overridable through later or scoped `--wa-*` values.
63
+
64
+ `Select` is pure markup until the app explicitly imports
65
+ `@kerfjs/ui/select/register`. Do that once in the application entry that uses
66
+ Select; never hide registration inside a component. Web Awesome is an optional
67
+ peer and stays out of bundles that use neither Select nor the Web Awesome theme. More detail ships at
68
+ `node_modules/@kerfjs/ui/ai/skill.md`.
69
+
20
70
  ## Public API — one import path
21
71
 
22
72
  ```ts
23
73
  import {
24
- signal, computed, effect, batch, // reactivity
25
- defineStore, resetAllStores, // stores
26
- mount, morph, each, // render (reactive + one-shot) + keyed list
27
- delegate, delegateCapture, // events
28
- toElement, // direct JSX → DOM Element (or DocumentFragment for multi-root)
29
- SafeHtml, isSafeHtml, raw, Fragment,
30
- } from 'kerfjs';
74
+ signal,
75
+ computed,
76
+ effect,
77
+ batch, // reactivity
78
+ defineStore,
79
+ resetAllStores, // stores
80
+ mount,
81
+ morph,
82
+ each, // render (reactive + one-shot) + keyed list
83
+ delegate,
84
+ delegateCapture, // events
85
+ toElement, // direct JSX → DOM Element (or DocumentFragment for multi-root)
86
+ SafeHtml,
87
+ isSafeHtml,
88
+ raw,
89
+ Fragment,
90
+ } from "kerfjs";
31
91
 
32
92
  // Optional, only when you need granular collection updates:
33
- import { arraySignal } from 'kerfjs/array-signal';
93
+ import { arraySignal } from "kerfjs/array-signal";
34
94
 
35
95
  // Development diagnostics — gate with YOUR build's dev flag, in YOUR code.
36
- if (import.meta.env.DEV) await import('kerfjs/dev');
96
+ if (import.meta.env.DEV) await import("kerfjs/dev");
37
97
  ```
38
98
 
39
- | Export | Use |
40
- | --- | --- |
41
- | `signal(initial)` | atomic reactive state; read/write via `.value` |
42
- | `computed(fn)` | derived value (read-only) |
43
- | `effect(fn)` | side effect that re-runs on signal change |
44
- | `batch(fn)` | coalesce multiple writes into one re-run |
45
- | `defineStore({initial, actions})` | named multi-consumer state |
46
- | `resetAllStores()` | reset every store (test teardown) |
47
- | `mount(el, render)` | bind reactive render to a DOM element; returns a disposer |
48
- | `morph(liveRoot, template)` | one-shot reconcile against an already-populated element (SSR hydration, page-refresh diffs). Template can be `Element`, `SafeHtml`, or HTML string |
49
- | `each(items, render, cacheKey?)` | keyed list iteration; per-row memoization on object identity (+ optional cacheKey — a passive comparator for external state). Distinct from `data-key` on the rendered element |
50
- | `each(items, render, { cacheKey, key })` | same, options form. **`key` gives the list a stable identity** — required whenever a *conditional* list can render before this one, else kerf rebuilds this list and its rows lose focus/scroll/IME. A keyed list takes no positional slot, so keying the conditional list usually fixes its siblings too |
51
- | `delegate(root, type, sel, h)` | one listener at the root, walks `closest(selector)` from target |
52
- | `delegateCapture(root, type, sel, h, opts?)` | capture-phase escape hatch; `closest()` walk-up by default (same as `delegate`); pass `{ match: 'direct' }` for strict `target.matches()` |
53
- | `attr(name, value)` | pre-computed `AttrSpec<N,V>` — `.selector` for `delegate()`, `.attrs` to spread into JSX (rename-safe) |
54
- | `attr(name)` | dynamic factory — `attr<N,V=string>(name)` returns `(value: V) => { readonly [name]: V }`; both generics off → N inferred, V defaults to string; specify both to constrain values |
55
- | `toElement(jsx)` | parse JSX into a DOM node (SVG-aware). Single-root → `Element`; multi-root (`<><svg/> label</>`, two icons side by side) → `DocumentFragment` that `appendChild`/`replaceChildren`/`append` inlines into the parent. |
56
- | `raw(html)` | inject pre-escaped HTML |
57
- | `arraySignal(initial?)` | granular keyed-list signal at `kerfjs/array-signal` subpath; `each()` reconciles in O(patches) |
58
- | `` html`…` `` | tagged template at `kerfjs/html` subpath — JSX-identical runtime semantics with NO build step (CDN/importmap projects). Real HTML attribute names (`class`, not `className`); holes only in text positions or as a COMPLETE attribute value (`attr=${v}` / `attr="${v}"`) |
99
+ | Export | Use |
100
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `signal(initial)` | atomic reactive state; read/write via `.value` |
102
+ | `computed(fn)` | derived value (read-only) |
103
+ | `effect(fn)` | side effect that re-runs on signal change |
104
+ | `batch(fn)` | coalesce multiple writes into one re-run |
105
+ | `defineStore({initial, actions})` | named multi-consumer state |
106
+ | `resetAllStores()` | reset every store (test teardown) |
107
+ | `mount(el, render)` | bind reactive render to a DOM element; returns a disposer |
108
+ | `morph(liveRoot, template)` | one-shot reconcile against an already-populated element (SSR hydration, page-refresh diffs). Template can be `Element`, `SafeHtml`, or HTML string |
109
+ | `each(items, render, cacheKey?)` | keyed list iteration; per-row memoization on object identity (+ optional cacheKey — a passive comparator for external state). Distinct from `data-key` on the rendered element |
110
+ | `each(items, render, { cacheKey, key })` | same, options form. **`key` gives the list a stable identity** — required whenever a _conditional_ list can render before this one, else kerf rebuilds this list and its rows lose focus/scroll/IME. A keyed list takes no positional slot, so keying the conditional list usually fixes its siblings too |
111
+ | `delegate(root, type, sel, h)` | one listener at the root, walks `closest(selector)` from target |
112
+ | `delegateCapture(root, type, sel, h, opts?)` | capture-phase escape hatch; `closest()` walk-up by default (same as `delegate`); pass `{ match: 'direct' }` for strict `target.matches()` |
113
+ | `attr(name, value)` | pre-computed `AttrSpec<N,V>` — `.selector` for `delegate()`, `.attrs` to spread into JSX (rename-safe) |
114
+ | `attr(name)` | dynamic factory — `attr<N,V=string>(name)` returns `(value: V) => { readonly [name]: V }`; both generics off → N inferred, V defaults to string; specify both to constrain values |
115
+ | `toElement(jsx)` | parse JSX into a DOM node (SVG-aware). Single-root → `Element`; multi-root (`<><svg/> label</>`, two icons side by side) → `DocumentFragment` that `appendChild`/`replaceChildren`/`append` inlines into the parent. |
116
+ | `raw(html)` | inject pre-escaped HTML |
117
+ | `arraySignal(initial?)` | granular keyed-list signal at `kerfjs/array-signal` subpath; `each()` reconciles in O(patches) |
118
+ | `` html`…` `` | tagged template at `kerfjs/html` subpath — JSX-identical runtime semantics with NO build step (CDN/importmap projects). Real HTML attribute names (`class`, not `className`); holes only in text positions or as a COMPLETE attribute value (`attr=${v}` / `attr="${v}"`) |
59
119
 
60
120
  ## Hard rules — get these right on the first try
61
121
 
@@ -66,7 +126,7 @@ if (import.meta.env.DEV) await import('kerfjs/dev');
66
126
  5. **Capture the `delegate()` / `delegateCapture()` disposer** whenever the registration's scope is shorter than the page. Both helpers return `() => void`; the listener closure pins `rootEl`, `handler`, and everything the handler closes over (stores, signals, app state). Discarding the disposer on a transient root (modal, route view, mount swap, dynamic widget) leaks the listener AND the app graph it captures; re-mount cycles stack listeners linearly. `mount()`'s own disposer does NOT remove delegates for you. Safe to discard only when the registration is truly page-lifetime (root is `document.body` or equivalent, attached once at startup, never torn down).
67
127
  6. **One `mount()` per root.** Don't nest. Compose with plain functions that return JSX.
68
128
  7. **No `<MyComponent />` semantics with hooks.** Components are plain functions returning JSX. State lives in module-scope signals or stores, never in component closures.
69
- 8. **Values bind, structure re-renders.** For a value hole, pass the signal/computed ITSELF (`<span>{count}</span>`, `class={sig}`) — kerf updates that one node directly, no render re-run. Read `.value` only when the JSX *structure* depends on the signal — and then the read must happen INSIDE the render function to be tracked: `const x = count.value; mount(el, () => <span>{x}</span>)` will NOT re-render. Bind a STABLE signal/computed instance per hole (a `computed` that switches internally), never `class={cond ? sigA : sigB}` — switching instances can go silently stale (`KERF_DEV_WARN_STALE_BINDING=1` detects it). Endpoint: a render reading NO `.value` runs exactly once — a fully bound mount never re-renders; `KERF_DEV_WARN_VALUE_ONLY_RERENDER=1` flags re-renders that could have been bindings.
129
+ 8. **Values bind, structure re-renders.** For a value hole, pass the signal/computed ITSELF (`<span>{count}</span>`, `class={sig}`) — kerf updates that one node directly, no render re-run. Read `.value` only when the JSX _structure_ depends on the signal — and then the read must happen INSIDE the render function to be tracked: `const x = count.value; mount(el, () => <span>{x}</span>)` will NOT re-render. Bind a STABLE signal/computed instance per hole (a `computed` that switches internally), never `class={cond ? sigA : sigB}` — switching instances can go silently stale (`KERF_DEV_WARN_STALE_BINDING=1` detects it). Endpoint: a render reading NO `.value` runs exactly once — a fully bound mount never re-renders; `KERF_DEV_WARN_VALUE_ONLY_RERENDER=1` flags re-renders that could have been bindings.
70
130
  9. **Store actions receive `(set, get)`, not `(state)`.** `set(next)` replaces state; mutating `get()` does nothing.
71
131
  10. **Use `data-action` attributes, not inline `onClick`.** Inline handlers are NOT supported by the JSX → string runtime; delegate from the root instead.
72
132
  11. **`arraySignal` is opt-in for long keyed lists** where most updates are pointwise (single-row edits, append-to-end). For short lists / filter+sort pipelines, plain `signal` + `each(items.value, ...)` is simpler and equally fast.
@@ -79,22 +139,26 @@ if (import.meta.env.DEV) await import('kerfjs/dev');
79
139
  When deciding which primitive to reach for, work down the axes:
80
140
 
81
141
  **Events.**
142
+
82
143
  - Originates inside the mount tree → `delegate(rootEl, type, sel, handler)`. Originates outside (window-level keyboard, online/offline, beforeunload) → native `window.addEventListener` at module top-level.
83
144
  - Gesture that needs to follow an element after press (drag, draw, resize) → at the start event, `el.setPointerCapture(e.pointerId)`. Subsequent `pointermove` / `pointerup` redirect to the captured element and `delegate(rootEl, 'pointermove', '[data-card]', …)` still picks them up. Don't reach for `window.addEventListener` for in-mount-tree gestures.
84
145
  - Well-known non-bubbler (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`, `mouseleave`) → still `delegate()`; it auto-promotes to capture. Custom non-bubblers or capture-phase interception → `delegateCapture()` (also `closest()`-matched by default). Need strict element-match? Add `{ match: 'direct' }` on either helper.
85
146
 
86
147
  **Lists.**
148
+
87
149
  - Items change across renders (todos, chat messages, table rows) → `each(items, render)`.
88
150
  - Static structural enumeration whose row render reads signals → `STATIC.map(item => <jsx/>)`. Inner `each(item.children, …)` still gets keyed reconcile.
89
151
  - Long list with point-wise mutations → `arraySignal` + `each(arraySig, render)` for O(patches) updates.
90
152
 
91
153
  **Side effects / imperative DOM.**
154
+
92
155
  - Library-owned subtree survives across renders → `data-morph-skip` on host.
93
156
  - Host attributes morph but subtree preserved → `data-morph-skip-children`.
94
157
  - Imperatively-injected element survives the trailing-removal pass → `data-morph-preserve`.
95
158
  - Focused input / contenteditable caret survives re-renders → automatic; no opt-in.
96
159
 
97
160
  **Raw HTML.**
161
+
98
162
  - User-controlled HTML → sanitize first (DOMPurify) then `raw(sanitized)`.
99
163
  - Author-controlled trusted HTML → `raw(html)` directly.
100
164
  - Dangerous URLs (`javascript:`/`vbscript:`/script-executing `data:`) on `href`/`src`/`xlink:href`/`formaction`/`action`/`data` are dropped — kerf THROWS in dev, WARNS + drops in prod. Sanitize user URLs upstream; wrap an intentional trusted one in `raw(url)` to bypass the screen in both modes. The `javascript:` no-op placeholders (`javascript:void(0)`, `javascript:;`, …) are allowed — they're the placeholder-link idiom, matched whole so nothing can ride along.
@@ -107,20 +171,27 @@ When deciding which primitive to reach for, work down the axes:
107
171
  // updates that one node directly with no render re-run; read `.value` only
108
172
  // when the JSX STRUCTURE depends on the signal (conditionals, list shape).
109
173
  const count = signal(0);
110
- const ACTIONS = { inc: attr('data-action', 'inc') } as const satisfies Record<string, AttrSpec<'data-action'>>;
174
+ const ACTIONS = { inc: attr("data-action", "inc") } as const satisfies Record<
175
+ string,
176
+ AttrSpec<"data-action">
177
+ >;
111
178
 
112
- mount(document.getElementById('app')!, () => (
179
+ mount(document.getElementById("app")!, () => (
113
180
  <div>
114
181
  <button {...ACTIONS.inc.attrs}>+</button>
115
182
  <span>{count}</span>
116
183
  </div>
117
184
  ));
118
- delegate(rootEl, 'click', ACTIONS.inc.selector, () => { count.value += 1; });
185
+ delegate(rootEl, "click", ACTIONS.inc.selector, () => {
186
+ count.value += 1;
187
+ });
119
188
 
120
189
  // Keyed list with per-item memoization
121
190
  mount(listEl, () => (
122
191
  <ul>
123
- {each(rows.value, (row) => <li data-key={row.id}>{row.label}</li>)}
192
+ {each(rows.value, (row) => (
193
+ <li data-key={row.id}>{row.label}</li>
194
+ ))}
124
195
  </ul>
125
196
  ));
126
197
 
@@ -128,8 +199,8 @@ mount(listEl, () => (
128
199
  const cart = defineStore({
129
200
  initial: () => ({ items: [] as string[] }),
130
201
  actions: (set, get) => ({
131
- add: (id: string) => set({ items: [...get().items, id] }),
132
- clear: () => set({ items: [] }),
202
+ add: (id: string) => set({ items: [...get().items, id] }),
203
+ clear: () => set({ items: [] }),
133
204
  }),
134
205
  });
135
206
 
@@ -139,11 +210,16 @@ morph(liveCard, '<article class="card">…</article>');
139
210
  // No build step (CDN / importmap): the html tagged template instead of JSX.
140
211
  // Same runtime semantics as JSX; write real HTML attribute names; a hole must
141
212
  // be a text position or a COMPLETE attribute value (partial values throw).
142
- import { html } from 'kerfjs/html';
143
- mount(rootEl, () => html`
144
- <div class="${cls}">Count: ${count}</div>
145
- <ul>${each(rows.value, (row) => html`<li data-key="${row.id}">${row.label}</li>`)}</ul>
146
- `);
213
+ import { html } from "kerfjs/html";
214
+ mount(
215
+ rootEl,
216
+ () => html`
217
+ <div class="${cls}">Count: ${count}</div>
218
+ <ul>
219
+ ${each(rows.value, (row) => html`<li data-key="${row.id}">${row.label}</li>`)}
220
+ </ul>
221
+ `,
222
+ );
147
223
 
148
224
  // Fine-grained binding (opt-in): pass the signal/computed ITSELF into a hole
149
225
  // so a change updates ONLY that node (no render re-run, no reconcile). For a
@@ -152,9 +228,15 @@ mount(rootEl, () => html`
152
228
  const selectedId = signal<number | null>(null);
153
229
  mount(listEl, () => (
154
230
  <ul>
155
- {each(rows.value, (row) => (
156
- <li class={computed(() => (row.id === selectedId.value ? 'sel' : ''))}>{row.label}</li>
157
- ), (row) => row.id)}
231
+ {each(
232
+ rows.value,
233
+ (row) => (
234
+ <li class={computed(() => (row.id === selectedId.value ? "sel" : ""))}>
235
+ {row.label}
236
+ </li>
237
+ ),
238
+ (row) => row.id,
239
+ )}
158
240
  </ul>
159
241
  ));
160
242
  ```
@@ -170,10 +252,10 @@ mount(listEl, () => (
170
252
  - SVG renders as broken / namespaceless markup → use `mount` (HTML path) or `toElement` (SVG-aware), not `innerHTML`.
171
253
  - Library widget destroyed on every render → wrap host in `data-morph-skip`; mount the library imperatively after first render.
172
254
  - `each(): row render at index N produced K top-level elements` → wrap multiple roots in one parent.
173
- - Drag/drop / state change has no visible effect, only stuff *outside* `each()` updates → you used `each(STATIC_ARRAY, …)` whose row render reads signals. Replace the outer with `STATIC_ARRAY.map(...)`; keep inner `each()` for the dynamic sub-list. See Hard Rule 14.
174
- - Row-enter CSS animation no longer replays when only a row's *content* changed (kerf ≥ 0.15.0) → 0.15.0+ morphs a same-identity, same-position row *in place* instead of recreating its node, so a mount-keyed `@keyframes` never re-triggers on a content-only update (≤ 0.14.x recreated the node, so it fired; the intentional flip side is that focus, scroll, IME, and in-progress transitions now survive). Key the animation on a state-class toggle, not element creation; to force a remount, churn the row's identity (new object ref / `data-key`).
255
+ - Drag/drop / state change has no visible effect, only stuff _outside_ `each()` updates → you used `each(STATIC_ARRAY, …)` whose row render reads signals. Replace the outer with `STATIC_ARRAY.map(...)`; keep inner `each()` for the dynamic sub-list. See Hard Rule 14.
256
+ - Row-enter CSS animation no longer replays when only a row's _content_ changed (kerf ≥ 0.15.0) → 0.15.0+ morphs a same-identity, same-position row _in place_ instead of recreating its node, so a mount-keyed `@keyframes` never re-triggers on a content-only update (≤ 0.14.x recreated the node, so it fired; the intentional flip side is that focus, scroll, IME, and in-progress transitions now survive). Key the animation on a state-class toggle, not element creation; to force a remount, churn the row's identity (new object ref / `data-key`).
175
257
  - Want a hot spot to update without re-running the whole render → fine-grained binding: pass the signal/`computed` ITSELF into the attr/text hole (`class={computed(() => …)}`), not `.value`. Use `computed()` not a bare `() => …` (memoization keeps a shared-signal flip to ~O(changed nodes)). Opt-in per hole. Limit: a bound hole depending on the row's OWN mutated data goes stale on a granular in-place update — use plain interpolation there.
176
- - `` html`` ``: partial attribute values are not supported → in `kerfjs/html` templates a hole must be the COMPLETE attribute value. Replace `class="a ${b}"` with a pre-built string (`` class="${`a ${b}`}" ``) or, for a bound attribute, `class="${computed(() => `a ${b.value}`)}"`.
258
+ - ` html` ``: partial attribute values are not supported → in `kerfjs/html` templates a hole must be the COMPLETE attribute value. Replace `class="a ${b}"` with a pre-built string (`` class="${`a ${b}`}" ``) or, for a bound attribute, `class="${computed(() => `a ${b.value}`)}"`.
177
259
  - An `each()` list's rows lose focus / scroll / typing state when an unrelated conditional list above them appears or disappears (kerf warns in dev) → lists without a key are identified by position among the render's `each()` calls, so adding/removing one above shifts this list's identity and kerf rebuilds it. Give the lists stable keys: `each(items, render, { key: 'results' })`; keying just the conditional list is usually enough.
178
260
  - Keyed `each()` list suddenly renders zero rows — only its `<!--kf-list:N-->` marker — with no errors, and it never recovers (kerfjs ≤ 2.0.1) → a conditionally-rendered sibling BEFORE the list (possibly higher in the tree, e.g. an error banner) was removed that render; older kerfjs rebuilt the shifted list container from the template, permanently detaching the list's internal binding. Upgrade kerfjs (fixed after 2.0.1 — the morph now moves the shifted container up in place, keeping node identity). On older versions, keep the structure before the list stable: wrap the conditional in an always-present container (`<div class="banners">{cond ? <div/> : ''}</div>`).
179
261
  - A numbered / zebra-striped / "N of M" `each()` list shows the wrong number on rows that MOVED (reorder, or non-tail insert/remove), while unmoved rows look right → the render fn's `index` argument is NOT part of the memo key (only item identity + `cacheKey` + content version are), so a row that keeps identity but changes position keeps HTML rendered at its old index. Fold the index into the memo key: `each(items, (it, i) => …, { cacheKey: (_, i) => i })` (add `key` if used). Opt-in dev warn: `KERF_DEV_WARN_STALE_INDEX=1`.
package/ai/manifest.json CHANGED
@@ -1,21 +1,79 @@
1
1
  {
2
- "kerfjsVersion": "4.4.1",
2
+ "kerfjsVersion": "4.5.0-beta.1",
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.22.0",
10
+ "sha256": "dc99b0ca5e911be11cbfa2ae515c0e161a414a5185895069535c41ba56664e70",
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
+ "1.22.0": "dc99b0ca5e911be11cbfa2ae515c0e161a414a5185895069535c41ba56664e70"
40
+ }
11
41
  },
12
42
  {
13
43
  "name": "cursorrules",
14
44
  "source": "kerf.cursorrules",
15
45
  "bundle": "ai/cursorrules",
16
46
  "dest": ".cursorrules",
17
- "version": "1.14.2",
18
- "sha256": "09d9f0290fe84d3244e1655c8284c8431b47cdbe220f7161f38bc96fc8ef8ba3"
47
+ "version": "1.20.0",
48
+ "sha256": "5e7b8d8ddc41f60220961417c9ae01c193fae045378a10a3e9c96a0e3cf10a03",
49
+ "history": {
50
+ "1.0.0": "98f7faff2b7585ef9cc511add42ea5d9bf0cbae8307b066af090e9281136d2ab",
51
+ "1.1.0": "a2ec8c221dc694c98b81009cd2d99f9159a45fda94585de2f54471b227099eda",
52
+ "1.1.1": "f28b963171b7c2cbc36823d7220080730cda85a41f105b90e81790c1188e6186",
53
+ "1.2.0": "dcf2fc03f5dfca3b9bfea8cfefc65ac8ce31e1c601c613e91af69b6195c5e267",
54
+ "1.2.1": "2fcd5e9e0fd889febdab19bcc7e415c342b10ae81e937852b8280014b8d374e0",
55
+ "1.3.0": "2524a42cc3b488f8cc3cea736cf5b8d3e57d868c85260342ec50905243aa65f1",
56
+ "1.4.0": "f2c0fb328d487cc4a64ccd0b671c3385f1a25074c8c5e502c507ee799cd65e80",
57
+ "1.5.0": "2c012cbffa2fd9f96a3b8cdca48b55e584530ee6e5be73c922d3bb49470b6c31",
58
+ "1.6.0": "9370876e58ac634936ca321bf5313bb339659cb7c7c1b38a9b10f2bf4b03836d",
59
+ "1.7.0": "8ce556caf8fa95f783f5423bf882a735fc3ce6b7eb83e823aaa0417b3272af15",
60
+ "1.8.0": "f91402df009941b4198ea659498f458d2433843d47a3b524b0ca5e2d3d2fc949",
61
+ "1.8.1": "eed385b8edf3b96e88d8f00ea45ee4146004a74d3f0e495b870a27c137d0ec44",
62
+ "1.9.0": "c3242e1aa8c5ca51fca3e2e3c3df8e55dffb5d6c7b5bc8d677949d48ff5563cc",
63
+ "1.10.0": "7adefcc5ab66fcc3e5ae10625d7c762e86ac02ca297648aa58b0b201b35b04f8",
64
+ "1.11.0": "506c3338ec1c2268912f5eb9530a6af04ad937061281b5ea84529fc8b10486ff",
65
+ "1.12.0": "48ff3d0f483a9fcd7f57ca20c17c0aadfce0e4a0b39d9e96e964ef9260f782d3",
66
+ "1.13.0": "3a6bf9b1c74e0be00053faddc2af3c979ba5bb26c11db93bfcb8b1ecbe66284d",
67
+ "1.14.0": "b83de9cff13789cdebf572f2e98d3d61fdd4f31c7eb237a0a4999b6a1dfe4d9b",
68
+ "1.14.1": "9d2cc9da69e974a56d79393ad3b3a9d76c068fbd46b392621790a82fc0772d00",
69
+ "1.14.2": "09d9f0290fe84d3244e1655c8284c8431b47cdbe220f7161f38bc96fc8ef8ba3",
70
+ "1.15.0": "eece0b3075ceeeec5f6a59393a82c105d28ad1a34374412fbf6a4e0c23d7e8b9",
71
+ "1.16.0": "49794d543ab1f9112f9f6a1dcdc7aad4347b8965e1b4003bb7884086a02411c3",
72
+ "1.17.0": "6ad51fb0eec468f584346d1d7b4d1c2eca2cb52b04891b38ba44e93b57eb9e1c",
73
+ "1.18.0": "de3eea1bc181da629694d19a0817d252a868e4704437fbdd9e923856b8a0890a",
74
+ "1.19.0": "c5b91e449f6feccaa36a04cb7f8550d3e414eb2795ea6c5dccdf6071206b3261",
75
+ "1.20.0": "5e7b8d8ddc41f60220961417c9ae01c193fae045378a10a3e9c96a0e3cf10a03"
76
+ }
19
77
  }
20
78
  ]
21
79
  }