kerfjs 2.0.1 → 3.0.0
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.
- package/CHANGELOG.md +100 -0
- package/ai/cursorrules +11 -1
- package/ai/manifest.json +5 -5
- package/ai/skill.md +11 -1
- package/dist/array-signal.d.ts +7 -1
- package/dist/array-signal.js +11 -2
- package/dist/array-signal.js.map +1 -1
- package/dist/bindings-CYwoJpQb.d.ts +60 -0
- package/dist/chunk-3APBEVHF.js +20 -0
- package/dist/chunk-3APBEVHF.js.map +1 -0
- package/dist/chunk-GY4XV2UV.js +73 -0
- package/dist/chunk-GY4XV2UV.js.map +1 -0
- package/dist/{chunk-GYRZQCSY.js → chunk-JVVU2RQO.js} +11 -79
- package/dist/chunk-JVVU2RQO.js.map +1 -0
- package/dist/chunk-QIP723L4.js +15 -0
- package/dist/chunk-QIP723L4.js.map +1 -0
- package/dist/chunk-SAYPJ6XR.js +43 -0
- package/dist/chunk-SAYPJ6XR.js.map +1 -0
- package/dist/chunk-VVDJLWMP.js +14 -0
- package/dist/chunk-VVDJLWMP.js.map +1 -0
- package/dist/chunk-YHH7OUFA.js +58 -0
- package/dist/chunk-YHH7OUFA.js.map +1 -0
- package/dist/dev.d.ts +339 -0
- package/dist/dev.js +607 -0
- package/dist/dev.js.map +1 -0
- package/dist/html.d.ts +1 -0
- package/dist/html.js +4 -2
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +49 -11
- package/dist/index.js +351 -340
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +16 -60
- package/dist/jsx-runtime.js +4 -2
- package/dist/testing.js +3 -2
- package/llms.txt +2 -2
- package/package.json +22 -11
- package/dist/chunk-GYRZQCSY.js.map +0 -1
- package/dist/chunk-KFUDM3VP.js +0 -131
- package/dist/chunk-KFUDM3VP.js.map +0 -1
- package/dist/chunk-NU7YHYEV.js +0 -90
- package/dist/chunk-NU7YHYEV.js.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { flatten, wrapWithTags, mergeChildSegments } from './chunk-GY4XV2UV.js';
|
|
2
|
+
import { effect, isSignal } from './chunk-3APBEVHF.js';
|
|
3
|
+
import { devHooks } from './chunk-VVDJLWMP.js';
|
|
2
4
|
|
|
3
5
|
// src/utils/syncFormProp.ts
|
|
4
6
|
function syncFormProp(el, name, value, present) {
|
|
@@ -46,7 +48,7 @@ function dangerousUrlWarning(name, value) {
|
|
|
46
48
|
}
|
|
47
49
|
function reportDangerousUrl(context2, name, value) {
|
|
48
50
|
const message = `${context2}: ${dangerousUrlWarning(name, value)}`;
|
|
49
|
-
|
|
51
|
+
devHooks.urlScreenThrow?.(message);
|
|
50
52
|
console.warn(message);
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -263,76 +265,6 @@ function collectComments(node, prefix, out) {
|
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
|
|
266
|
-
// src/segment.ts
|
|
267
|
-
var LIST_MARKER_PREFIX = "kf-list:";
|
|
268
|
-
function flatten(segment, withMarkers) {
|
|
269
|
-
if (segment.kind === "static") return segment.html;
|
|
270
|
-
if (segment.kind === "list") {
|
|
271
|
-
const items = segment.items.map((i) => i.html).join("");
|
|
272
|
-
return withMarkers ? `<!--${LIST_MARKER_PREFIX}${segment.id}-->${items}` : items;
|
|
273
|
-
}
|
|
274
|
-
return segment.parts.map((p) => flatten(p, withMarkers)).join("");
|
|
275
|
-
}
|
|
276
|
-
function flattenWithoutListItems(segment) {
|
|
277
|
-
if (segment.kind === "static") return segment.html;
|
|
278
|
-
if (segment.kind === "list") return `<!--${LIST_MARKER_PREFIX}${segment.id}-->`;
|
|
279
|
-
return segment.parts.map(flattenWithoutListItems).join("");
|
|
280
|
-
}
|
|
281
|
-
function collectLists(segment, out = /* @__PURE__ */ new Map()) {
|
|
282
|
-
if (segment.kind === "list") out.set(segment.id, segment);
|
|
283
|
-
else if (segment.kind === "mixed") {
|
|
284
|
-
for (const part of segment.parts) collectLists(part, out);
|
|
285
|
-
}
|
|
286
|
-
return out;
|
|
287
|
-
}
|
|
288
|
-
function mergeChildSegments(parts) {
|
|
289
|
-
if (parts.length === 0) return { kind: "static", html: "" };
|
|
290
|
-
if (parts.every((p) => p.kind === "static")) {
|
|
291
|
-
return {
|
|
292
|
-
kind: "static",
|
|
293
|
-
html: parts.map((p) => p.html).join("")
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
const merged = [];
|
|
297
|
-
let coalesced = "";
|
|
298
|
-
for (const p of parts) {
|
|
299
|
-
if (p.kind === "static") {
|
|
300
|
-
coalesced += p.html;
|
|
301
|
-
} else {
|
|
302
|
-
if (coalesced !== "") {
|
|
303
|
-
merged.push({ kind: "static", html: coalesced });
|
|
304
|
-
coalesced = "";
|
|
305
|
-
}
|
|
306
|
-
merged.push(p);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
if (coalesced !== "") merged.push({ kind: "static", html: coalesced });
|
|
310
|
-
return { kind: "mixed", parts: merged };
|
|
311
|
-
}
|
|
312
|
-
function wrapWithTags(child, openTag, closeTag) {
|
|
313
|
-
if (child.kind === "static") {
|
|
314
|
-
return { kind: "static", html: openTag + child.html + closeTag };
|
|
315
|
-
}
|
|
316
|
-
if (child.kind === "mixed") {
|
|
317
|
-
return {
|
|
318
|
-
kind: "mixed",
|
|
319
|
-
parts: [
|
|
320
|
-
{ kind: "static", html: openTag },
|
|
321
|
-
...child.parts,
|
|
322
|
-
{ kind: "static", html: closeTag }
|
|
323
|
-
]
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
return {
|
|
327
|
-
kind: "mixed",
|
|
328
|
-
parts: [
|
|
329
|
-
{ kind: "static", html: openTag },
|
|
330
|
-
child,
|
|
331
|
-
{ kind: "static", html: closeTag }
|
|
332
|
-
]
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
268
|
// src/utils/escapeHtml.ts
|
|
337
269
|
function escapeHtml(str) {
|
|
338
270
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -464,11 +396,11 @@ function isSafeHtml(value) {
|
|
|
464
396
|
function raw(html) {
|
|
465
397
|
return new SafeHtml(html);
|
|
466
398
|
}
|
|
467
|
-
function listSafeHtml(id, items) {
|
|
468
|
-
return new SafeHtml({ kind: "list", id, items });
|
|
399
|
+
function listSafeHtml(id, items, source) {
|
|
400
|
+
return new SafeHtml({ kind: "list", id, items, source });
|
|
469
401
|
}
|
|
470
|
-
function granularListSafeHtml(id, items, patches) {
|
|
471
|
-
return new SafeHtml({ kind: "list", id, items, patches });
|
|
402
|
+
function granularListSafeHtml(id, items, patches, source) {
|
|
403
|
+
return new SafeHtml({ kind: "list", id, items, patches, source });
|
|
472
404
|
}
|
|
473
405
|
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
474
406
|
"area",
|
|
@@ -599,6 +531,6 @@ function _renderAttrVerbatim(name, value) {
|
|
|
599
531
|
return renderAttrNamed(name, name, value);
|
|
600
532
|
}
|
|
601
533
|
|
|
602
|
-
export { Fragment,
|
|
603
|
-
//# sourceMappingURL=chunk-
|
|
604
|
-
//# sourceMappingURL=chunk-
|
|
534
|
+
export { Fragment, ROW_TEXT_PREFIX, SafeHtml, TEXT_MARKER_PREFIX, _renderAttrVerbatim, _setBindingContext, _toSegment, assertEmittableAttrName, bindAttr, bindMarkerAttr, boundTextNodeOf, captureRowBindings, carryOrRewireRowBindings, disposeRowBindings, granularListSafeHtml, isSafeHtml, jsx, listSafeHtml, newBindingContext, raw, syncFormProp, wireBindings, wireRowBindings };
|
|
535
|
+
//# sourceMappingURL=chunk-JVVU2RQO.js.map
|
|
536
|
+
//# sourceMappingURL=chunk-JVVU2RQO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/syncFormProp.ts","../src/utils/urlScreen.ts","../src/bindings.ts","../src/utils/escapeHtml.ts","../src/utils/jsx-attr-aliases.ts","../src/jsx-runtime.ts"],"names":["context","SAFE_HTML_BRAND"],"mappings":"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","file":"chunk-JVVU2RQO.js","sourcesContent":["/**\n * Form-state property sync — shared by the morph's attribute writer and the\n * fine-grained binding writer (KF-335: controlled checked/value/selected went\n * stale after user interaction).\n *\n * Browsers detach a form control's live property from its attribute once the\n * control is \"dirty\" (the user — or script — has touched it): the attribute\n * becomes only the *default*. An attribute-only reconciler therefore updates\n * `checked=\"\"` / `value=\"…\"` / `selected=\"\"` while the visible state stays\n * stale.\n *\n * The rule: callers invoke this ONLY at the moment they actually MUTATE one of\n * these attributes (set, change, or remove). The property then follows the\n * attribute. Attribute-unchanged elements are never touched, so uncontrolled\n * usage — JSX that never mentions the attribute — keeps user-driven state\n * exactly as before. This is the same philosophy as the user-agent-owned\n * `open` carve-out on `<details>`/`<dialog>`: kerf only touches what the\n * template expressed an opinion about.\n *\n * The focused-element exception: a focused text-entry control's `value` is the\n * user's in-progress edit — the morph's preservation rule owns it, so `value`\n * sync is skipped for `document.activeElement`.\n */\nexport function syncFormProp(\n el: Element,\n name: string,\n value: string,\n present: boolean,\n): void {\n const tag = el.tagName;\n if (name === 'checked') {\n if (tag === 'INPUT') (el as HTMLInputElement).checked = present;\n } else if (name === 'value') {\n if (tag === 'INPUT' && el !== document.activeElement) {\n (el as HTMLInputElement).value = present ? value : '';\n }\n } else if (name === 'selected') {\n if (tag === 'OPTION') (el as HTMLOptionElement).selected = present;\n }\n}\n","/**\n * URL-attribute screening, shared by the JSX runtime's static-attribute\n * serializer (`renderAttr` in `jsx-runtime.ts`) and the fine-grained binding's\n * live-attribute writer (`setBoundAttr` in `bindings.ts`) — KF-297.\n *\n * A stored-XSS payload like `href={userInput}` with `javascript:alert(1)` is\n * dropped (attribute omitted from the string / removed from the live node)\n * rather than written. `raw(...)` / `SafeHtml` values bypass this — the\n * documented opt-out for legitimate cases (bookmarklet builders, values\n * sanitized by a separate trust layer).\n *\n * The screen is a scheme allow/deny check, not a full URL sanitizer, but it is\n * hardened against the obfuscations a real browser sees through:\n *\n * - **Control-character / whitespace obfuscation of the scheme.** Browsers\n * strip leading C0 controls + spaces and remove TAB/LF/CR from anywhere in\n * a URL before resolving the scheme, so `java\\tscript:`, `\\x01javascript:`,\n * and `javascript\\x00:` all resolve to `javascript:`. We normalize the same\n * way (strip every C0 control 0x00-0x1F + DEL, trim leading whitespace)\n * BEFORE extracting the scheme, so none of those slip past.\n *\n * - **`data:` is subtype-specific.** `data:image/png` in `<img src>` is safe\n * and common; `data:text/html` / `data:image/svg+xml` / `data:*xml` load as\n * a document and run script (SVG `<script>`, HTML/XHTML `<script>`). We\n * allowlist the inert media families (raster images, fonts, audio, video,\n * plain text/css) and drop every other `data:` — so a novel dangerous\n * subtype fails closed.\n */\n\nimport { devHooks } from '../dev-hooks.js';\n\n/**\n * URL-bearing HTML/SVG attributes whose plain-string values are screened.\n * `data` covers `<object data>` (loads its target as a document, so\n * `data:text/html` there executes). Image-context URL attributes\n * (`srcset` / `poster` / `background` / `ping`) are intentionally NOT screened:\n * a `javascript:` / `data:` value in them does not execute as script in modern\n * browsers, so screening would add false positives without closing a real sink.\n */\nconst URL_ATTRS = new Set(['href', 'src', 'xlink:href', 'formaction', 'action', 'data']);\n\n/** Schemes that execute script when a browser resolves them as a URL. */\nconst DANGEROUS_SCHEMES = new Set(['javascript', 'vbscript']);\n\n/** Matches every C0 control character (0x00-0x1F) and DEL (0x7F). */\n// eslint-disable-next-line no-control-regex -- deliberately matching C0 + DEL.\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Normalize a URL the way a browser does before scheme resolution: remove every\n * C0 control (0x00-0x1F) and DEL (0x7F) from anywhere — no legitimate URL\n * carries a raw control char (they must be percent-encoded), so this collapses\n * `java\\tscript:` / `\\x01javascript:` / `javascript\\x00:` back to their real\n * scheme — then trim leading whitespace. Internal spaces are left intact: a\n * space is not a valid scheme character, so a browser treats `java script:` as\n * a relative URL, not the `javascript:` scheme, and we must not \"repair\" it\n * into a false positive.\n */\nfunction normalizeUrl(value: string): string {\n return value.replace(CONTROL_CHARS, '').replace(/^\\s+/, '');\n}\n\n/** The lowercased URL scheme (text before the first `:`), or null if none. */\nfunction extractScheme(value: string): string | null {\n const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(normalizeUrl(value));\n return m ? m[1].toLowerCase() : null;\n}\n\n/**\n * True if a `data:` URL is a script-executing document type. Allowlists the\n * inert media families and fails closed on everything else — so `text/html`,\n * `image/svg+xml`, XHTML/XML, and any unknown/novel subtype are all treated as\n * dangerous, while `data:image/png` / fonts / audio / video / plain text pass.\n */\nfunction isDangerousDataUrl(value: string): boolean {\n const media = /^data:([^;,]*)/.exec(normalizeUrl(value).toLowerCase())?.[1].trim() ?? '';\n if (media === '' || media === 'text/plain' || media === 'text/css') return false;\n if (media === 'image/svg+xml') return true; // SVG can carry <script>\n if (media.startsWith('image/')) return false;\n if (media.startsWith('font/') || media.startsWith('application/font')) return false;\n if (media.startsWith('audio/') || media.startsWith('video/')) return false;\n return true; // text/html, application/xhtml+xml, *xml, unknown → block\n}\n\n/** True if `value` is a dangerous URL for the URL-bearing attribute `name`. */\nexport function isDangerousUrlValue(name: string, value: string): boolean {\n if (!URL_ATTRS.has(name)) return false;\n const scheme = extractScheme(value);\n if (scheme === null) return false;\n if (DANGEROUS_SCHEMES.has(scheme)) return true;\n return scheme === 'data' && isDangerousDataUrl(value);\n}\n\n/**\n * The shared warning body for a dropped dangerous URL. Each caller prefixes\n * its own context (`JSX:` for the string serializer, `kerf binding:` for the\n * live-attribute writer).\n */\nexport function dangerousUrlWarning(name: string, value: string): string {\n return `dropped dangerous URL value for ${name}=${JSON.stringify(value.slice(0, 80))}. `\n + 'kerf blocks javascript:, vbscript:, and script-executing data: URLs '\n + '(text/html, image/svg+xml, xml) in href/src/data/formaction/action/xlink:href by default. '\n + 'Wrap in raw() if this is intentional (e.g. bookmarklets), or sanitize upstream.';\n}\n\n/**\n * Report a screened dangerous URL, with the dev-vs-prod split (KF-340). Both\n * call sites (the JSX string serializer's `renderAttr` and the live-attribute\n * writer's `setBoundAttr`) still DROP the attribute; this only chooses how the\n * screen surfaces the diagnostic:\n *\n * - **DEV** (the consumer imported `kerfjs/dev`): THROW an `Error` with the\n * diagnostic, failing loudly at the developer's desk so a mistyped/unsafe\n * URL can't slip by unnoticed (nobody reads the console).\n * - **PRODUCTION** (no dev entry installed): `console.warn` and let the\n * caller drop — never crash a shipped app on attacker-influenced data.\n *\n * `context` is the caller's prefix (`JSX`/`kerf binding`); the composed message\n * is byte-identical to the pre-KF-340 warn text in production.\n */\nexport function reportDangerousUrl(context: string, name: string, value: string): void {\n const message = `${context}: ${dangerousUrlWarning(name, value)}`;\n devHooks.urlScreenThrow?.(message);\n console.warn(message);\n}\n","/**\n * Fine-grained signal bindings (KF-294 spike).\n *\n * When a `Signal` is interpolated straight into a JSX attribute\n * (`class={sig}`) or a text child (`{sig}`) INSIDE a `mount()` render, the\n * JSX runtime stops stringifying it. Instead it emits a marker into the HTML\n * string and records a binding here; after the string is parsed to DOM, a\n * wiring pass attaches one `effect` per binding that writes straight to the\n * live node. A later change to that signal then updates the node WITHOUT\n * re-running the render function or walking the list reconciler.\n *\n * This reuses the \"marker in string, wire up after parse\" mechanism the\n * keyed-list reconciler already uses for `<!--kf-list:{id}-->` markers.\n *\n * TWO SCOPES of binding, with disjoint marker namespaces so their wiring\n * passes never collide:\n *\n * - GLOBAL holes — signals in the static surrounds (outside any `each()`\n * row). Markers: `data-kfb` attribute / `<!--kfb:{id}-->` comment. Ids come\n * from the mount render context's counter; wired by `wireBindings()` over\n * the whole mount root; disposed/re-wired by `mount()` each render.\n *\n * - ROW holes — signals inside an `each()` row. Markers: `data-kfbrow`\n * attribute / `<!--kfbr:{id}-->` comment. Ids are row-LOCAL (reset per row)\n * so they stay stable and collision-free as rows are inserted/removed/moved.\n * Captured per row by `captureRowBindings()`, carried on the list segment\n * item, and wired/disposed by the list reconciler at each row node's\n * create/remove — so a binding's lifetime tracks its row node's lifetime,\n * and row reorders (which reuse the same node) are free.\n *\n * Outside a `mount()` render (SSR / `SafeHtml.toString()`) neither scope is\n * active: the runtime snapshots `signal.value` and emits no markers, so server\n * output is correct and legacy `.toString()` callers are unaffected.\n *\n * Module-level mutable state note: `context` / `rowSink` (plus `rowCounter`,\n * the row-hole id counter that resets with each row capture) are a third\n * sanctioned module-level mutable location (alongside `store.ts:REGISTRY` and\n * `each.ts:context`). They hold the current render's binding sinks and are set\n * / cleared by `mount()` and `each()` around the render calls. The only other\n * module-level container here is `insertedTextNodes`, a WeakMap keyed on text\n * marker comments — a pure cache whose entries die with their nodes (GC-tied\n * lifetime), so it carries no cross-render semantics.\n */\n\nimport { effect, isSignal, type Signal } from './reactive.js';\nimport { syncFormProp } from './utils/syncFormProp.js';\nimport { isDangerousUrlValue, reportDangerousUrl } from './utils/urlScreen.js';\n\n// RESERVED NAMESPACE (consumer contract, KF-314). The wiring pass finds these\n// markers by scanning the mounted subtree and matching by id, and resolves an\n// id collision by document order (last/first wins) with no ownership check — so\n// a consumer element that carries `data-kfb`/`data-kfbrow`, or a comment\n// beginning `kfb:`/`kfbr:`/`kf-list:`, can collide with a real binding's id and\n// steal its effect (the update wires to the wrong node). These names are\n// therefore reserved: consumers must not emit them (documented in\n// docs/2-reactivity.md § \"Reserved marker names\"). Kerf's own escaping already\n// prevents a plain text/attribute *value* from forging one; the remaining ways\n// in are a hand-written attribute or a `raw()` payload — i.e. the consumer's\n// own trust boundary. A dev-mode collision warning is a possible future\n// enhancement (fix direction (a) on KF-314) if silent mis-wiring proves easy to\n// hit in practice.\n\n/** Marker attribute for GLOBAL (static-surround) attribute bindings. */\nexport const BIND_ATTR = 'data-kfb';\n/** Comment-marker prefix for a GLOBAL text binding: `<!--kfb:{id}-->`. */\nexport const TEXT_MARKER_PREFIX = 'kfb:';\n/** Marker attribute for ROW (each()-scoped) attribute bindings. */\nexport const BIND_ATTR_ROW = 'data-kfbrow';\n/** Comment-marker prefix for a ROW text binding: `<!--kfbr:{id}-->`. */\nexport const ROW_TEXT_PREFIX = 'kfbr:';\n\ninterface AttrBinding {\n kind: 'attr';\n id: string;\n attr: string;\n signal: Signal<unknown>;\n}\ninterface TextBinding {\n kind: 'text';\n id: string;\n signal: Signal<unknown>;\n}\nexport type Binding = AttrBinding | TextBinding;\n\n/**\n * Per-render GLOBAL binding sink. `counter` assigns ids by registration order\n * (stable across renders since the JSX render is deterministic); `list`\n * accumulates this render's global (static-surround) holes.\n */\nexport interface BindingContext {\n counter: number;\n list: Binding[];\n}\n\nlet context: BindingContext | null = null;\n\n// Active row-capture sink + its row-local id counter. Non-null only while\n// `each()` renders a single row through `captureRowBindings()`.\nlet rowSink: Binding[] | null = null;\nlet rowCounter = 0;\n\n// Frozen shared sentinel for the no-holes case — readonly so no caller can\n// push into it and corrupt every other no-hole mount.\nconst NO_DISPOSERS: ReadonlyArray<() => void> = Object.freeze([]);\n\nexport function newBindingContext(): BindingContext {\n return { counter: 0, list: [] };\n}\n\nexport function _setBindingContext(c: BindingContext | null): void {\n context = c;\n}\n\n/**\n * Render one `each()` row while capturing the row-scoped bindings it emits.\n * Row ids are local (reset to 0) so they're stable + collision-free per row.\n * Returns the row HTML and its captured bindings (empty when the row has no\n * signal holes). Restores the prior capture state (supports nested `each()`).\n */\nexport function captureRowBindings(renderRow: () => string): { html: string; bindings: Binding[] } {\n const prevSink = rowSink;\n const prevCounter = rowCounter;\n rowSink = [];\n rowCounter = 0;\n try {\n const html = renderRow();\n return { html, bindings: rowSink };\n } finally {\n rowSink = prevSink;\n rowCounter = prevCounter;\n }\n}\n\n/**\n * Register a signal attribute. Routes to the row sink when a row capture is\n * active, else the global context. Returns the marker id,\n * or null to snapshot. The marker attribute NAME (`data-kfb` vs `data-kfbrow`)\n * is fetched once per element via `bindMarkerAttr()` — all of an element's\n * signal attrs share the same scope — so this avoids a per-attr object alloc.\n */\nexport function bindAttr(attr: string, signal: Signal<unknown>): string | null {\n if (rowSink !== null) {\n const id = `a${rowCounter++}`;\n rowSink.push({ kind: 'attr', id, attr, signal });\n return id;\n }\n if (context !== null) {\n const id = `a${context.counter++}`;\n context.list.push({ kind: 'attr', id, attr, signal });\n return id;\n }\n return null;\n}\n\n/**\n * The marker attribute name for the currently-active binding scope — row holes\n * use `data-kfbrow`, global holes `data-kfb`. Called once per element by the\n * JSX runtime after collecting the element's signal-attr ids.\n */\nexport function bindMarkerAttr(): string {\n return rowSink !== null ? BIND_ATTR_ROW : BIND_ATTR;\n}\n\n/**\n * Register a signal text hole. Returns the comment-marker HTML to emit, or\n * null to snapshot. Same routing as `bindAttr()`.\n */\nexport function bindText(signal: Signal<unknown>): string | null {\n if (rowSink !== null) {\n const id = `t${rowCounter++}`;\n rowSink.push({ kind: 'text', id, signal });\n return `<!--${ROW_TEXT_PREFIX}${id}-->`;\n }\n if (context !== null) {\n const id = `t${context.counter++}`;\n context.list.push({ kind: 'text', id, signal });\n return `<!--${TEXT_MARKER_PREFIX}${id}-->`;\n }\n return null;\n}\n\n/**\n * Wire this render's GLOBAL bindings against the freshly-built live DOM.\n * Disposes `prevDisposers` first (a surrounds-changed morph strips bound attrs\n * and removes inserted text nodes, so we re-establish them), then attaches one\n * effect per hole. Zero-cost when there are no global holes.\n */\nexport function wireBindings(\n rootEl: Element,\n ctx: BindingContext,\n prevDisposers: ReadonlyArray<() => void>,\n): ReadonlyArray<() => void> {\n for (const d of prevDisposers) d();\n if (ctx.list.length === 0) return NO_DISPOSERS;\n const disposers: Array<() => void> = [];\n wireInto(rootEl, ctx.list, disposers);\n return disposers;\n}\n\n/**\n * Wire one row's ROW-scoped bindings against its freshly-built row node\n * (including the node itself, since a row's top-level element may carry the\n * marker, e.g. `<tr data-kfbrow>`). Returns the row's disposers for the\n * reconciler to store on the bound item and call when the row node is removed.\n */\nexport function wireRowBindings(rowNode: Element, bindings: Binding[]): Array<() => void> {\n // Callers (snapshot buildFreshNodes, mount first-render inline) only invoke\n // this for rows with at least one binding, so no empty-guard is needed.\n //\n // Hot path: most row holes are an attribute on the row's ROOT element (e.g.\n // `<tr class={sig}>`). For those we resolve the node with a single\n // `getAttribute` + allocation-free id membership check — no `querySelectorAll`,\n // no `collectComments` walk, no Map. The descendant-attr index and the\n // text-comment index are built lazily, only if a hole actually needs them.\n const disposers: Array<() => void> = new Array(bindings.length);\n const rootIds = rowNode.getAttribute(BIND_ATTR_ROW);\n // For the common single-root-binding row, `rootIds === b.id` resolves the\n // node with zero allocation. Only a row with 2+ root bindings builds the Set.\n let rootIdSet: Set<string> | null = null;\n let descIndex: Map<string, Element> | null = null;\n let textMarkers: Map<string, Comment> | null = null;\n\n for (let i = 0; i < bindings.length; i++) {\n const b = bindings[i];\n if (b.kind === 'attr') {\n let onRoot = false;\n if (rootIds !== null) {\n if (rootIds === b.id) {\n onRoot = true;\n } else if (rootIds.indexOf(',') !== -1) {\n rootIdSet ??= new Set(rootIds.split(','));\n onRoot = rootIdSet.has(b.id);\n }\n }\n let el: Element | undefined;\n if (onRoot) {\n el = rowNode;\n } else {\n descIndex ??= indexAttrEls(rowNode, BIND_ATTR_ROW);\n el = descIndex.get(b.id);\n }\n /* c8 ignore next -- defensive: a registered binding always emits its marker into the row. */\n if (el === undefined) continue;\n disposers[i] = attachAttrEffect(el, b.attr, b.signal);\n } else {\n if (textMarkers === null) {\n textMarkers = new Map();\n collectComments(rowNode, ROW_TEXT_PREFIX, textMarkers);\n }\n const marker = textMarkers.get(b.id);\n /* c8 ignore next -- defensive: same invariant as the attr branch. */\n if (marker === undefined) continue;\n disposers[i] = attachTextEffect(marker, b.signal);\n }\n }\n return disposers;\n}\n\n/** Dispose a row's binding effects (called when its node leaves the DOM). */\nexport function disposeRowBindings(disposers: Array<() => void> | undefined): void {\n if (disposers === undefined) return;\n for (const d of disposers) d();\n}\n\n/**\n * KF-347 — decide, for a row whose DOM node survives an in-place update,\n * whether the live binding effects can be carried forward or must be\n * re-wired. The old effects closed over the signal INSTANCES captured at the\n * previous render; if this render's bindings reference the same instances\n * per hole (the cache-hit case, and rows binding stable external signals),\n * the old effects remain correct and re-wiring would be wasted work — the\n * select-row hot path stays free. If any hole's instance changed — the\n * typical shape after `arraySignal.update()` swaps the row object and the\n * row re-renders fresh `computed`s closing over the NEW item — the old\n * effects are stale (they'd keep reading the pre-update row), so dispose\n * them and wire the fresh bindings against the surviving node. Marker\n * alignment holds across every in-place route: row-local binding ids are\n * deterministic per render, the surgical fast paths leave marker attrs and\n * comments untouched, and `_morphElement` syncs them.\n *\n * Also covers the from-scratch case (`oldBindings` undefined with fresh\n * `newBindings`): the length mismatch routes to the wire branch, so a\n * replaceChild caller can hand its brand-new node straight here.\n */\nexport function carryOrRewireRowBindings(\n node: Element,\n oldBindings: Binding[] | undefined,\n oldDisposers: Array<() => void> | undefined,\n newBindings: Binding[] | undefined,\n): { bindings: Binding[] | undefined; bindingDisposers: Array<() => void> | undefined } {\n const oldLen = oldBindings === undefined ? 0 : oldBindings.length;\n const newLen = newBindings === undefined ? 0 : newBindings.length;\n if (oldLen === newLen) {\n let same = true;\n for (let i = 0; i < newLen; i++) {\n if ((oldBindings as Binding[])[i].signal !== (newBindings as Binding[])[i].signal) {\n same = false;\n break;\n }\n }\n if (same) return { bindings: oldBindings, bindingDisposers: oldDisposers };\n }\n disposeRowBindings(oldDisposers);\n return {\n bindings: newBindings,\n bindingDisposers: newLen > 0 ? wireRowBindings(node, newBindings as Binding[]) : undefined,\n };\n}\n\n/**\n * GLOBAL-scope wiring core. Indexes `data-kfb`-marked elements + `kfb:`\n * comment markers under `scope` by binding id, then attaches an effect per\n * binding. Row-scope wiring lives in `wireRowBindings` (with its own inlined\n * root-attr fast path), so this always operates on the GLOBAL marker names.\n */\nfunction wireInto(\n scope: Element,\n bindings: Binding[],\n disposers: Array<() => void>,\n): void {\n const attrEls = indexAttrEls(scope, BIND_ATTR);\n const textMarkers = new Map<string, Comment>();\n collectComments(scope, TEXT_MARKER_PREFIX, textMarkers);\n\n for (const b of bindings) {\n if (b.kind === 'attr') {\n const el = attrEls.get(b.id);\n /* c8 ignore next -- defensive: a registered binding always emits its marker into `scope`. */\n if (el === undefined) continue;\n disposers.push(attachAttrEffect(el, b.attr, b.signal));\n } else {\n const marker = textMarkers.get(b.id);\n /* c8 ignore next -- defensive: same invariant as the attr branch. */\n if (marker === undefined) continue;\n disposers.push(attachTextEffect(marker, b.signal));\n }\n }\n}\n\n/**\n * Index every DESCENDANT element under `scope` carrying `attrName` by binding\n * id. Callers that also need the scope root itself check it separately (row\n * roots via the `rootIds` fast path; global holes are never on the mount root).\n */\nfunction indexAttrEls(scope: Element, attrName: string): Map<string, Element> {\n const map = new Map<string, Element>();\n for (const el of scope.querySelectorAll(`[${attrName}]`)) {\n for (const id of (el.getAttribute(attrName) as string).split(',')) map.set(id, el);\n }\n return map;\n}\n\n/** Attach a fine-grained attribute effect; returns its disposer. */\nfunction attachAttrEffect(el: Element, attr: string, signal: Signal<unknown>): () => void {\n return effect(() => setBoundAttr(el, attr, signal.value));\n}\n\n/**\n * Tracks the live text node each text-hole marker owns, so RE-wiring a hole\n * (KF-347 — an in-place row update whose binding instances changed) reuses\n * the node it inserted the first time instead of stacking a second one next\n * to it. Keyed on the marker comment: markers persist exactly as long as\n * their row/surround nodes do, so the WeakMap entries die with them.\n */\nconst insertedTextNodes = new WeakMap<Comment, Text>();\n\n/**\n * The live text node `marker` currently owns, or null (KF-374 — morph drops\n * static siblings of a bound hole). Position-strict, same rule as the reuse\n * check below: the node counts as owned only while it still sits at\n * `marker.nextSibling`. The morph's child pairing uses this to step PAST the\n * binding-inserted node — the template never contains it (templates carry\n * only the marker comment), so without the skip a template static sibling\n * pairs positionally with the inserted node, the morph overwrites the bound\n * text with the static content, and the real static sibling falls off the\n * cursor's tail and is removed.\n */\nexport function boundTextNodeOf(marker: Comment): Text | null {\n const t = insertedTextNodes.get(marker);\n return t !== undefined && marker.nextSibling === t ? t : null;\n}\n\n/**\n * Bind a live text node after `marker` to `signal`; returns the effect\n * disposer. Reuses the marker's previously-inserted node when it is still\n * exactly where we put it (`marker.nextSibling`) — a rewire then just\n * retargets the write, preserving node identity. Anything else (fresh parse,\n * node removed by a morph) inserts anew. The reuse check is deliberately\n * position-strict so a static text node that legitimately follows the marker\n * in template output can never be hijacked: static content sits at\n * `nextSibling` only until the FIRST wiring inserts our node in front of it.\n */\nfunction attachTextEffect(marker: Comment, signal: Signal<unknown>): () => void {\n let text = insertedTextNodes.get(marker);\n if (text === undefined || marker.nextSibling !== text) {\n text = (marker.ownerDocument as Document).createTextNode('');\n (marker.parentNode as Node).insertBefore(text, marker.nextSibling);\n insertedTextNodes.set(marker, text);\n }\n const node = text;\n return effect(() => { node.data = coerceText(signal.value); });\n}\n\n/**\n * Apply a bound value to a live attribute, mirroring the JSX runtime's\n * `renderAttr` rules: boolean/nullish toggling, `SafeHtml` (raw()) unwrapping,\n * and the KF-297 URL-screening that drops `javascript:` / `vbscript:` /\n * `data:text/html` on `href`/`src`/`formaction`/`action`/`xlink:href`.\n *\n * Unlike `renderAttr` (which builds an HTML string, so escapes with\n * `escapeAttr`), this writes the RAW value via `setAttribute` — the DOM stores\n * attribute values verbatim, so no HTML-escaping is applied here.\n *\n * The attribute NAME is trusted here: `on*` and malformed names are rejected at\n * binding-registration time in the JSX runtime's `jsx()` signal branch (shared\n * `assertEmittableAttrName`, KF-322), so `setAttribute('onclick', …)` — which\n * would install a live inline handler — can never reach this writer.\n */\nfunction setBoundAttr(el: Element, name: string, value: unknown): void {\n // KF-335: every attribute write below also syncs the matching form-state\n // property (checked/value/selected) — after user interaction the dirty flag\n // detaches the property from the attribute, so an attribute-only write\n // leaves the visible state stale. `syncFormProp` no-ops for other names.\n if (value == null || value === false) {\n el.removeAttribute(name);\n syncFormProp(el, name, '', false);\n return;\n }\n if (value === true) {\n el.setAttribute(name, '');\n syncFormProp(el, name, '', true);\n return;\n }\n // SafeHtml (raw()) is the documented opt-out — bypasses URL screening and is\n // written verbatim, matching renderAttr's SafeHtml branch.\n if (isSafeHtmlValue(value)) {\n el.setAttribute(name, value.__html);\n syncFormProp(el, name, value.__html, true);\n return;\n }\n const str = String(value);\n if (isDangerousUrlValue(name, str)) {\n // KF-340: throw in dev (fail loudly), warn+drop in prod.\n reportDangerousUrl('kerf binding', name, str);\n el.removeAttribute(name);\n return;\n }\n el.setAttribute(name, str);\n syncFormProp(el, name, str, true);\n}\n\n// Cross-bundle SafeHtml brand check (mirrors jsx-runtime's `isSafeHtml`).\n// Duplicated here — rather than importing `isSafeHtml` from jsx-runtime — to\n// keep the bindings ← jsx-runtime dependency acyclic. `Symbol.for` makes it\n// recognize SafeHtml instances from any copy of the module.\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\nfunction isSafeHtmlValue(v: unknown): v is { __html: string } {\n return typeof v === 'object' && v !== null\n && (v as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Coerce a bound text value: nullish + boolean render nothing (React-style). */\nfunction coerceText(value: unknown): string {\n if (value == null || typeof value === 'boolean') return '';\n return String(value);\n}\n\n/**\n * Collect comment markers with the given prefix under `node`, keyed by id.\n * Hand-rolled recursion (same as `mount.ts`'s comment walker) because\n * happy-dom's `TreeWalker` does not surface comment nodes.\n */\nfunction collectComments(node: Node, prefix: string, out: Map<string, Comment>): void {\n for (let c: Node | null = node.firstChild; c !== null; c = c.nextSibling) {\n if (c.nodeType === Node.COMMENT_NODE) {\n const data = (c as Comment).data;\n if (data.startsWith(prefix)) out.set(data.slice(prefix.length), c as Comment);\n } else if (c.nodeType === Node.ELEMENT_NODE) {\n collectComments(c, prefix, out);\n }\n }\n}\n\n// Re-exported so the JSX runtime imports signal-detection + binding\n// registration from one place.\nexport { isSignal };\n","/**\n * HTML / attribute escaping for the JSX runtime. Identical to the helpers\n * used in any reasonable HTML emitter — included here so kerf has no extra\n * runtime dependencies beyond `@preact/signals-core`.\n */\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","/**\n * JSX → HTML / SVG attribute name aliases.\n *\n * The JSX runtime translates camelCase attributes (React convention) to\n * the kebab-case / colon-form names the browser actually wants. Anything\n * not in this map is passed through verbatim — `data-*`, `aria-*`, and\n * any custom attribute work without ceremony.\n *\n * Lives in its own module so the alias data stays a separable concern from\n * `src/jsx-runtime.ts`'s runtime logic; the bulk of `jsx-runtime.ts` was this\n * table.\n */\n\nexport const ATTR_ALIASES: Record<string, string> = {\n // HTML attributes\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n accessKey: 'accesskey',\n autoCapitalize: 'autocapitalize',\n autoComplete: 'autocomplete',\n autoFocus: 'autofocus',\n autoPlay: 'autoplay',\n colSpan: 'colspan',\n contentEditable: 'contenteditable',\n crossOrigin: 'crossorigin',\n dateTime: 'datetime',\n defaultChecked: 'checked',\n defaultValue: 'value',\n encType: 'enctype',\n formAction: 'formaction',\n formEncType: 'formenctype',\n formMethod: 'formmethod',\n formNoValidate: 'formnovalidate',\n formTarget: 'formtarget',\n hrefLang: 'hreflang',\n inputMode: 'inputmode',\n maxLength: 'maxlength',\n minLength: 'minlength',\n noModule: 'nomodule',\n noValidate: 'novalidate',\n readOnly: 'readonly',\n referrerPolicy: 'referrerpolicy',\n rowSpan: 'rowspan',\n spellCheck: 'spellcheck',\n srcDoc: 'srcdoc',\n srcLang: 'srclang',\n srcSet: 'srcset',\n tabIndex: 'tabindex',\n useMap: 'usemap',\n\n // SVG presentation attributes (camelCase → kebab-case)\n strokeWidth: 'stroke-width',\n strokeLinecap: 'stroke-linecap',\n strokeLinejoin: 'stroke-linejoin',\n strokeDasharray: 'stroke-dasharray',\n strokeDashoffset: 'stroke-dashoffset',\n strokeMiterlimit: 'stroke-miterlimit',\n strokeOpacity: 'stroke-opacity',\n fillOpacity: 'fill-opacity',\n fillRule: 'fill-rule',\n clipPath: 'clip-path',\n clipRule: 'clip-rule',\n colorInterpolation: 'color-interpolation',\n colorInterpolationFilters: 'color-interpolation-filters',\n floodColor: 'flood-color',\n floodOpacity: 'flood-opacity',\n lightingColor: 'lighting-color',\n stopColor: 'stop-color',\n stopOpacity: 'stop-opacity',\n shapeRendering: 'shape-rendering',\n imageRendering: 'image-rendering',\n textRendering: 'text-rendering',\n pointerEvents: 'pointer-events',\n vectorEffect: 'vector-effect',\n paintOrder: 'paint-order',\n\n // SVG text/font attributes\n fontFamily: 'font-family',\n fontSize: 'font-size',\n fontStyle: 'font-style',\n fontVariant: 'font-variant',\n fontWeight: 'font-weight',\n fontStretch: 'font-stretch',\n textAnchor: 'text-anchor',\n textDecoration: 'text-decoration',\n dominantBaseline: 'dominant-baseline',\n alignmentBaseline: 'alignment-baseline',\n baselineShift: 'baseline-shift',\n letterSpacing: 'letter-spacing',\n wordSpacing: 'word-spacing',\n writingMode: 'writing-mode',\n\n // SVG marker attributes\n markerStart: 'marker-start',\n markerMid: 'marker-mid',\n markerEnd: 'marker-end',\n\n // SVG xlink (legacy but still used)\n xlinkHref: 'xlink:href',\n xlinkShow: 'xlink:show',\n xlinkActuate: 'xlink:actuate',\n xlinkType: 'xlink:type',\n xlinkRole: 'xlink:role',\n xlinkTitle: 'xlink:title',\n xlinkArcrole: 'xlink:arcrole',\n xmlBase: 'xml:base',\n xmlLang: 'xml:lang',\n xmlSpace: 'xml:space',\n xmlnsXlink: 'xmlns:xlink',\n};\n","/**\n * kerf JSX runtime.\n *\n * JSX renders to `SafeHtml`, which wraps both:\n * - `__html`: the flattened HTML string (what `toString()` returns; what\n * legacy/SSR consumers care about)\n * - `__segment`: a structured representation that distinguishes \"static\n * html\", \"keyed list\", and \"mixed\" content.\n *\n * Most renders are pure-static and the segment is just `{kind:'static',html}`.\n * When the tree contains a list (via `each()`) or a parent whose children\n * include a non-static segment, the runtime threads that structure up so\n * `mount()` can dispatch on it — running its native keyed reconciler for\n * the list parts and leaving the static surrounds to the general-purpose\n * diff.\n *\n * Configure in your `tsconfig.json`:\n *\n * \"jsx\": \"react-jsx\",\n * \"jsxImportSource\": \"kerfjs\"\n *\n * Then write JSX as you normally would — kerf provides the `jsx` /\n * `jsxs` / `jsxDEV` / `Fragment` exports the JSX transform looks for.\n */\n\nimport { bindAttr, bindMarkerAttr, bindText, isSignal } from './bindings.js';\nimport type { KerfBuiltinIntrinsicElements } from './jsx-types.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport {\n flatten,\n type ListSegment,\n mergeChildSegments,\n type Segment,\n wrapWithTags,\n} from './segment.js';\nimport { escapeAttr, escapeHtml } from './utils/escapeHtml.js';\nimport { ATTR_ALIASES } from './utils/jsx-attr-aliases.js';\nimport { isDangerousUrlValue, reportDangerousUrl } from './utils/urlScreen.js';\n\n// Cross-realm/cross-bundle brand. Using `Symbol.for` (the global registry)\n// means two `SafeHtml` classes from different module copies still recognize\n// each other. Same approach React uses for `$$typeof: Symbol.for('react.element')`.\n// Without this, `instanceof SafeHtml` fails when the consumer's bundler ends\n// up loading two copies of kerf (separate barrel + jsx-runtime entries,\n// monorepo dedup misses, ESM/CJS interop, etc.).\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\n\nexport class SafeHtml {\n readonly __html: string;\n readonly __segment: Segment;\n // Branded so `isSafeHtml()` recognizes instances from any copy of this module.\n readonly [SAFE_HTML_BRAND] = true as const;\n constructor(input: string | Segment) {\n if (typeof input === 'string') {\n this.__segment = { kind: 'static', html: input };\n this.__html = input;\n } else {\n this.__segment = input;\n this.__html = flatten(input, false);\n }\n }\n toString(): string {\n return this.__html;\n }\n}\n\n/**\n * Type guard for `SafeHtml`. Prefer this over `instanceof SafeHtml` — it works\n * across module copies (e.g. when the consumer's bundler loads kerf's barrel\n * and JSX-runtime entries as independent modules).\n *\n * Security note (KF-321): this is a duck-check on the global `Symbol.for` brand,\n * so same-realm code *can* forge a \"trusted\" value — `{ [SAFE_HTML_BRAND]: true,\n * __html: '<img onerror=…>' }` passes and bypasses escaping + the URL screen.\n * This is intentional and not a vulnerability: minting the brand requires a\n * Symbol key, which no data channel (JSON.parse, form/query/localStorage,\n * structuredClone, JSON-based prototype-pollution) can produce — those all yield\n * string keys. The only way to forge it is to run JS that writes the symbol, and\n * such code can equally `import { raw }`. Forgery therefore grants no capability\n * an attacker with code execution lacks — the same posture as React's\n * `$$typeof: Symbol.for('react.element')`. The global `Symbol.for` (vs a\n * module-private symbol) is a deliberate cross-bundle-recognition tradeoff (see\n * the `SAFE_HTML_BRAND` note above); a private symbol would additionally block\n * same-realm forgery, but only closes a non-threat at the cost of that\n * recognition, so it's kept global by design.\n */\nexport function isSafeHtml(value: unknown): value is SafeHtml {\n return typeof value === 'object'\n && value !== null\n && (value as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Inject a pre-escaped HTML string. Use sparingly — caller is responsible for escaping. */\nexport function raw(html: string): SafeHtml {\n return new SafeHtml(html);\n}\n\n/**\n * Internal: build a `SafeHtml` representing a list segment. Used by\n * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction.\n */\nexport function listSafeHtml(\n id: string,\n items: ListSegment['items'],\n source?: object,\n): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items, source });\n}\n\n/**\n * Internal: build a `SafeHtml` representing a granular list segment with\n * patches (KF-92). The reconciler applies the patches to the existing\n * binding directly, skipping the per-item iteration that the snapshot\n * `listSafeHtml` requires. `items` is included for fall-through paths\n * (toString during SSR, fall-back when the binding doesn't exist yet).\n *\n * Patch HTML is rendered upstream (in `each()`) inside a try/catch — see\n * KF-99 — so by the time we get here every `update` / `insert` patch\n * already carries a `html` string, and the reconciler does no further\n * row rendering.\n */\nexport function granularListSafeHtml(\n id: string,\n items: ListSegment['items'],\n patches: NonNullable<ListSegment['patches']>,\n source?: object,\n): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items, patches, source });\n}\n\n// KF-294: `ReadonlySignal<unknown>` (covariant) accepts both `signal()` and\n// `computed()` values of any T handed straight into a text hole — the runtime\n// binds them fine-grained instead of stringifying.\ntype Child = SafeHtml | string | number | boolean | null | undefined | ReadonlySignal<unknown>;\ntype Children = Child | Children[];\n\ninterface Props {\n children?: Children;\n [key: string]: unknown;\n}\n\nconst VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'source', 'track', 'wbr',\n]);\n\n/**\n * Convert a single JSX child into a Segment. Handles SafeHtml passthrough,\n * primitive coercion + escaping, arrays (recursive), and the nullish/false\n * skip cases.\n */\nfunction toSegment(child: Children): Segment {\n if (child == null || typeof child === 'boolean') return { kind: 'static', html: '' };\n // KF-294: a signal handed straight into a text position. Inside a mount\n // render, emit a comment marker and record a binding so the text node\n // updates fine-grained (no render re-run). Outside a mount (SSR/toString),\n // snapshot the current value as escaped text.\n if (isSignal(child)) {\n const marker = bindText(child);\n if (marker !== null) return { kind: 'static', html: marker };\n const v = (child as Signal<unknown>).value;\n return { kind: 'static', html: v == null || typeof v === 'boolean' ? '' : escapeHtml(String(v)) };\n }\n if (isSafeHtml(child)) {\n // Cross-bundle SafeHtml shims (KF-14 case) may have only `__html`.\n return child.__segment ?? { kind: 'static', html: child.__html };\n }\n if (typeof child === 'string') return { kind: 'static', html: escapeHtml(child) };\n if (typeof child === 'number') return { kind: 'static', html: String(child) };\n if (Array.isArray(child)) return mergeChildSegments(child.map(toSegment));\n // Catch the common mistake of passing a DOM element (e.g. the result of\n // toElement(...)) as a JSX child. The runtime renders to HTML strings, so\n // DOM nodes can't be composed — they'd silently serialize to \"\" and their\n // event listeners would be lost. Throw loudly so this can't sneak in.\n const maybeNode = child as unknown;\n if (typeof maybeNode === 'object' && maybeNode !== null\n && ('nodeType' in maybeNode || 'outerHTML' in maybeNode)) {\n throw new Error(\n 'JSX: DOM elements cannot be passed as children (the JSX runtime renders to HTML strings). '\n + 'Build the tree in one JSX expression and use querySelector after toElement() to get element refs.',\n );\n }\n throw new Error(\n `JSX: unsupported child of type ${describeValue(child)}. `\n + 'Children must be SafeHtml, string, number, boolean, null, undefined, or an array of those. '\n + 'Common mistakes: passing a Signal/Store object directly (use signal.value or store.state.value), '\n + 'passing a function (call it first), or passing a Promise (await it before render).',\n );\n}\n\nfunction describeValue(v: unknown): string {\n if (Array.isArray(v)) return 'array';\n if (typeof v === 'object' && v !== null) {\n const ctor = (v as { constructor?: { name?: string } }).constructor?.name;\n return ctor && ctor !== 'Object' ? `object (${ctor})` : 'object';\n }\n return typeof v;\n}\n\n/**\n * A well-formed HTML/SVG attribute name: a letter/underscore/colon, then\n * letters, digits, or `_.:-` — covers `class`, `data-id`, `aria-label`,\n * `xlink:href`, `xmlns:xlink`, `stroke-width`, `viewBox`, etc. A name outside\n * this shape can only come from spreading untrusted KEYS into JSX\n * (`<div {...obj}>`), and is exactly the shape that could carry the whitespace,\n * `>`, `=`, quotes, or control chars needed to break out of the open tag and\n * inject markup — so `renderAttr` rejects it (KF-306).\n */\nconst SAFE_ATTR_NAME = /^[A-Za-z_:][\\w.:-]*$/;\n\n/**\n * Reject an attribute NAME that kerf must never route into HTML or the DOM.\n * Shared by BOTH attribute paths so they enforce one contract (KF-306, KF-322):\n *\n * - the static string path (`renderAttr`) — a name is emitted verbatim into\n * the open tag, and `on*` values become live inline handlers once parsed;\n * - the fine-grained bound path (`jsx()` signal branch → `bindAttr` →\n * `setBoundAttr` → `el.setAttribute(name, …)`) — `setAttribute('onclick', …)`\n * installs a LIVE handler in the browser, an XSS vector that would otherwise\n * bypass the static guard entirely.\n *\n * Two rejections:\n * 1. `on*` (any case) — kerf's model is event delegation, never inline\n * handlers. `isFn` tailors the message: function values get the\n * delegate() migration pointer; string/signal/other values get the\n * XSS-aware message.\n * 2. a malformed name (spread of untrusted keys, `<div {...obj}>`) that could\n * break out of the open tag. The bound path can't actually inject markup\n * this way — `setAttribute` throws `InvalidCharacterError` rather than\n * parsing — but the same guard runs on both paths for one consistent\n * contract. Validated post-alias; every `ATTR_ALIASES` value is itself a\n * valid name, so aliasing is unaffected.\n */\nfunction assertEmittableAttrName(key: string, name: string, isFn: boolean): void {\n if (/^on[a-z]/i.test(name)) {\n if (isFn) {\n throw new Error(\n `JSX: inline event handlers like ${key}={fn} are not supported by kerf's JSX → HTML-string runtime. `\n + 'Use event delegation from the mount root instead:\\n\\n'\n + ' delegate(rootEl, \\'click\\', \\'[data-action=\"...\"]\\', (evt, target) => { ... });\\n'\n + ' <button data-action=\"...\">click</button>\\n\\n'\n + 'See docs/5-event-delegation.md for the tier-1/tier-2/tier-3 model.',\n );\n }\n throw new Error(\n `JSX: event-handler attribute ${JSON.stringify(key)} is not allowed — an `\n + '`on*` attribute (whether a string emitted into HTML or a signal bound via '\n + 'setAttribute) installs a live inline handler, an XSS vector. '\n + 'kerf uses event delegation: delegate(rootEl, \\'click\\', \\'[data-action=\"...\"]\\', handler). '\n + 'See docs/5-event-delegation.md.',\n );\n }\n if (!SAFE_ATTR_NAME.test(name)) {\n throw new Error(\n `JSX: invalid attribute name ${JSON.stringify(key)}. Attribute names must be a `\n + 'letter/underscore/colon followed by letters, digits, or \"_.:-\" (e.g. class, '\n + 'data-id, aria-label, xlink:href). This usually means an untrusted object was '\n + 'spread into JSX ({...obj}) with attacker-controlled keys — validate keys first.',\n );\n }\n}\n\nfunction renderAttr(key: string, value: unknown): string {\n return renderAttrNamed(key, ATTR_ALIASES[key] ?? key, value);\n}\n\n/**\n * Core attribute renderer. `key` is the author-written attribute key (used in\n * error messages); `name` is the final emitted attribute name — post-alias for\n * JSX, verbatim for the `kerfjs/html` tagged-template path.\n */\nfunction renderAttrNamed(key: string, name: string, value: unknown): string {\n if (value == null || value === false) return '';\n assertEmittableAttrName(key, name, typeof value === 'function');\n if (value === true) return ` ${name}`;\n let strValue: string;\n if (isSafeHtml(value)) {\n strValue = value.__html;\n } else if (typeof value === 'number') {\n strValue = String(value);\n } else if (typeof value === 'string') {\n // URL-screening shared with the fine-grained binding writer (KF-297).\n // `SafeHtml` values (raw()) skip this — they hit the branch above.\n // KF-340: throw in dev (fail loudly), warn+drop in prod.\n if (isDangerousUrlValue(name, value)) {\n reportDangerousUrl('JSX', name, value);\n return '';\n }\n strValue = escapeAttr(value);\n } else {\n throw new Error(\n `JSX: unsupported value for attribute \"${key}\" — got ${describeValue(value)}. `\n + 'Attribute values must be string, number, boolean, null, undefined, or SafeHtml. '\n + 'Did you mean to read .value off a Signal, or stringify the object first?',\n );\n }\n return ` ${name}=\"${strValue}\"`;\n}\n\nexport function jsx(tag: string | ((props: Props) => SafeHtml), props: Props): SafeHtml {\n if (typeof tag === 'function') return tag(props);\n\n const { children, ...attrs } = props;\n let attrStr = '';\n let bindIds: string[] | null = null;\n for (const [k, v] of Object.entries(attrs)) {\n // KF-294: a signal handed straight into an attribute. Inside a mount\n // render, register a binding and mark the element (via `data-kfb` for\n // static-surround holes / `data-kfbrow` for each()-row holes) instead of\n // emitting the attribute — the wiring pass sets it after parse and keeps\n // it fine-grained. Outside a mount, snapshot the current value.\n if (isSignal(v)) {\n const name = ATTR_ALIASES[k] ?? k;\n // Reject `on*` / malformed names on the bound path too (KF-322 completes\n // KF-306). Unconditional: a signal's value can change over time, so we\n // can't gate on the current value — an `onclick={signal}` binding would\n // otherwise install a live inline handler via setAttribute, bypassing the\n // static renderAttr guard entirely. `isFn` is false: a signal is not a\n // function value, so this routes to the XSS-aware message.\n assertEmittableAttrName(k, name, false);\n const id = bindAttr(name, v);\n if (id !== null) {\n (bindIds ??= []).push(id);\n continue;\n }\n attrStr += renderAttr(k, (v as Signal<unknown>).value);\n continue;\n }\n attrStr += renderAttr(k, v);\n }\n // All signal attrs on one element share a scope, so the marker attr name is\n // fetched once (avoids a per-attr object alloc from bindAttr).\n if (bindIds !== null) attrStr += ` ${bindMarkerAttr()}=\"${bindIds.join(',')}\"`;\n\n if (VOID_TAGS.has(tag)) return new SafeHtml(`<${tag}${attrStr}>`);\n\n const childSegment: Segment = children != null\n ? toSegment(children)\n : { kind: 'static', html: '' };\n return new SafeHtml(wrapWithTags(childSegment, `<${tag}${attrStr}>`, `</${tag}>`));\n}\n\nexport { jsx as jsxs };\n// vitest's dev-mode JSX transform emits `jsxDEV(tag, props, ...)`; the\n// alias lets tests import this module without the production build pipeline\n// caring.\nexport { jsx as jsxDEV };\n\nexport function Fragment({ children }: { children?: Children }): SafeHtml {\n return new SafeHtml(children != null ? toSegment(children) : { kind: 'static', html: '' });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n export type Element = SafeHtml;\n export interface ElementChildrenAttribute {\n children: unknown;\n }\n // Per-tag attribute contracts live in `./jsx-types.ts` as\n // `KerfBuiltinIntrinsicElements`. Re-exposed as an **interface** (not a\n // type alias) so consumers can declaration-merge custom-element tags\n // (KF-100):\n //\n // declare module 'kerfjs/jsx-runtime' {\n // namespace JSX {\n // interface IntrinsicElements {\n // 'my-element': KerfCustomElement & { foo?: string };\n // }\n // }\n // }\n //\n // KF-123: the imported interface is named `KerfBuiltinIntrinsicElements`\n // upstream so tsup/tsc cannot strip an import alias and end up emitting\n // `interface IntrinsicElements extends IntrinsicElements {}` in the .d.ts\n // — that shadowed form self-resolves to empty and breaks every `<tag>` in\n // consumer .tsx with TS2339. Verified against `dist/jsx-runtime.d.ts` by\n // `tests/dist/jsx-typing/` on every `npm run build`.\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n export interface IntrinsicElements extends KerfBuiltinIntrinsicElements {}\n}\n\n/**\n * Internal coordination exports for the `kerfjs/html` tagged-template\n * front-end (`src/html.ts`). Underscore-prefixed and deliberately NOT in the\n * main barrel — the contract is that `html\\`\\`` routes through the exact same\n * value semantics as JSX (text holes: `_toSegment`; attribute holes:\n * `_assertEmittableAttrName` + `_renderAttrVerbatim`), so the two authoring\n * paths cannot drift. `_renderAttrVerbatim` skips the camelCase\n * `ATTR_ALIASES` table: template authors write real HTML attribute names\n * (`class`, not `className`).\n */\nexport function _toSegment(child: unknown): Segment {\n return toSegment(child as Children);\n}\n\nexport function _renderAttrVerbatim(name: string, value: unknown): string {\n return renderAttrNamed(name, name, value);\n}\n\nexport { assertEmittableAttrName as _assertEmittableAttrName };\n\n/**\n * Public re-exports of the JSX type primitives so consumers can compose\n * attribute interfaces for custom elements without reaching into\n * `kerfjs/jsx-types` (which is intentionally not in `package.json#exports`).\n */\nexport type {\n AttrLike,\n AttrValue,\n DataAriaAttrs,\n KerfBaseAttrs,\n KerfCustomElement,\n} from './jsx-types.js';\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/item-version.ts
|
|
2
|
+
var versions = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
var anyVersioned = false;
|
|
4
|
+
function bumpItemVersion(item) {
|
|
5
|
+
if (item === null || typeof item !== "object" && typeof item !== "function") return;
|
|
6
|
+
anyVersioned = true;
|
|
7
|
+
versions.set(item, (versions.get(item) ?? 0) + 1);
|
|
8
|
+
}
|
|
9
|
+
function itemVersion(item) {
|
|
10
|
+
return anyVersioned ? versions.get(item) ?? 0 : 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { bumpItemVersion, itemVersion };
|
|
14
|
+
//# sourceMappingURL=chunk-QIP723L4.js.map
|
|
15
|
+
//# sourceMappingURL=chunk-QIP723L4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/item-version.ts"],"names":[],"mappings":";AA8BA,IAAM,QAAA,uBAAe,OAAA,EAAwB;AAC7C,IAAI,YAAA,GAAe,KAAA;AAWZ,SAAS,gBAAgB,IAAA,EAAqB;AACnD,EAAA,IAAI,SAAS,IAAA,IAAS,OAAO,SAAS,QAAA,IAAY,OAAO,SAAS,UAAA,EAAa;AAC/E,EAAA,YAAA,GAAe,IAAA;AACf,EAAA,QAAA,CAAS,IAAI,IAAA,EAAA,CAAO,QAAA,CAAS,IAAI,IAAI,CAAA,IAAK,KAAK,CAAC,CAAA;AAClD;AAGO,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,OAAO,YAAA,GAAe,QAAA,CAAS,GAAA,CAAI,IAAI,KAAK,CAAA,GAAI,CAAA;AAClD","file":"chunk-QIP723L4.js","sourcesContent":["/**\n * Per-item content version — makes a same-ref `arraySignal.update()` visible to\n * the row memo (KF-418).\n *\n * kerf memoizes `each()` rows by object IDENTITY (`WeakMap<item, CacheEntry>`),\n * so a same-ref mutation — `update(i, r => { r.x = 1; return r })` — changes the\n * row's content without changing its identity and is invisible to the memo. The\n * KF-414 fix repaired only the single list that drained the patch; every other\n * consumer (a second list over the same signal, a second `mount()`, a\n * plain-array `filter()` view of the same items) still trusted its own memo\n * entry, keyed on an identity that didn't change, and rendered stale forever.\n *\n * `arraySignal.update()` bumps the returned item's version here; each row's\n * `CacheEntry` records the version it rendered at, and a cache hit now requires\n * the version to still match. A same-ref update therefore re-renders the row in\n * EVERY consumer, because they all read the same version off the same item ref.\n *\n * Why a shared module rather than a field on `ArraySignal`:\n * - the main-bundle `each()` must read the version, and importing the\n * `kerfjs/array-signal` subpath into the main bundle would defeat KF-95\n * (arraySignal only ships when the consumer imports it). This module lives in\n * the main bundle; the subpath imports it, not the other way round.\n * - keying on the item object (not the signal) is what lets a plain-array\n * `filter()` view — which has no reference to the arraySignal — still see the\n * bump: it holds the same item refs.\n *\n * `anyVersioned` keeps the common case free: until some `update()` actually\n * bumps a version, every lookup returns 0 without touching the WeakMap, so a app\n * that never mutates in place pays nothing per row.\n */\nconst versions = new WeakMap<object, number>();\nlet anyVersioned = false;\n\n/**\n * Bump `item`'s content version. Called by `arraySignal.update()` on the item it\n * returns. A no-op for any value that isn't a valid WeakMap key — a primitive\n * item (an `arraySignal<number>` used purely as a standalone signal) can never be\n * an `each()` row, since rows are memoized by object identity, so it needs no\n * content version. Guarding here keeps `update()` from throwing `Invalid value\n * used as weak map key` and leaves `anyVersioned` false until a real object is\n * versioned (so the primitive path stays a zero-cost lookup).\n */\nexport function bumpItemVersion(item: unknown): void {\n if (item === null || (typeof item !== 'object' && typeof item !== 'function')) return;\n anyVersioned = true;\n versions.set(item, (versions.get(item) ?? 0) + 1);\n}\n\n/** `item`'s current content version — 0 if it has never been same-ref-updated. */\nexport function itemVersion(item: object): number {\n return anyVersioned ? versions.get(item) ?? 0 : 0;\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { signal } from './chunk-3APBEVHF.js';
|
|
2
|
+
import { devHooks } from './chunk-VVDJLWMP.js';
|
|
3
|
+
|
|
4
|
+
// src/store.ts
|
|
5
|
+
var REGISTRY = [];
|
|
6
|
+
function defineStore(spec) {
|
|
7
|
+
const internal = signal(spec.initial());
|
|
8
|
+
const warnCtx = devHooks.narrowSet ? { warned: false } : null;
|
|
9
|
+
const set = (next) => {
|
|
10
|
+
const toRaw = devHooks.storeToRaw;
|
|
11
|
+
const raw = toRaw ? toRaw(next) : next;
|
|
12
|
+
if (warnCtx) devHooks.narrowSet?.(internal.value, raw, warnCtx);
|
|
13
|
+
internal.value = raw;
|
|
14
|
+
};
|
|
15
|
+
const get = () => {
|
|
16
|
+
const v = internal.value;
|
|
17
|
+
const readonly = devHooks.storeReadonly;
|
|
18
|
+
if (readonly && v !== null && typeof v === "object") {
|
|
19
|
+
return readonly(v);
|
|
20
|
+
}
|
|
21
|
+
return v;
|
|
22
|
+
};
|
|
23
|
+
const actions = spec.actions(set, get);
|
|
24
|
+
const store = {
|
|
25
|
+
state: internal,
|
|
26
|
+
actions,
|
|
27
|
+
reset() {
|
|
28
|
+
internal.value = spec.initial();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
REGISTRY.push(store);
|
|
32
|
+
return store;
|
|
33
|
+
}
|
|
34
|
+
function resetAllStores() {
|
|
35
|
+
for (const s of REGISTRY) s.reset();
|
|
36
|
+
}
|
|
37
|
+
function clearStoreRegistry() {
|
|
38
|
+
REGISTRY.length = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { clearStoreRegistry, defineStore, resetAllStores };
|
|
42
|
+
//# sourceMappingURL=chunk-SAYPJ6XR.js.map
|
|
43
|
+
//# sourceMappingURL=chunk-SAYPJ6XR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts"],"names":[],"mappings":";;;;AAsCA,IAAM,WAAyC,EAAC;AAEzC,SAAS,YACd,IAAA,EACyB;AACzB,EAAA,MAAM,QAAA,GAA2B,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,CAAA;AAKtD,EAAA,MAAM,UAAkC,QAAA,CAAS,SAAA,GAAY,EAAE,MAAA,EAAQ,OAAM,GAAI,IAAA;AAEjF,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAuB;AAMlC,IAAA,MAAM,QAAQ,QAAA,CAAS,UAAA;AACvB,IAAA,MAAM,GAAA,GAAM,KAAA,GAAQ,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAClC,IAAA,IAAI,SAAS,QAAA,CAAS,SAAA,GAAY,QAAA,CAAS,KAAA,EAAO,KAAK,OAAO,CAAA;AAC9D,IAAA,QAAA,CAAS,KAAA,GAAQ,GAAA;AAAA,EACnB,CAAA;AAOA,EAAA,MAAM,MAAM,MAAwB;AAClC,IAAA,MAAM,IAAI,QAAA,CAAS,KAAA;AACnB,IAAA,MAAM,WAAW,QAAA,CAAS,aAAA;AAC1B,IAAA,IAAI,QAAA,IAAY,CAAA,KAAM,IAAA,IAAQ,OAAO,MAAM,QAAA,EAAU;AACnD,MAAA,OAAO,SAAS,CAAoB,CAAA;AAAA,IACtC;AACA,IAAA,OAAO,CAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAA;AAErC,EAAA,MAAM,KAAA,GAAiC;AAAA,IACrC,KAAA,EAAO,QAAA;AAAA,IACP,OAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAK,OAAA,EAAQ;AAAA,IAChC;AAAA,GACF;AAEA,EAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,EAAA,OAAO,KAAA;AACT;AAOO,SAAS,cAAA,GAAuB;AACrC,EAAA,KAAA,MAAW,CAAA,IAAK,QAAA,EAAU,CAAA,CAAE,KAAA,EAAM;AACpC;AAMO,SAAS,kBAAA,GAA2B;AACzC,EAAA,QAAA,CAAS,MAAA,GAAS,CAAA;AACpB","file":"chunk-SAYPJ6XR.js","sourcesContent":["/**\n * `defineStore({ initial, actions })` — composable testable stores layered on\n * top of `reactive.ts`'s signals.\n *\n * Three rules:\n * 1. `state` is read-only. Consumers read via `state.value` or subscribe via\n * `effect()`. They cannot write directly.\n * 2. `actions` is the only mutation surface. All writes go through named\n * action functions. This is what makes stores testable — assert against\n * actions, not against arbitrary writes.\n * 3. `reset()` resets to `initial()`. Always defined; tests use it for\n * setup, lifecycle hooks (route change, sign-out, etc.) use it for\n * tear-down.\n *\n * A module-level registry tracks every store created via `defineStore()`;\n * `resetAllStores()` walks the registry and calls each `reset()`. Useful for\n * tests + project-switch / logout / route-reset scenarios where every piece\n * of client state should return to its initial shape.\n */\n\nimport { devHooks, type WarnOnceContext } from './dev-hooks.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport { signal } from './reactive.js';\n\nexport interface Store<TState, TActions> {\n /** Read-only reactive view. Consumers read `state.value` or subscribe via `effect()`. */\n readonly state: ReadonlySignal<TState>;\n /** Named mutators — the only way to change state. */\n readonly actions: TActions;\n /** Reset state to `initial()`. Used by tests and lifecycle hooks. */\n reset(): void;\n}\n\ninterface DefineStoreSpec<TState, TActions> {\n initial: () => TState;\n actions: (set: (next: TState) => void, get: () => Readonly<TState>) => TActions;\n}\n\nconst REGISTRY: Array<{ reset: () => void }> = [];\n\nexport function defineStore<TState, TActions>(\n spec: DefineStoreSpec<TState, TActions>,\n): Store<TState, TActions> {\n const internal: Signal<TState> = signal(spec.initial());\n // KF-212: per-store one-shot dedup for the opt-in narrow-set warning.\n // Default off; consumers opt in via `KERF_DEV_WARN_NARROW_SET=1` in dev.\n // Allocated only when the diagnostics are installed — production never pays\n // for an object it will never read.\n const warnCtx: WarnOnceContext | null = devHooks.narrowSet ? { warned: false } : null;\n\n const set = (next: TState): void => {\n // With `kerfjs/dev` installed, `next` may carry proxies handed back by the\n // `get()` trap (e.g. `set({ ...get(), count: 1 })`). Unwrap them so the\n // internal signal only ever holds a plain object — the narrow-set warning\n // and every consumer read see raw state, never a Proxy. Production stores\n // the bare reference.\n const toRaw = devHooks.storeToRaw;\n const raw = toRaw ? toRaw(next) : next;\n if (warnCtx) devHooks.narrowSet?.(internal.value, raw, warnCtx);\n internal.value = raw;\n };\n // With the diagnostics installed, wrap the reference returned to actions in a\n // deep read-only Proxy so that `get().count = 42` / `get().nested.x = 1`\n // (documented Rule 8 violations) throw a `TypeError` instead of silently\n // landing on the underlying state without notifying subscribers. The live\n // state object is never frozen or mutated, so external references to it stay\n // writable. Production returns the bare reference — no proxy, no wrapping.\n const get = (): Readonly<TState> => {\n const v = internal.value;\n const readonly = devHooks.storeReadonly;\n if (readonly && v !== null && typeof v === 'object') {\n return readonly(v as TState & object);\n }\n return v;\n };\n\n const actions = spec.actions(set, get);\n\n const store: Store<TState, TActions> = {\n state: internal,\n actions,\n reset() {\n internal.value = spec.initial();\n },\n };\n\n REGISTRY.push(store);\n return store;\n}\n\n/**\n * Reset every store registered via `defineStore()` to its `initial()` value.\n * Used by tests and by application lifecycle hooks (project switch, logout,\n * route reset).\n */\nexport function resetAllStores(): void {\n for (const s of REGISTRY) s.reset();\n}\n\n/**\n * Test helper — clears the registry. Exposed via the `kerfjs/testing` subpath,\n * not the main `kerfjs` entry. Unit tests use it to isolate stores between cases.\n */\nexport function clearStoreRegistry(): void {\n REGISTRY.length = 0;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/dev-hooks.ts
|
|
2
|
+
var devHooks = {};
|
|
3
|
+
function installDevHooks(hooks) {
|
|
4
|
+
Object.assign(devHooks, hooks);
|
|
5
|
+
}
|
|
6
|
+
function clearDevHooks() {
|
|
7
|
+
for (const key of Object.keys(devHooks)) {
|
|
8
|
+
delete devHooks[key];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { clearDevHooks, devHooks, installDevHooks };
|
|
13
|
+
//# sourceMappingURL=chunk-VVDJLWMP.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-VVDJLWMP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev-hooks.ts"],"names":[],"mappings":";AA4IO,IAAM,WAAqB;AAS3B,SAAS,gBAAgB,KAAA,EAAuB;AACrD,EAAA,MAAA,CAAO,MAAA,CAAO,UAAU,KAAK,CAAA;AAC/B;AAOO,SAAS,aAAA,GAAsB;AACpC,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AACvC,IAAA,OAAQ,SAAqC,GAAG,CAAA;AAAA,EAClD;AACF","file":"chunk-VVDJLWMP.js","sourcesContent":["/**\n * The dev-hook registry — kerf's single seam between production code and the\n * opt-in development diagnostics.\n *\n * ## Why this exists\n *\n * kerf used to INFER whether it was running in development, by reading\n * `globalThis.process?.env?.NODE_ENV` through `utils/devMode.ts`. That\n * inference cannot be made correct, and it was wrong in the most common case:\n * bundlers substitute the BARE `process.env.NODE_ENV` token and never create a\n * `globalThis.process` object for browser targets, so the read returned\n * `undefined` and `undefined !== 'production'` resolved to DEVELOPMENT inside\n * production browser bundles.\n *\n * It also could not be fixed by rewriting the expression. Only the\n * *production* answer can be made static: `X && false` folds to a constant for\n * any side-effect-free `X`, while `X && true` does not. So any form that a\n * bundler can eliminate is also a form that treats \"no `process` binding\" as\n * production — which silently disables every warning in the no-build/CDN path\n * and in browser dev bundles.\n *\n * The fix is to stop guessing. Every environment already has a correct,\n * statically-foldable dev flag; what none of them offers is a way to hand that\n * flag to a *library*. So the consumer writes the conditional, in their own\n * code, with their own flag:\n *\n * ```js\n * if (import.meta.env.DEV) await import('kerfjs/dev'); // Vite\n * if (process.env.NODE_ENV !== 'production') await import('kerfjs/dev');\n * ```\n *\n * Because that condition folds to `false` in the consumer's production build,\n * the entire statement is eliminated and the dev chunk is never emitted or\n * fetched. Installation IS the development signal — there is nothing left to\n * detect, and no environment kerf can be wrong about.\n *\n * ## The contract\n *\n * Core modules never import a `dev-*` module. They read a nullable slot off\n * `devHooks` and call through it:\n *\n * ```ts\n * devHooks.listRebind?.(id, marker.parentElement as Element);\n * ```\n *\n * When nothing is installed every slot is `undefined`, so the cost is one\n * property read per call site and the `dev-*` modules are unreachable from the\n * main entry — which is what lets a bundler drop them. This is why the gate\n * lives at the CALL SITE rather than inside each warner: an unconditional call\n * into a self-gating warner keeps the module reachable no matter how the gate\n * is written, so no amount of dead-code elimination can reclaim it.\n *\n * Slots ending in `Enabled` are predicates rather than warnings. They exist for\n * the handful of call sites that must decide whether to do *expensive\n * preparatory work* — capturing the previous render's binding list, allocating\n * a per-render `Map` — before there is anything to warn about. Core must check\n * those before paying the cost, exactly as it checked the old `isOptedIn()`\n * exports.\n *\n * Each warner keeps its own internal opt-in check (the `KERF_DEV_WARN_*` env\n * reads). Installation decides whether the diagnostics are *present*; the\n * individual warner still decides whether it is *switched on*.\n *\n * @see docs/11-dev-warnings.md\n */\n\nimport type { Binding } from './bindings.js';\nimport type { ListBinding } from './list-binding.js';\nimport type { Signal } from './reactive.js';\n\n/** Per-mount / per-store one-shot dedup flag, owned by the caller in core. */\nexport interface WarnOnceContext {\n warned: boolean;\n}\n\nexport interface DevHooks {\n // --- reactive.ts -------------------------------------------------------\n /**\n * Replaces `signal()`'s constructor so writes to never-subscribed signals can\n * warn. Resolved at signal-CREATION time, so signals created before the dev\n * entry is installed stay plain — see `signalsCreatedBeforeInstall`.\n */\n signalFactory?: <T>(value: T) => Signal<T>;\n /**\n * Wraps an `effect()` body so `delegate()` can detect that it is running\n * inside one. Returns the body to actually run.\n */\n wrapEffect?: (fn: () => void | (() => void)) => () => void | (() => void);\n\n // --- delegate.ts -------------------------------------------------------\n delegateInEffect?: (fn: 'delegate' | 'delegateCapture') => void;\n\n // --- store.ts ----------------------------------------------------------\n narrowSet?: (prev: unknown, next: unknown, ctx: WarnOnceContext) => void;\n /** Deep read-only proxy for the `get()` snapshot, so stray writes throw. */\n storeReadonly?: <T extends object>(state: T) => T;\n /** Unwraps a proxy handed back through `set({ ...get() })`. */\n storeToRaw?: <T>(next: T) => T;\n\n // --- mount.ts ----------------------------------------------------------\n listenerRebuild?: (rootEl: Element) => MutationObserver | null;\n listIdShift?: (id: string) => void;\n parserRepair?: (html: string) => void;\n staleBindingEnabled?: () => boolean;\n staleBinding?: (prevWired: readonly Binding[], current: readonly Binding[]) => void;\n listInvariantsEnabled?: () => boolean;\n listInvariants?: (\n rootEl: Element,\n bindings: ReadonlyMap<string, ListBinding>,\n expectedCounts?: ReadonlyMap<string, number>,\n ) => void;\n valueOnlyRerender?: (prevHtml: string, nextHtml: string, ctx: WarnOnceContext) => void;\n listRebind?: (id: string, liveParent: Element) => void;\n eachInMorphSkip?: (id: string, liveParent: Element, rootEl: Element) => void;\n missingRowKey?: (\n rowEl: Element,\n rowHtml: string,\n binding: { warnedMissingKey?: boolean },\n ) => void;\n\n // --- each.ts -----------------------------------------------------------\n staleIndexEnabled?: () => boolean;\n staleIndex?: (id: string) => void;\n duplicateCacheKeys?: (id: string, segItems: readonly { cacheKey: unknown }[]) => void;\n\n // --- utils/urlScreen.ts ------------------------------------------------\n /**\n * When installed, a screened URL throws instead of warning-and-dropping.\n * A slot rather than a boolean so the check stays uniform with the rest.\n */\n urlScreenThrow?: (message: string) => never;\n}\n\n/**\n * The live slot table. Mutable by design — this is the fourth sanctioned\n * module-level mutable location (Design rule 5), and like `store.ts:REGISTRY`\n * it depends on there being exactly ONE copy at runtime. `tsup`'s\n * `splitting: true` guarantees that: shared modules are promoted into a single\n * chunk that both the main entry and the `kerfjs/dev` entry import.\n */\nexport const devHooks: DevHooks = {};\n\n/**\n * Install (or extend) the dev hooks. Called by the `kerfjs/dev` entry; not part\n * of the public API surface.\n *\n * Merges rather than replaces, so a consumer can install the standard bundle\n * and then override a single slot in a test.\n */\nexport function installDevHooks(hooks: DevHooks): void {\n Object.assign(devHooks, hooks);\n}\n\n/**\n * Remove every installed hook. Exists for test isolation — a suite that asserts\n * the not-installed (production-shaped) path needs to get back to a clean slate\n * without reloading modules.\n */\nexport function clearDevHooks(): void {\n for (const key of Object.keys(devHooks)) {\n delete (devHooks as Record<string, unknown>)[key];\n }\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/utils/rowContract.ts
|
|
2
|
+
var ROW_HTML_SNIPPET_MAX = 120;
|
|
3
|
+
function truncateRowHtml(html) {
|
|
4
|
+
return html.length > ROW_HTML_SNIPPET_MAX ? html.slice(0, ROW_HTML_SNIPPET_MAX) + "\u2026" : html;
|
|
5
|
+
}
|
|
6
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
7
|
+
var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
|
|
8
|
+
var SVG_HTML_INTEGRATION = /* @__PURE__ */ new Set(["foreignObject", "desc", "title"]);
|
|
9
|
+
var MATHML_TEXT_INTEGRATION = /* @__PURE__ */ new Set(["mi", "mo", "mn", "ms", "mtext"]);
|
|
10
|
+
function foreignWrapper(parent) {
|
|
11
|
+
if (parent == null) return null;
|
|
12
|
+
const { namespaceURI, localName } = parent;
|
|
13
|
+
if (namespaceURI === SVG_NS) {
|
|
14
|
+
return SVG_HTML_INTEGRATION.has(localName) ? null : "svg";
|
|
15
|
+
}
|
|
16
|
+
if (namespaceURI === MATHML_NS) {
|
|
17
|
+
return MATHML_TEXT_INTEGRATION.has(localName) ? null : "math";
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function parseRowTemplate(html, parent) {
|
|
22
|
+
const tpl = document.createElement("template");
|
|
23
|
+
const wrapper = foreignWrapper(parent);
|
|
24
|
+
if (wrapper === null) {
|
|
25
|
+
tpl.innerHTML = html;
|
|
26
|
+
return { content: tpl.content, count: tpl.content.children.length };
|
|
27
|
+
}
|
|
28
|
+
tpl.innerHTML = `<${wrapper}>${html}</${wrapper}>`;
|
|
29
|
+
const host = tpl.content.firstElementChild;
|
|
30
|
+
const content = document.createDocumentFragment();
|
|
31
|
+
while (host.firstChild !== null) content.appendChild(host.firstChild);
|
|
32
|
+
return { content, count: content.children.length };
|
|
33
|
+
}
|
|
34
|
+
function parseSingleRow(html, index, parent) {
|
|
35
|
+
const { content, count } = parseRowTemplate(html, parent);
|
|
36
|
+
if (count !== 1) throw rowContractError(index, html, parent);
|
|
37
|
+
return content.firstElementChild;
|
|
38
|
+
}
|
|
39
|
+
function collectTemplateChildren(content, n) {
|
|
40
|
+
const nodes = new Array(n);
|
|
41
|
+
let child = content.firstElementChild;
|
|
42
|
+
for (let k = 0; k < n; k++) {
|
|
43
|
+
nodes[k] = child;
|
|
44
|
+
child = child.nextElementSibling;
|
|
45
|
+
}
|
|
46
|
+
return nodes;
|
|
47
|
+
}
|
|
48
|
+
function rowContractError(index, html, parent) {
|
|
49
|
+
const { count } = parseRowTemplate(html, parent);
|
|
50
|
+
const reason = count === 0 ? "produced no top-level element" : `produced ${count} top-level elements; exactly one is required`;
|
|
51
|
+
return new Error(
|
|
52
|
+
`each(): row render at index ${index} ${reason}. Each item's render must return exactly one element \u2014 wrap multiple roots in a single parent (e.g. <li>...</li>). Got HTML: ${JSON.stringify(truncateRowHtml(html))}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { collectTemplateChildren, parseRowTemplate, parseSingleRow, rowContractError, truncateRowHtml };
|
|
57
|
+
//# sourceMappingURL=chunk-YHH7OUFA.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-YHH7OUFA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/rowContract.ts"],"names":[],"mappings":"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","file":"chunk-YHH7OUFA.js","sourcesContent":["/**\n * Shared \"exactly one top-level element per row\" contract helpers (KF-103).\n *\n * The keyed-list reconciler in `list-reconcile.ts` and the first-render\n * binding path in `mount.ts` both enforce the same contract on `each()`\n * row HTML. Centralizing the constants and helpers here keeps the three\n * call sites that build error snippets and the eight call sites that\n * parse a row's HTML into a template element from drifting (KF-111 +\n * KF-115).\n */\n\n\n/**\n * Truncation limit applied to row HTML when including it in a contract-\n * violation error message. Keeps the error readable when a row produced\n * megabytes of HTML by accident.\n */\nexport const ROW_HTML_SNIPPET_MAX = 120;\n\n/**\n * Truncate a row's HTML for inclusion in an error message. Returns the\n * raw string if it's short enough; otherwise the first\n * `ROW_HTML_SNIPPET_MAX` characters with a trailing ellipsis.\n */\nexport function truncateRowHtml(html: string): string {\n return html.length > ROW_HTML_SNIPPET_MAX\n ? html.slice(0, ROW_HTML_SNIPPET_MAX) + '…'\n : html;\n}\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst MATHML_NS = 'http://www.w3.org/1998/Math/MathML';\n\n/**\n * HTML integration points: foreign-content elements under which the HTML parser\n * UNCONDITIONALLY re-enters HTML content, so an `each()` row parented there is\n * ordinary HTML and must NOT be wrapped. SVG has three (`foreignObject`, `desc`,\n * `title`); MathML has the five text integration points (`mi`, `mo`, `mn`, `ms`,\n * `mtext`). localNames are case-preserving for both namespaces, so `foreignObject`\n * matches as authored.\n *\n * `annotation-xml` is deliberately absent: it is a MathML HTML integration point\n * only CONDITIONALLY (with `encoding=\"text/html\"`/`application/xhtml+xml`),\n * otherwise its children are MathML. That conditional rule for a rare element is\n * out of scope — a MathML parent not in the text set always wraps in `<math>`.\n */\nconst SVG_HTML_INTEGRATION = new Set(['foreignObject', 'desc', 'title']);\nconst MATHML_TEXT_INTEGRATION = new Set(['mi', 'mo', 'mn', 'ms', 'mtext']);\n\n/**\n * The foreign-content wrapper tag a row destined for `parent` must be parsed\n * inside — `'svg'` (KF-389) or `'math'` (KF-417) — or `null` for the ordinary\n * HTML path.\n *\n * A bare `<template>` parses in the HTML namespace, so an `each()` row like\n * `<circle/>` or `<mn/>` comes back an `HTMLUnknownElement` and never paints as\n * SVG/MathML. First render escapes this because rows are inlined into the mount\n * root's `innerHTML`, where the surrounding `<svg>`/`<math>` puts the parser in\n * foreign-content mode; every LATER parse (granular insert, snapshot rebuild,\n * structural update) goes through the bare template and produces dead nodes.\n * `parseRowTemplate` re-enters foreign content by wrapping the row HTML in this\n * tag, so those parses namespace the rows the same way the first render did.\n *\n * A parent that is itself an HTML integration point (see the sets above) takes\n * the ordinary HTML path — wrapping HTML rows in `<svg>`/`<math>` there would\n * either crash (a breakout tag pops the wrapper, emptying it) or mis-namespace\n * the row (KF-420).\n */\nfunction foreignWrapper(parent: Element | null | undefined): 'svg' | 'math' | null {\n if (parent == null) return null;\n const { namespaceURI, localName } = parent;\n if (namespaceURI === SVG_NS) {\n return SVG_HTML_INTEGRATION.has(localName) ? null : 'svg';\n }\n if (namespaceURI === MATHML_NS) {\n return MATHML_TEXT_INTEGRATION.has(localName) ? null : 'math';\n }\n return null;\n}\n\n/**\n * Parse a row's HTML and report how many top-level elements it produced. The\n * reconciler uses the count to detect contract violations (count !== 1) on\n * both single-row and bulk-row paths.\n *\n * `parent` is the list's live parent when the caller knows it; it selects the\n * parse namespace (KF-389). Callers that only need the element COUNT for an\n * error message may omit it — the count is namespace-independent.\n *\n * Returns a `DocumentFragment` either way, so callers insert it identically.\n */\nexport function parseRowTemplate(\n html: string,\n parent?: Element | null,\n): { content: DocumentFragment; count: number } {\n const tpl = document.createElement('template');\n const wrapper = foreignWrapper(parent);\n if (wrapper === null) {\n tpl.innerHTML = html;\n return { content: tpl.content, count: tpl.content.children.length };\n }\n // Re-enter foreign content the same way the first render does: wrap in an\n // <svg>/<math> so the HTML parser namespaces the rows, then lift them out.\n // Using the parser (rather than DOMParser/XML) keeps row markup as forgiving\n // here as it is on first render — the two paths must accept the same input.\n tpl.innerHTML = `<${wrapper}>${html}</${wrapper}>`;\n const host = tpl.content.firstElementChild as Element;\n const content = document.createDocumentFragment();\n while (host.firstChild !== null) content.appendChild(host.firstChild);\n return { content, count: content.children.length };\n}\n\n/**\n * Parse one row's HTML to its single top-level element, enforcing the\n * \"exactly one top-level element per row\" contract with a row-precise error.\n * Shared by the granular reconciler's single-row paths and the snapshot\n * in-place morph path.\n */\nexport function parseSingleRow(html: string, index: number, parent?: Element | null): Element {\n const { content, count } = parseRowTemplate(html, parent);\n if (count !== 1) throw rowContractError(index, html, parent);\n return content.firstElementChild as Element;\n}\n\n/**\n * Capture the first `n` element children of a parsed row fragment into an\n * array BEFORE a fragment insert empties it. Callers have already verified the\n * parse count equals `n`, so the walk never sees a null.\n */\nexport function collectTemplateChildren(content: DocumentFragment, n: number): Element[] {\n const nodes = new Array<Element>(n);\n let child = content.firstElementChild;\n for (let k = 0; k < n; k++) {\n nodes[k] = child as Element;\n child = (child as Element).nextElementSibling;\n }\n return nodes;\n}\n\n/**\n * Build a precise contract-violation `Error` for the row at `index` whose\n * render produced the given `html`. The thrown message mentions the row\n * index, the actual element count, and the (truncated) HTML so the author\n * can locate and fix the offending row quickly.\n *\n * `parent` selects the parse namespace so the reported count matches what the\n * reconciler actually saw (KF-420): re-parsing without it could count a row\n * differently under a foreign-content parent and print a self-contradictory\n * \"produced 1 top-level elements; exactly one is required\".\n */\nexport function rowContractError(index: number, html: string, parent?: Element | null): Error {\n const { count } = parseRowTemplate(html, parent);\n const reason = count === 0\n ? 'produced no top-level element'\n : `produced ${count} top-level elements; exactly one is required`;\n return new Error(\n `each(): row render at index ${index} ${reason}. `\n + 'Each item\\'s render must return exactly one element — '\n + 'wrap multiple roots in a single parent (e.g. <li>...</li>). '\n + `Got HTML: ${JSON.stringify(truncateRowHtml(html))}`,\n );\n}\n"]}
|