kerfjs 4.4.1 → 5.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/LICENSE +1 -1
  3. package/README.md +3 -3
  4. package/ai/cursorrules +46 -1
  5. package/ai/manifest.json +62 -5
  6. package/ai/skill.md +53 -2
  7. package/dist/actions.d.ts +1 -1
  8. package/dist/actions.js +4 -4
  9. package/dist/actions.js.map +1 -1
  10. package/dist/array-signal.js +5 -5
  11. package/dist/async.js +17 -10
  12. package/dist/async.js.map +1 -1
  13. package/dist/attach.d.ts +11 -8
  14. package/dist/attach.js +52 -4
  15. package/dist/attach.js.map +1 -1
  16. package/dist/chunk-GY4XV2UV.js +1 -1
  17. package/dist/{chunk-VVDJLWMP.js → chunk-HW7KSM2Y.js} +2 -2
  18. package/dist/chunk-HW7KSM2Y.js.map +1 -0
  19. package/dist/{chunk-KEZTD6H4.js → chunk-KPXIOG2C.js} +3 -3
  20. package/dist/{chunk-KEZTD6H4.js.map → chunk-KPXIOG2C.js.map} +1 -1
  21. package/dist/{chunk-SAYPJ6XR.js → chunk-KZJXHFIB.js} +10 -6
  22. package/dist/chunk-KZJXHFIB.js.map +1 -0
  23. package/dist/{chunk-MRYM3O3V.js → chunk-LVH3GC6B.js} +11 -8
  24. package/dist/chunk-LVH3GC6B.js.map +1 -0
  25. package/dist/chunk-QIP723L4.js +1 -1
  26. package/dist/{chunk-SRWQKB33.js → chunk-SVATPF5R.js} +97 -74
  27. package/dist/chunk-SVATPF5R.js.map +1 -0
  28. package/dist/{chunk-3APBEVHF.js → chunk-U6FK33SG.js} +3 -3
  29. package/dist/{chunk-3APBEVHF.js.map → chunk-U6FK33SG.js.map} +1 -1
  30. package/dist/{chunk-U32TFTGZ.js → chunk-UZJ6I4T6.js} +3 -3
  31. package/dist/chunk-UZJ6I4T6.js.map +1 -0
  32. package/dist/{chunk-YHH7OUFA.js → chunk-V46JKE44.js} +3 -3
  33. package/dist/chunk-V46JKE44.js.map +1 -0
  34. package/dist/{chunk-SUPUPSBE.js → chunk-ZDCJZCNO.js} +9 -8
  35. package/dist/chunk-ZDCJZCNO.js.map +1 -0
  36. package/dist/dev.d.ts +9 -6
  37. package/dist/dev.js +5 -5
  38. package/dist/dev.js.map +1 -1
  39. package/dist/html.d.ts +1 -1
  40. package/dist/html.js +5 -5
  41. package/dist/html.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +11 -11
  44. package/dist/jsx-runtime.js +5 -5
  45. package/dist/list.d.ts +1 -1
  46. package/dist/list.js +273 -211
  47. package/dist/list.js.map +1 -1
  48. package/dist/overlay.d.ts +190 -280
  49. package/dist/overlay.js +401 -361
  50. package/dist/overlay.js.map +1 -1
  51. package/dist/remount.d.ts +5 -3
  52. package/dist/remount.js +29 -10
  53. package/dist/remount.js.map +1 -1
  54. package/dist/router.d.ts +1 -1
  55. package/dist/router.js +44 -22
  56. package/dist/router.js.map +1 -1
  57. package/dist/scope.d.ts +4 -3
  58. package/dist/scope.js +10 -10
  59. package/dist/scope.js.map +1 -1
  60. package/dist/testing.js +4 -4
  61. package/dist/timing.d.ts +3 -2
  62. package/dist/timing.js +5 -5
  63. package/dist/timing.js.map +1 -1
  64. package/llms.txt +7 -4
  65. package/package.json +12 -8
  66. package/dist/chunk-MRYM3O3V.js.map +0 -1
  67. package/dist/chunk-SAYPJ6XR.js.map +0 -1
  68. package/dist/chunk-SRWQKB33.js.map +0 -1
  69. package/dist/chunk-SUPUPSBE.js.map +0 -1
  70. package/dist/chunk-U32TFTGZ.js.map +0 -1
  71. package/dist/chunk-VVDJLWMP.js.map +0 -1
  72. package/dist/chunk-YHH7OUFA.js.map +0 -1
  73. /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attr.ts"],"names":[],"mappings":";AAiEA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,UAAU,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AACA,EAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA;AAC3B,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA;AAGvB,IAAA,IAAI,OAAO,CAAA,EAAQ;AACjB,MAAA,MAAA,IAAU,QAAA;AACV,MAAA;AAAA,IACF;AAEA,IAAA,IAAK,EAAA,IAAM,CAAA,IAAU,EAAA,IAAM,EAAA,IAAW,OAAO,GAAA,EAAQ;AACnD,MAAA,MAAA,IAAU,IAAA,GAAO,EAAA,CAAG,QAAA,CAAS,EAAE,CAAA,GAAI,GAAA;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAA,KAAM,CAAA,IAAK,EAAA,IAAM,EAAA,IAAU,MAAM,EAAA,EAAQ;AAC3C,MAAA,MAAA,IAAU,IAAA,GAAO,EAAA,CAAG,QAAA,CAAS,EAAE,CAAA,GAAI,GAAA;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,IACE,CAAA,KAAM,CAAA,IACN,EAAA,IAAM,EAAA,IAAU,EAAA,IAAM,MACtB,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA,KAAM,EAAA,EACtB;AACA,MAAA,MAAA,IAAU,IAAA,GAAO,EAAA,CAAG,QAAA,CAAS,EAAE,CAAA,GAAI,GAAA;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,IACE,EAAA,IAAM,OACN,EAAA,KAAO,EAAA;AAAA,IACP,EAAA,KAAO,EAAA;AAAA,IACN,EAAA,IAAM,MAAU,EAAA,IAAM,EAAA;AAAA,IACtB,EAAA,IAAM,MAAU,EAAA,IAAM,EAAA;AAAA,IACtB,EAAA,IAAM,EAAA,IAAU,EAAA,IAAM,GAAA,EACvB;AACA,MAAA,MAAA,IAAU,EAAA;AACV,MAAA;AAAA,IACF;AAEA,IAAA,MAAA,IAAU,IAAA,GAAO,EAAA;AAAA,EACnB;AACA,EAAA,OAAO,MAAA;AACT;AAMA,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,EAAA,GAAK,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC7B,IAAA,MAAM,EAAA,GAAK,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AACzB,IAAA,IAAI,OAAO,CAAA,EAAQ;AACjB,MAAA,MAAA,IAAU,QAAA;AAAA,IACZ,WAAY,EAAA,IAAM,CAAA,IAAU,EAAA,IAAM,EAAA,IAAW,OAAO,GAAA,EAAQ;AAE1D,MAAA,MAAA,IAAU,IAAA,GAAO,EAAA,CAAG,QAAA,CAAS,EAAE,CAAA,GAAI,GAAA;AAAA,IACrC,CAAA,MAAA,IAAW,OAAO,EAAA,EAAQ;AAExB,MAAA,MAAA,IAAU,MAAA;AAAA,IACZ,CAAA,MAAA,IAAW,OAAO,EAAA,EAAQ;AAExB,MAAA,MAAA,IAAU,KAAA;AAAA,IACZ,CAAA,MAAO;AACL,MAAA,MAAA,IAAU,EAAA;AAAA,IACZ;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAkBO,SAAS,IAAA,CACd,MACA,KAAA,EACqE;AACrE,EAAA,MAAM,WAAA,GAAc,eAAe,IAAI,CAAA;AACvC,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,MAAM,WAAW,CAAA,CAAA,EAAI,WAAW,CAAA,EAAA,EAAK,eAAA,CAAgB,KAAK,CAAC,CAAA,EAAA,CAAA;AAC3D,IAAA,OAAO,OAAO,MAAA,CAAO;AAAA,MACnB,IAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA,KAAA,EAAO,OAAO,MAAA,CAAO,EAAE,CAAC,IAAI,GAAG,OAAO;AAAA,KACvC,CAAA;AAAA,EACH;AACA,EAAA,OAAO,CAAC,MACN,MAAA,CAAO,MAAA,CAAO,EAAE,CAAC,IAAI,GAAG,CAAA,EAAG,CAAA;AAC/B","file":"chunk-UZJ6I4T6.js","sourcesContent":["/**\n * `attr(name, value)` — create a pre-computed attribute descriptor (static form).\n * `attr(name)` — create a per-render factory for dynamic attribute values (dynamic form).\n *\n * **Static form** — best for fixed action names, filter keys, role values, etc.\n * Escapes once at module-load time; produces a full {@link AttrSpec} with\n * `.name`, `.value`, `.selector`, and `.attrs`.\n *\n * const ACTIONS = {\n * toggle: attr('data-action', 'toggle'),\n * remove: attr('data-action', 'remove'),\n * } as const satisfies Record<string, AttrSpec<'data-action'>>;\n *\n * // In JSX — spread .attrs (rename-safe; no hardcoded attribute name):\n * <button {...ACTIONS.toggle.attrs}>Toggle</button>\n *\n * // In delegate — use the pre-computed selector:\n * delegate(root, 'click', ACTIONS.toggle.selector, handler);\n *\n * **Dynamic form** — best for per-row data like `data-id`, where the value\n * changes per item but the attribute name is constant.\n * The name is validated and pre-escaped at definition time; calling the\n * returned factory is cheap (it just freezes a one-key object — the value is\n * escaped later by the JSX attribute renderer when the result is spread).\n *\n * const ITEM = { id: attr('data-id') } as const;\n *\n * // In JSX — call the factory inline:\n * <li {...ITEM.id(String(item.id))}>…</li>\n *\n * For ad-hoc compound selectors, concatenate `.selector` strings:\n *\n * delegate(root, 'click',\n * ACTIONS.toggle.selector + attr('data-id', id).selector,\n * handler);\n *\n * Escaping:\n * - Attribute name: escaped as a CSS identifier via `cssEscapeIdent`, which is\n * an SSR-safe (no `CSS.escape`) adaptation of the Mathias Bynens polyfill\n * (https://github.com/mathiasbynens/CSS.escape, MIT licensed — see the\n * Acknowledgements section of LICENSE). Handles\n * control chars, leading digits, non-ASCII, and CSS metacharacters.\n * - Attribute value: embedded in double quotes as a CSS string. Backslashes and\n * double-quote characters are backslash-escaped; control characters are\n * hex-escaped per CSS Syntax Level 3 §3.4.\n *\n * Throws on an empty attribute name (not a valid CSS identifier).\n */\n\n/** Descriptor created by the static {@link attr} overload. */\nexport interface AttrSpec<N extends string = string, V extends string = string> {\n /** The raw attribute name passed to `attr()`. */\n readonly name: N;\n /** The raw attribute value passed to `attr()`. */\n readonly value: V;\n /** Pre-computed `[name=\"value\"]` CSS selector string, safe to pass to `delegate()`. */\n readonly selector: string;\n /** Spreadable JSX object — `{ [name]: value }` — keeps the attribute name out of JSX literals. */\n readonly attrs: { readonly [K in N]: V };\n}\n\n/**\n * Escape `value` as a CSS identifier (for attribute names, id fragments, etc.).\n * Adapted from the CSS.escape polyfill by Mathias Bynens (MIT).\n */\nfunction cssEscapeIdent(value: string): string {\n if (value === '') {\n throw new Error('attr: attribute name must not be empty');\n }\n const str = String(value);\n let result = '';\n for (let i = 0; i < str.length; i++) {\n const cp = str.charCodeAt(i);\n const ch = str.charAt(i);\n\n // U+0000 NULL → replacement character\n if (cp === 0x0000) {\n result += '�';\n continue;\n }\n // Control characters and DEL: hex-escape\n if ((cp >= 0x0001 && cp <= 0x001F) || cp === 0x007F) {\n result += '\\\\' + cp.toString(16) + ' ';\n continue;\n }\n // Leading digit: hex-escape to avoid \"-NN\" / \"3px\"-style ambiguity\n if (i === 0 && cp >= 0x0030 && cp <= 0x0039) {\n result += '\\\\' + cp.toString(16) + ' ';\n continue;\n }\n // Second char is a digit when first is '-' (e.g. \"-3foo\"): hex-escape digit\n if (\n i === 1 &&\n cp >= 0x0030 && cp <= 0x0039 &&\n str.charCodeAt(0) === 0x002D\n ) {\n result += '\\\\' + cp.toString(16) + ' ';\n continue;\n }\n // Non-ASCII, safe identifier chars (letters, digits, underscore, hyphen)\n if (\n cp >= 0x0080 ||\n cp === 0x002D || // `-`\n cp === 0x005F || // `_`\n (cp >= 0x0030 && cp <= 0x0039) || // 0-9\n (cp >= 0x0041 && cp <= 0x005A) || // A-Z\n (cp >= 0x0061 && cp <= 0x007A) // a-z\n ) {\n result += ch;\n continue;\n }\n // Everything else: backslash-escape\n result += '\\\\' + ch;\n }\n return result;\n}\n\n/**\n * Escape `value` as a CSS double-quoted string (for attribute values in\n * `[attr=\"value\"]` selectors).\n */\nfunction escapeCSSString(value: string): string {\n let result = '';\n for (let i = 0; i < value.length; i++) {\n const cp = value.charCodeAt(i);\n const ch = value.charAt(i);\n if (cp === 0x0000) {\n result += '�';\n } else if ((cp >= 0x0001 && cp <= 0x001F) || cp === 0x007F) {\n // Control chars: hex-escape\n result += '\\\\' + cp.toString(16) + ' ';\n } else if (cp === 0x005C) {\n // Backslash\n result += '\\\\\\\\';\n } else if (cp === 0x0022) {\n // Double quote (the string delimiter we use)\n result += '\\\\\"';\n } else {\n result += ch;\n }\n }\n return result;\n}\n\n/**\n * Static overload — pre-computes the full descriptor at definition time.\n * Returns an {@link AttrSpec} with `.name`, `.value`, `.selector`, and `.attrs`.\n */\nexport function attr<N extends string, V extends string>(name: N, value: V): AttrSpec<N, V>;\n\n/**\n * Dynamic overload — pre-validates and pre-escapes the attribute name, returns a\n * factory that accepts a per-render value and produces a frozen spreadable object.\n * Use for per-row attributes like `data-id` where the value changes per item.\n * The optional `V` generic constrains which values the factory accepts:\n * `attr<'data-id', 'a'|'b'>('data-id')` → `(value: 'a'|'b') => { 'data-id': 'a'|'b' }`.\n * Leaving both generics off infers `N` from the argument and defaults `V` to `string`.\n */\nexport function attr<N extends string, V extends string = string>(name: N): (value: V) => { readonly [K in N]: V };\n\nexport function attr<N extends string, V extends string>(\n name: N,\n value?: V,\n): AttrSpec<N, V> | ((value: string) => { readonly [K in N]: string }) {\n const escapedName = cssEscapeIdent(name); // validates + pre-escapes name in both paths\n if (value !== undefined) {\n const selector = `[${escapedName}=\"${escapeCSSString(value)}\"]`;\n return Object.freeze({\n name,\n value,\n selector,\n attrs: Object.freeze({ [name]: value }) as { readonly [K in N]: V },\n }) as AttrSpec<N, V>;\n }\n return (v: string): { readonly [K in N]: string } =>\n Object.freeze({ [name]: v }) as { readonly [K in N]: string };\n}\n"]}
@@ -1,4 +1,4 @@
1
- // src/utils/rowContract.ts
1
+ // src/utils/row-contract.ts
2
2
  var ROW_HTML_SNIPPET_MAX = 120;
3
3
  function truncateRowHtml(html) {
4
4
  return html.length > ROW_HTML_SNIPPET_MAX ? html.slice(0, ROW_HTML_SNIPPET_MAX) + "\u2026" : html;
@@ -54,5 +54,5 @@ function rowContractError(index, html, parent) {
54
54
  }
55
55
 
56
56
  export { collectTemplateChildren, parseRowTemplate, parseSingleRow, rowContractError, truncateRowHtml };
57
- //# sourceMappingURL=chunk-YHH7OUFA.js.map
58
- //# sourceMappingURL=chunk-YHH7OUFA.js.map
57
+
58
+ //# sourceMappingURL=chunk-V46JKE44.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/row-contract.ts"],"names":[],"mappings":"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","file":"chunk-V46JKE44.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"]}
@@ -1,6 +1,6 @@
1
- import { effect, isSignal } from './chunk-3APBEVHF.js';
1
+ import { effect, isSignal } from './chunk-U6FK33SG.js';
2
2
  import { flatten, wrapWithTags, mergeChildSegments } from './chunk-GY4XV2UV.js';
3
- import { devHooks } from './chunk-VVDJLWMP.js';
3
+ import { devHooks } from './chunk-HW7KSM2Y.js';
4
4
 
5
5
  // src/utils/syncFormProp.ts
6
6
  function syncFormProp(el, name, value, present) {
@@ -16,7 +16,7 @@ function syncFormProp(el, name, value, present) {
16
16
  }
17
17
  }
18
18
 
19
- // src/utils/urlScreen.ts
19
+ // src/utils/url-screen.ts
20
20
  var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "xlink:href", "formaction", "action", "data"]);
21
21
  var DANGEROUS_SCHEMES = /* @__PURE__ */ new Set(["javascript", "vbscript"]);
22
22
  var INERT_JAVASCRIPT_URLS = /* @__PURE__ */ new Set([
@@ -28,6 +28,7 @@ var INERT_JAVASCRIPT_URLS = /* @__PURE__ */ new Set([
28
28
  "javascript:void 0;"
29
29
  ]);
30
30
  var CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
31
+ var DANGEROUS_URL_DIAGNOSTIC_VALUE_LENGTH = 80;
31
32
  function normalizeUrl(value) {
32
33
  return value.replace(CONTROL_CHARS, "").replace(/^\s+/, "");
33
34
  }
@@ -45,7 +46,7 @@ function isDangerousDataUrl(normalized) {
45
46
  return true;
46
47
  }
47
48
  function isDangerousUrlValue(name, value) {
48
- if (!URL_ATTRS.has(name)) return false;
49
+ if (!URL_ATTRS.has(name.toLowerCase())) return false;
49
50
  const normalized = normalizeUrl(value);
50
51
  const scheme = schemeOf(normalized);
51
52
  if (scheme === null) return false;
@@ -55,7 +56,7 @@ function isDangerousUrlValue(name, value) {
55
56
  return scheme === "data" && isDangerousDataUrl(normalized);
56
57
  }
57
58
  function dangerousUrlWarning(name, value) {
58
- return `dropped dangerous URL value for ${name}=${JSON.stringify(value.slice(0, 80))}. kerf blocks javascript:, vbscript:, and script-executing data: URLs (text/html, image/svg+xml, xml) in href/src/data/formaction/action/xlink:href by default. Wrap in raw() if this is intentional (e.g. bookmarklets), or sanitize upstream.`;
59
+ return `dropped dangerous URL value for ${name}=${JSON.stringify(value.slice(0, DANGEROUS_URL_DIAGNOSTIC_VALUE_LENGTH))}. kerf blocks javascript:, vbscript:, and script-executing data: URLs (text/html, image/svg+xml, xml) in href/src/data/formaction/action/xlink:href by default. Wrap in raw() if this is intentional (e.g. bookmarklets), or sanitize upstream.`;
59
60
  }
60
61
  function reportDangerousUrl(context2, name, value) {
61
62
  const message = `${context2}: ${dangerousUrlWarning(name, value)}`;
@@ -543,6 +544,6 @@ function _renderAttrVerbatim(name, value) {
543
544
  return renderAttrNamed(name, name, value);
544
545
  }
545
546
 
546
- 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 };
547
- //# sourceMappingURL=chunk-SUPUPSBE.js.map
548
- //# sourceMappingURL=chunk-SUPUPSBE.js.map
547
+ export { Fragment, ROW_TEXT_PREFIX, SafeHtml, TEXT_MARKER_PREFIX, _renderAttrVerbatim, _setBindingContext, _toSegment, assertEmittableAttrName, bindAttr, bindMarkerAttr, boundTextNodeOf, captureRowBindings, carryOrRewireRowBindings, disposeRowBindings, escapeHtml, granularListSafeHtml, isSafeHtml, jsx, listSafeHtml, newBindingContext, raw, syncFormProp, wireBindings, wireRowBindings };
548
+
549
+ //# sourceMappingURL=chunk-ZDCJZCNO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/syncFormProp.ts","../src/utils/url-screen.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-ZDCJZCNO.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 * - **The `javascript:` no-op placeholders are allowed.** `javascript:void(0)`\n * and its handful of spellings are the placeholder-link idiom, not an\n * attack; matching is against the whole normalized value so nothing can be\n * appended to them. See `INERT_JAVASCRIPT_URLS`.\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/**\n * `javascript:` URLs that are provably no-ops — the placeholder-link idiom\n * (`<a href=\"javascript:void(0)\">`) that a decade of jQuery-era markup, CMS\n * output, and ported templates is full of.\n *\n * Screening them out was a false positive with a bad failure mode: the `href`\n * is dropped, so the anchor stops being a link — no keyboard focus, no `:link`\n * styling, no pointer cursor — and in production that happens behind a\n * `console.warn` nobody reads.\n *\n * Allowing them costs nothing. The match is against the **whole** normalized\n * value, so no attacker-supplied suffix survives it: `javascript:void(0)` is in\n * the set and `javascript:void(0);alert(1)` is not. The bodies here evaluate to\n * `undefined` or to nothing at all, and they carry no interpolation point.\n *\n * The alternative — telling authors to write `raw('javascript:void(0)')` — is\n * strictly worse for security: it teaches the general-purpose escape hatch as\n * the answer to a benign case, and an author who has learned `raw()` for one\n * `href` will reach for it on the next one.\n *\n * Matching is exact after `normalizeUrl` + lowercase + trailing-whitespace trim.\n * Internal whitespace is NOT collapsed, so each accepted spelling is listed\n * explicitly and the set stays auditable at a glance.\n */\nconst INERT_JAVASCRIPT_URLS = new Set([\n 'javascript:',\n 'javascript:;',\n 'javascript:void(0)',\n 'javascript:void(0);',\n 'javascript:void 0',\n 'javascript:void 0;',\n]);\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/** Bound diagnostic payloads so an unsafe, attacker-controlled URL cannot flood logs. */\nconst DANGEROUS_URL_DIAGNOSTIC_VALUE_LENGTH = 80;\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/**\n * The lowercased URL scheme (text before the first `:`) of an ALREADY-NORMALIZED\n * value, or null if none.\n */\nfunction schemeOf(normalized: string): string | null {\n const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(normalized);\n return m ? m[1].toLowerCase() : null;\n}\n\n/**\n * True if a `data:` URL (already normalized) is a script-executing document\n * type. Allowlists the inert media families and fails closed on everything else\n * — so `text/html`, `image/svg+xml`, XHTML/XML, and any unknown/novel subtype\n * are all treated as dangerous, while `data:image/png` / fonts / audio / video\n * / plain text pass.\n */\nfunction isDangerousDataUrl(normalized: string): boolean {\n const media = /^data:([^;,]*)/.exec(normalized.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 // HTML attribute names are ASCII-case-insensitive, and JSX can reach this\n // helper with the author-written casing on both static and bound paths.\n // Normalize before the allowlist check so `HREF` / `Src` / `XLINK:HREF`\n // cannot bypass the same browser sink written in lowercase.\n if (!URL_ATTRS.has(name.toLowerCase())) return false;\n // Normalized once here — every helper below reasons about the same string the\n // browser's scheme resolution would see.\n const normalized = normalizeUrl(value);\n const scheme = schemeOf(normalized);\n if (scheme === null) return false;\n if (DANGEROUS_SCHEMES.has(scheme)) {\n return !INERT_JAVASCRIPT_URLS.has(normalized.trimEnd().toLowerCase());\n }\n return scheme === 'data' && isDangerousDataUrl(normalized);\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, DANGEROUS_URL_DIAGNOSTIC_VALUE_LENGTH))}. `\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/url-screen.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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\nexport function escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\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 * **When a camelCase attribute needs an entry.** Pass-through is not the same\n * as \"no translation\": an unlisted `fooBar` is emitted verbatim, and the HTML\n * parser then lowercases it to `foobar`. That is silently *correct* whenever\n * the real attribute name is exactly the lowercased spelling (`fetchPriority`\n * → `fetchpriority`, `charSet` → `charset`, `playsInline` → `playsinline`), and\n * silently *wrong* whenever it isn't — `defaultSelected` rendered\n * `defaultselected`, an attribute no browser has ever read, so the option it\n * was supposed to pre-select never was. Any camelCase key whose HTML name is\n * not its own lowercase MUST have a row here.\n *\n * `tests/unit/jsx-attr-names.test.ts` pins that rule: it reads every attribute\n * key declared in `src/jsx-types.ts` and asserts the name kerf would actually\n * emit for each camelCase one, so a new typed attribute cannot join without\n * someone stating what it renders as.\n *\n * SVG is the reason the lowercasing shortcut can't be relied on in general —\n * SVG attribute names are case-sensitive, and only the fixed set in the HTML\n * parser's SVG adjustment table (`viewBox`, `preserveAspectRatio`,\n * `pathLength`, …) survives the round trip with its case intact.\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 defaultSelected: 'selected',\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/url-screen.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/**\n * Inject a pre-escaped HTML string verbatim, bypassing kerf's auto-escaping.\n *\n * **Reach for this rarely.** kerf escapes automatically everywhere else, so a lot\n * of `raw()` in a codebase is usually a sign the wrong tool is being used — the\n * common cases have a safer, first-class answer:\n * - Interpolating dynamic text/attributes? Plain JSX (`<p>{value}</p>`,\n * `class={sig}`) already escapes it; you don't need `raw()`.\n * - Composing markup? Build a {@link SafeHtml} the normal way — a JSX expression,\n * the `html` tagged template (`kerfjs/html`), `each()`, or a component function\n * that returns JSX. Those all produce trusted `SafeHtml` without hand-writing\n * an HTML string.\n * - A genuinely trusted, pre-escaped **dynamic** value (server output, config, a\n * hard-coded string)? That's the one legitimate use. The\n * `kerfjs/no-raw-with-dynamic-arg` lint rule flags a `raw()` whose argument is\n * not a literal — because unsanitized user input is the canonical XSS mistake —\n * so acknowledge it with a `// eslint-disable-next-line\n * kerfjs/no-raw-with-dynamic-arg` at that call. That explicit override is the\n * single sanctioned way to say \"I've reviewed this; it's trusted,\" and it\n * leaves a searchable audit trail.\n *\n * `raw()` is NOT a sanitizer — it does no escaping. For user-controlled input,\n * sanitize first (`raw(DOMPurify.sanitize(marked(userMarkdown)))`) or, better,\n * render it through escaping JSX instead.\n */\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"]}
package/dist/dev.d.ts CHANGED
@@ -115,9 +115,11 @@ interface ListBinding {
115
115
  * those before paying the cost, exactly as it checked the old `isOptedIn()`
116
116
  * exports.
117
117
  *
118
- * Each warner keeps its own internal opt-in check (the `KERF_DEV_WARN_*` env
119
- * reads). Installation decides whether the diagnostics are *present*; the
120
- * individual warner still decides whether it is *switched on*.
118
+ * Each opt-in warner keeps its own internal switch check through `devFlag()`;
119
+ * an `enableWarnings()` override wins over the matching `KERF_DEV_WARN_*`
120
+ * environment variable. Installation decides whether the diagnostics are
121
+ * *present*; the opt-in warner decides whether it is *switched on*. Always-on
122
+ * hooks skip that second gate.
121
123
  *
122
124
  * @see docs/11-dev-warnings.md
123
125
  */
@@ -295,9 +297,10 @@ interface DevWarningOptions {
295
297
  * `Signal.prototype`'s `value` accessor is non-configurable, and reaching live
296
298
  * instances would require a per-signal registry that production would pay for.
297
299
  *
298
- * Installation decides whether the diagnostics are PRESENT; each individual
299
- * warner still reads its own `KERF_DEV_WARN_*` env var to decide whether it is
300
- * switched ON. That keeps the existing opt-in contract intact.
300
+ * Installation decides whether the diagnostics are PRESENT. Each opt-in
301
+ * warner then reads its own switch through `devFlag()`: an `enableWarnings()`
302
+ * override wins over the matching `KERF_DEV_WARN_*` environment variable.
303
+ * The always-on hooks skip that second gate.
301
304
  *
302
305
  * @see docs/11-dev-warnings.md
303
306
  */
package/dist/dev.js CHANGED
@@ -1,7 +1,7 @@
1
- import { truncateRowHtml } from './chunk-YHH7OUFA.js';
1
+ import { truncateRowHtml } from './chunk-V46JKE44.js';
2
2
  import { LIST_MARKER_PREFIX } from './chunk-GY4XV2UV.js';
3
- import { installDevHooks } from './chunk-VVDJLWMP.js';
4
- export { clearDevHooks, devHooks, installDevHooks } from './chunk-VVDJLWMP.js';
3
+ import { installDevHooks } from './chunk-HW7KSM2Y.js';
4
+ export { clearDevHooks, devHooks, installDevHooks } from './chunk-HW7KSM2Y.js';
5
5
  import { signal, Signal } from '@preact/signals-core';
6
6
 
7
7
  // src/dev-warn-config.ts
@@ -491,7 +491,7 @@ function maybeWarnNarrowSet(prev, next, ctx) {
491
491
  console.warn(`${WARNING_PREFIX}${keysList}${WARNING_SUFFIX}`);
492
492
  }
493
493
 
494
- // src/utils/devReadonly.ts
494
+ // src/utils/dev-readonly.ts
495
495
  var RULE_MESSAGE = "kerf: store state is read-only \u2014 all writes must go through actions (build a new state object and pass it to `set()`). Mutating the object returned by `get()` is a Rule 8 violation and never notifies subscribers.";
496
496
  var proxyToRaw = /* @__PURE__ */ new WeakMap();
497
497
  var rawToProxy = /* @__PURE__ */ new WeakMap();
@@ -603,5 +603,5 @@ installDevHooks(DEV_HOOKS);
603
603
  if (isDevWarnUntrackedEnabled()) noteUntrackedCoverage();
604
604
 
605
605
  export { DEV_HOOKS, enableWarnings };
606
- //# sourceMappingURL=dev.js.map
606
+
607
607
  //# sourceMappingURL=dev.js.map