@zvndev/darkmown 2.12.1 → 2.12.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +14 -1
- package/README.md +10 -1
- package/SECURITY.md +9 -2
- package/docs/cli.md +10 -0
- package/docs/spec-alignment.md +1 -1
- package/package.json +2 -2
- package/site/pages/docs/index.wd +1 -1
- package/site/pages/playground/index.js +52 -58
- package/src/cli.js +23 -1
- package/src/compiler/format.js +3 -2
- package/src/compiler/frontmatter.js +2 -1
- package/src/compiler/markdown.js +61 -7
- package/src/compiler/page.js +11 -14
- package/src/dev.js +63 -0
- package/src/feeds.js +24 -9
- package/src/headers.js +8 -2
- package/src/image-variant-resize.js +6 -1
- package/src/router.js +4 -1
- package/src/runtime.js +16 -6
- package/src/runtime.min.js +1 -1
- package/src/runtime.min.js.map +1 -1
- package/src/scaffold.js +13 -5
- package/src/statics.js +16 -1
- package/types/dev.d.ts +28 -0
- package/types/feeds.d.ts +19 -26
- package/types/headers.d.ts +6 -1
package/src/runtime.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.js"],"sourcesContent":["/**\n * Darkmown's tiny reactive runtime. Browser-only: it reads `data-wd-*` attributes\n * emitted by the compiler, hydrates declared state, and re-renders on changes.\n * @module runtime\n */\n\n/**\n * A reconciled loop-row element. Carries its source row on `__wdItem` so per-row\n * button actions can resolve which row was clicked.\n * @typedef {Element & { __wdItem?: any }} WdRow\n */\n\n/** @type {Record<string, any>} */\nconst state = {};\n/** @type {Set<string>} */\nconst persistKeys = new Set();\n/** @type {Set<string>} Non-ephemeral :store names, persisted under wd:store:<name>. */\nconst storeKeys = new Set();\n\n/** @type {Set<string>} Keys a `:state … from-url` mirrors into the query string. */\nconst urlKeys = new Set();\n\n// Seed snapshot, recorded per key BEFORE its localStorage/URL override, so\n// `reset` deep-clones the DECLARED value — not the last persisted one.\n/** @type {Record<string, any>} */\nconst initials = {};\n\n/**\n * Best-effort localStorage. Touching it THROWS outright in a sandboxed iframe or\n * a browser set to block site data, and setItem throws on quota — persistence\n * must never abort the boot or stop a render, so every access is swallowed here.\n * `ls(k)` reads, `ls(k, v)` writes, `ls(k, null)` drops a corrupt entry.\n * @param {string} k\n * @param {string | null} [v]\n * @returns {any}\n */\nfunction ls(k, v) {\n try {\n return v === undefined ? localStorage.getItem(k) : v === null ? localStorage.removeItem(k) : localStorage.setItem(k, v);\n } catch { return null; }\n}\n\n/** The query-string parameter a state key mirrors to (`cart:items` → `cart.items`). */\nconst urlParam = (/** @type {string} */ key) => key.replace(\":\", \".\");\n\n/**\n * Claim every seed `<script>` in the LIVE tree: apply the declared value for any\n * key state does not already hold, record it as that key's reset seed, then let\n * localStorage and the query string override it in that order.\n *\n * Boot runs this over the document; `scan()` runs it again whenever a render\n * changes structure. That second call is the point: `querySelectorAll` cannot\n * see into `<template>` content, so a `:state`/`:store`/`:theme` declared inside\n * an unopened `:if` branch was never hydrated at all — it started undefined and\n * never persisted. Re-opening a branch injects FRESH script nodes (the branch is\n * cloned from a template each time), so the guard here is \"state does not have\n * this key yet\", not a per-node claim: re-opening a branch must not reset what\n * the reader already changed.\n * @returns {void}\n */\nfunction hydrate() {\n for (const script of document.querySelectorAll(\"script[data-wd-state],script[data-wd-store]\")) {\n const store = script.getAttribute(\"data-wd-store\");\n const seed = JSON.parse(script.textContent || \"null\");\n for (const [key, value] of store ? [[store, seed]] : Object.entries(seed || {})) {\n if (key in state) continue;\n // The seed is the reset baseline, so state gets a COPY: mutating a list\n // through an action must not rewrite what `reset` restores.\n state[key] = structuredClone((initials[key] = value));\n const durable = store ? !script.hasAttribute(\"data-wd-store-ephemeral\") : script.getAttribute(\"data-wd-persist\") === key;\n if (durable) {\n (store ? storeKeys : persistKeys).add(key);\n const lsKey = (store ? \"wd:store:\" : \"wd:\") + key;\n const stored = ls(lsKey);\n // A corrupt entry self-heals: drop it and keep the declared seed, rather\n // than re-reading the same garbage on every load. A :store with nothing\n // stored yet writes its seed so other tabs see it.\n if (stored === null) { if (store) ls(lsKey, JSON.stringify(value ?? null)); }\n else if (jsonOr(stored) === undefined) ls(lsKey, null);\n else state[key] = jsonOr(stored);\n }\n // `from-url` outranks both: a shared link must show what it says.\n if (script.getAttribute(\"data-wd-url\") === key) {\n urlKeys.add(key);\n readUrl(false, [key]);\n }\n }\n }\n}\n\n/**\n * Mirror every `from-url` key into the address bar. A value equal to its\n * declared seed DROPS its parameter, so the default page keeps a clean URL, and\n * the write is `replaceState`, so filtering never fills the back button.\n * @returns {void}\n */\nfunction syncUrl() {\n if (!urlKeys.size) return;\n const params = new URLSearchParams(location.search);\n for (const key of urlKeys) {\n const value = state[key];\n if (JSON.stringify(value ?? null) === JSON.stringify(initials[key] ?? null)) params.delete(urlParam(key));\n else params.set(urlParam(key), typeof value === \"string\" ? value : JSON.stringify(value));\n }\n const query = params.toString();\n const next = location.pathname + (query ? `?${query}` : \"\") + location.hash;\n if (next === location.pathname + location.search + location.hash) return;\n // about:srcdoc (the playground's preview iframe) answers replaceState with a\n // SecurityError. This call sits between the state write and the render in\n // every handler, so an unguarded throw froze the preview on the first\n // keystroke. Like `ls`, URL sync is best-effort and never blocks a paint.\n try { history.replaceState(history.state, \"\", next); } catch { /* history is not writable here */ }\n}\n\n/**\n * Re-read every `from-url` key from the address bar. `restore` is true for a\n * popstate, where a parameter that is GONE means \"back to the declared value\";\n * it is false while hydrating, where an absent parameter must leave the\n * persisted value the storage read just restored.\n * `only` narrows the read to the keys just claimed by `hydrate`: re-reading the\n * WHOLE set there would revert a write the page has already made but not yet\n * mirrored, which is exactly what happens when an `:effect` writes a `from-url`\n * key and the same action opens a branch declaring another one.\n * @param {boolean} [restore]\n * @param {string[]} [only]\n * @returns {void}\n */\nfunction readUrl(restore, only) {\n const params = new URLSearchParams(location.search);\n for (const key of only || urlKeys) {\n const raw = params.get(urlParam(key));\n const seed = initials[key];\n if (raw == null) { if (restore) state[key] = structuredClone(seed); }\n else state[key] = typeof seed === \"string\" ? raw : jsonOr(raw) ?? raw;\n }\n}\n\nfunction savePersisted() {\n for (const key of persistKeys) ls(`wd:${key}`, JSON.stringify(state[key] ?? null));\n for (const name of storeKeys) ls(`wd:store:${name}`, JSON.stringify(state[name] ?? null));\n syncUrl();\n}\n\nhydrate();\n\n// Back/forward across `replaceState` history entries, and any other navigation\n// that changes the query string without a reload.\nwindow.addEventListener(\"popstate\", () => {\n if (!urlKeys.size) return;\n readUrl(true);\n // Storage follows the URL. Boot reads URL > storage > seed, so leaving a\n // persisted value behind after a back to a parameter-less URL would show the\n // seed now and flip back to the stored value on the next reload.\n savePersisted();\n render();\n});\n\nwindow.addEventListener(\"storage\", (event) => {\n const name = event.key && event.key.startsWith(\"wd:store:\") ? event.key.slice(9) : \"\";\n if (!storeKeys.has(name) || event.newValue == null) return;\n let next;\n try { next = JSON.parse(event.newValue); } catch { return; }\n if (JSON.stringify(next) === JSON.stringify(state[name])) return;\n state[name] = next;\n render();\n});\n\n/**\n * True for prototype-pollution path segments that must never be read or written.\n * Single source of truth for the rejected-key list (used by getPath + setPath).\n * @param {string} segment\n * @returns {boolean}\n */\nconst unsafeKey = (segment) => segment === \"__proto__\" || segment === \"constructor\" || segment === \"prototype\";\n\n/**\n * Safely read a dotted path off a value, rejecting prototype-pollution segments.\n * @param {any} value\n * @param {string | null} path\n * @returns {any}\n */\nfunction getPath(value, path) {\n if (!path) return value;\n let current = value;\n for (const segment of path.split(\".\")) {\n if (current == null) return undefined;\n if (unsafeKey(segment)) return undefined;\n current = current[segment];\n }\n return current;\n}\n\n/**\n * Safely write a value at a dotted path, creating plain objects for missing\n * intermediates and rejecting prototype-pollution segments at every level.\n * @param {any} obj\n * @param {string} path\n * @param {any} value\n * @returns {void}\n */\nfunction setPath(obj, path, value) {\n const segs = path.split(\".\");\n const last = segs.pop() || \"\";\n let cur = obj;\n for (const seg of segs) {\n if (unsafeKey(seg)) return;\n if (cur[seg] == null || typeof cur[seg] !== \"object\") cur[seg] = {};\n cur = cur[seg];\n }\n if (unsafeKey(last)) return;\n cur[last] = value;\n}\n\n/**\n * Empty for fetch purposes: null/undefined, an empty array, or an object with\n * no own keys. Drives the `name_empty` lifecycle flag.\n * @param {any} v\n * @returns {boolean}\n */\nfunction isEmpty(v) {\n return v == null || (typeof v === \"object\" && (Array.isArray(v) ? v.length : Object.keys(v).length) === 0);\n}\n\n/**\n * Parse JSON, or undefined when the text is not JSON. `undefined` is a safe\n * sentinel: no JSON document parses to it, so a body of literal `null` is still\n * distinguishable from \"not JSON at all\".\n * @param {string} text\n * @returns {any}\n */\nfunction jsonOr(text) {\n try { return JSON.parse(text); } catch { return undefined; }\n}\n\n// The characters a value must not carry into a URL, mirroring `DEST_UNSAFE`\n// in src/compiler/markdown.js: an unencoded space or paren makes the paint and\n// the re-render disagree, and `tests/attr-binding.test.js` pins the two together.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — a control character in a URL has to be encoded, not passed through.\nconst URL_UNSAFE = /[\\u0000-\\u0020()<>\"'`\\\\\\u007F]/g;\n\n/**\n * Every node matching `sel` at or under `node` — the self-match matters because\n * a loop row's own element can carry the marker, not just its descendants.\n * @param {Element} node\n * @param {string} sel\n * @returns {Element[]}\n */\nconst selfAnd = (node, sel) => (node.matches(sel) ? [node, ...node.querySelectorAll(sel)] : [...node.querySelectorAll(sel)]);\n\n/**\n * Repaint one bound attribute (a markdown link/image destination that read\n * `:state`, `:computed`, or a loop row). The marker is `[attr, …parts]`, where a\n * part is either a literal string or a reader: `[\"s\", key, path]` for state,\n * `[\"m\", name]` for a row meta variable, `[\"i\", path]` for the row itself.\n *\n * The scheme guard is the load-bearing half. The compiler vets URLs the AUTHOR\n * wrote; the value arriving here came from state, a form field, or a fetched\n * payload, and nothing upstream has seen it. Control characters are stripped\n * before the test because a browser strips them out of a URL before resolving\n * it, so `java<TAB>script:` is a live javascript: URL. An unsafe value is not\n * applied — the attribute is emptied instead. Mirrored by `unsafeUrlValue` in\n * src/compiler/interpolation.js for the build-time paint.\n * @param {Element} el\n * @param {string | null} raw\n * @param {any} item Loop row, or undefined outside one.\n * @param {Record<string, any>} meta Per-row meta values.\n * @returns {void}\n */\nfunction applyAttr(el, raw, item, meta) {\n if (!raw) return;\n let parts;\n try { parts = JSON.parse(raw); } catch { return; }\n let out = \"\"; // encoded: what lands on the attribute\n let plain = \"\"; // raw: what the scheme guard tests\n for (let i = 1; i < parts.length; i++) {\n const p = parts[i];\n // A literal chunk is the author's own URL text, already vetted by the\n // compiler; only a READER value is encoded, and with exactly the character\n // set `encodeDest` uses in src/compiler/markdown.js, or the build-time paint\n // and the first re-render would disagree on every value holding a space.\n if (typeof p === \"string\") { out += p; plain += p; continue; }\n const v = String((p[0] === \"s\" ? getPath(state[p[1]], p[2]) : p[0] === \"m\" ? meta[p[1]] : getPath(item, p[1])) ?? \"\");\n plain += v;\n out += v.replace(URL_UNSAFE, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`);\n }\n // Defense in depth: the compiler only ever emits href (a link) or src (an\n // image), so anything else in a marker did not come from a compile.\n if (parts[0] !== \"href\" && parts[0] !== \"src\") return;\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — a browser strips these out of a URL, so the guard must too.\n el.setAttribute(parts[0], /^(javascript|data|vbscript):/i.test(plain.replace(/[\\u0000-\\u0020\\u007F]+/g, \"\")) ? \"\" : out);\n}\n\n// --- Format pipes -----------------------------------------------------------\n// A compact mirror of src/compiler/format.js: the same formatter math, so a\n// reactive `{ value | money }` re-formats in the browser exactly as the static\n// fold did at build time. tests/format-parity.test.js guards the two against\n// drift. Pure functions of (value, args) — no clock, no DOM — Intl does the work.\n\n/** @param {any} v @returns {number} */\nconst fmtNum = (v) => { const n = typeof v === \"number\" ? v : Number.parseFloat(v); return Number.isFinite(n) ? n : 0; };\n/** @param {any} v @returns {any[]} */\nconst fmtList = (v) => (Array.isArray(v) ? v : v == null ? [] : [v]);\n/** @param {any} row @param {string} [f] */\nconst fmtPick = (row, f) => (f != null && f !== \"\" ? (row == null ? undefined : row[f]) : row);\n/** @param {any[]} list @param {string} [f] */\nconst fmtNums = (list, f) => list.map((r) => fmtNum(fmtPick(r, f)));\n/** @param {any} v @returns {Date} */\nconst fmtToDate = (v) => {\n if (v instanceof Date) return v;\n if (typeof v === \"number\") return new Date(v);\n const s = String(v).trim();\n return /^-?\\d+$/.test(s) ? new Date(Number(s)) : new Date(s);\n};\n/** @param {Date} d @param {any} opts @param {any} fb */\nconst fmtDate = (d, opts, fb) => (Number.isNaN(d.getTime()) ? String(fb) : new Intl.DateTimeFormat(undefined, opts).format(d));\n/** Date-only strings (no time) format in UTC so the calendar date is stable. @param {any} v */\nconst fmtTZ = (v) => (typeof v === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(v.trim()) ? { timeZone: \"UTC\" } : undefined);\n\n/** @type {Record<string, (value: any, args: any[]) => any>} */\nconst FMT = {\n money: (v, [c = \"USD\", l]) => new Intl.NumberFormat(l || undefined, { style: \"currency\", currency: String(c || \"USD\") }).format(fmtNum(v)),\n number: (v, [d]) => new Intl.NumberFormat(undefined, d == null ? {} : { minimumFractionDigits: +d, maximumFractionDigits: +d }).format(fmtNum(v)),\n percent: (v, [d = 0]) => new Intl.NumberFormat(undefined, { style: \"percent\", minimumFractionDigits: +d, maximumFractionDigits: +d }).format(fmtNum(v)),\n round: (v, [d = 0]) => { const p = 10 ** +d; return Math.round(fmtNum(v) * p) / p; },\n date: (v, [s = \"medium\"]) => fmtDate(fmtToDate(v), { dateStyle: s, ...fmtTZ(v) }, v),\n time: (v, [s = \"short\"]) => fmtDate(fmtToDate(v), { timeStyle: s, ...fmtTZ(v) }, v),\n datetime: (v, [ds = \"medium\", ts = \"short\"]) => fmtDate(fmtToDate(v), { dateStyle: ds, timeStyle: ts, ...fmtTZ(v) }, v),\n upper: (v) => String(v ?? \"\").toUpperCase(),\n lower: (v) => String(v ?? \"\").toLowerCase(),\n capitalize: (v) => { const s = String(v ?? \"\"); return s ? s[0].toUpperCase() + s.slice(1) : s; },\n truncate: (v, [n = 50, suf = \"…\"]) => { const s = String(v ?? \"\"); return s.length > +n ? s.slice(0, +n).trimEnd() + String(suf) : s; },\n trim: (v) => String(v ?? \"\").trim(),\n pluralize: (v, [sg = \"\", pl]) => { const n = fmtNum(v); return `${new Intl.NumberFormat().format(n)} ${Math.abs(n) === 1 ? String(sg) : pl != null ? String(pl) : `${sg}s`}`; },\n default: (v, [fb = \"\"]) => (v == null || v === \"\" || (Array.isArray(v) && !v.length) ? String(fb) : v),\n sum: (v, [f]) => fmtNums(fmtList(v), f).reduce((a, n) => a + n, 0),\n avg: (v, [f]) => { const l = fmtList(v); return l.length ? FMT.sum(l, [f]) / l.length : 0; },\n min: (v, [f]) => { const l = fmtList(v); return l.length ? Math.min(...fmtNums(l, f)) : 0; },\n max: (v, [f]) => { const l = fmtList(v); return l.length ? Math.max(...fmtNums(l, f)) : 0; },\n count: (v) => fmtList(v).length,\n join: (v, [sep = \", \", f]) => fmtList(v).map((r) => fmtPick(r, f)).join(String(sep))\n};\n\n/**\n * Run a node's `data-wd-fmt` pipe chain over a resolved value; identity when the\n * node carries no chain. Shared by text binds, loop-row binds, and meta markers.\n * @param {Element} node\n * @param {any} value\n * @returns {any}\n */\nfunction applyFmt(node, value) {\n const raw = node.getAttribute(\"data-wd-fmt\");\n if (!raw) return value;\n let stages;\n try { stages = JSON.parse(raw); } catch { return value; }\n let out = value;\n for (const [name, args] of stages) { if (FMT[name]) out = FMT[name](out, args || []); }\n return out;\n}\n\n/** Aggregate helper exposed to `:computed` as `A(\"sum\", list, \"field\")`. */\nconst AGG = (/** @type {string} */ name, /** @type {any} */ list, /** @type {string} */ field) =>\n FMT[name] ? FMT[name](list, field == null ? [] : [field]) : undefined;\n\n/**\n * Shared HTTP→JSON core for `:fetch` and the round-trip `:form`. Sends the\n * request, throws on non-2xx, and parses the body as JSON (falling back to a\n * `{status,body}` wrapper for non-JSON responses). Unifying both callers keeps\n * the runtime under budget.\n * @param {string} url\n * @param {RequestInit} [init]\n * @returns {Promise<any>}\n */\nasync function httpJson(url, init) {\n const response = await fetch(url, init);\n const text = await response.text();\n const body = jsonOr(text);\n if (!response.ok) {\n // A real API says WHY it refused. Prefer the body's own `error`/`message`\n // string over the bare status line, and carry the parsed body along so a\n // page can render field-level errors from `<name>_error_body`.\n // The first NON-EMPTY string of the two: `{ \"error\": \"\", \"message\": \"…\" }`\n // is a real shape, and `??` would keep the empty string and render \"Error\".\n const said = body && [body.error, body.message].find((v) => typeof v === \"string\" && v);\n const err = /** @type {Error & { status?: number, body?: any }} */ (new Error(said || `HTTP ${response.status}`));\n err.status = response.status; // lets the :fetch lifecycle single out a 401 for token refresh\n err.body = body ?? null;\n throw err;\n }\n return body === undefined ? { status: response.status, body: text } : body;\n}\n\n/**\n * Write the `<name>_error` / `<name>_error_body` pair for a key: the server's\n * own message and its parsed JSON body, or null/null when there is no failure.\n * @param {string} key\n * @param {any} [error]\n * @returns {void}\n */\nfunction setError(key, error) {\n state[key + \"_error\"] = error ? error.message || String(error) : null;\n state[key + \"_error_body\"] = (error && error.body) ?? null;\n}\n\n/**\n * Bump a node's generation token and hand back a staleness probe. An in-flight\n * request that a newer one superseded must never write its result. Shared by the\n * `:fetch` lifecycle and the round-trip `:form` submit.\n * @param {{ __wdGen?: number }} node\n * @returns {() => boolean}\n */\nfunction genGuard(node) {\n const gen = (node.__wdGen = (node.__wdGen || 0) + 1);\n return () => gen !== node.__wdGen;\n}\n\n/**\n * Set whenever a render pass changes STRUCTURE — an `:if` branch injected, a loop\n * row cloned, an `@empty` branch filled. It buys one more pass (the document-wide\n * queries must reach the new nodes) and one more `scan()` (the new nodes may be\n * directives no query had ever seen). True at boot: nothing is registered yet.\n */\nlet dirty = true;\n\n/**\n * Log an expression failure when `window.wd.debug` is enabled.\n * @param {unknown} expr\n * @param {unknown} error\n * @returns {void}\n */\nfunction warn(expr, error) {\n const wd = /** @type {any} */ (window).wd;\n if (wd && wd.debug) console.warn(`[wd] expression failed: ${expr}`, error);\n}\n\n/**\n * Re-evaluate every `:computed` marker in the live tree into `state`. Read from\n * the DOM rather than a registry captured at load: `querySelectorAll` cannot see\n * into `<template>` content, so a registry would permanently miss a `:computed`\n * that only becomes live when an `:if` branch opens. `parseAst` caches by the raw\n * attribute text, so re-reading costs an attribute read, not a re-parse.\n * @returns {void}\n */\nfunction recompute() {\n for (const node of document.querySelectorAll(\"[data-wd-computed]\")) {\n const key = node.getAttribute(\"data-wd-computed-key\") || \"\";\n const expr = node.getAttribute(\"data-wd-computed-expr\") || \"null\";\n try {\n state[key] = evalAst(parseAst(expr), undefined);\n } catch (error) {\n state[key] = undefined;\n warn(expr, error);\n }\n }\n}\n\n/** @param {unknown} a @param {unknown} b @returns {boolean} */\nconst containsFn = (a, b) => String(a ?? \"\").toLowerCase().includes(String(b ?? \"\").toLowerCase());\n\n/**\n * Evaluate a compact expression AST (see src/compiler/expr-ast.js) against the\n * live state and an optional loop `item`. The compiler emits this serialized AST\n * into the `data-wd-*` attributes; the runtime WALKS it — no `new Function`, so\n * reactive pages run under a strict CSP with no `'unsafe-eval'`. The op vocabulary\n * is closed: every node tag is one of the readers (L/S/I/C/A), a unary (!/u-), or\n * a binary operator, and an unknown tag is a hard error, never a fallthrough.\n * Prototype-pollution segments are rejected by getPath, exactly as before.\n * @param {any[]} node\n * @param {any} item Loop row for I(); undefined otherwise.\n * @returns {any}\n */\nfunction evalAst(node, item) {\n /** @param {any[]} n @returns {any} */\n const ev = (n) => {\n switch (n[0]) {\n case \"L\": return n[1];\n case \"S\": return getPath(state[n[1]], n[2] ?? null);\n case \"I\": return getPath(item, n[1] ?? null);\n case \"C\": return containsFn(ev(n[1]), ev(n[2]));\n case \"A\": return AGG(n[1], ev(n[2]), n[3]);\n case \"!\": return !ev(n[1]);\n case \"u-\": return -ev(n[1]);\n case \"&&\": return ev(n[1]) && ev(n[2]);\n case \"||\": return ev(n[1]) || ev(n[2]);\n // biome-ignore lint/suspicious/noDoubleEquals: loose equality preserves prior `new Function` semantics exactly.\n case \"==\": return ev(n[1]) == ev(n[2]);\n // biome-ignore lint/suspicious/noDoubleEquals: loose inequality preserves prior `new Function` semantics exactly.\n case \"!=\": return ev(n[1]) != ev(n[2]);\n case \">\": return ev(n[1]) > ev(n[2]);\n case \"<\": return ev(n[1]) < ev(n[2]);\n case \">=\": return ev(n[1]) >= ev(n[2]);\n case \"<=\": return ev(n[1]) <= ev(n[2]);\n case \"+\": return ev(n[1]) + ev(n[2]);\n case \"-\": return ev(n[1]) - ev(n[2]);\n case \"*\": return ev(n[1]) * ev(n[2]);\n case \"/\": return ev(n[1]) / ev(n[2]);\n case \"%\": return ev(n[1]) % ev(n[2]);\n }\n throw new Error(`wd: unknown op ${n[0]}`);\n };\n return ev(node);\n}\n\n/** @type {Map<string, any[]>} Parsed-AST cache keyed by the raw attribute JSON. */\nconst astCache = new Map();\n/** @param {string} raw @returns {any[]} */\nfunction parseAst(raw) {\n const cached = astCache.get(raw);\n if (cached) return cached;\n const ast = JSON.parse(raw);\n astCache.set(raw, ast);\n return ast;\n}\n\n/**\n * Evaluate an expression AST to a boolean, swallowing failures (logs under\n * `wd.debug`). Shared by reactive classes (`.class when`), loop `where`, and\n * expression conditionals (`:if a > b`).\n * @param {any[]} ast\n * @param {any} item Loop row for per-row predicates, else undefined.\n * @returns {boolean}\n */\nfunction evalPredicate(ast, item) {\n try { return Boolean(evalAst(ast, item)); }\n catch (error) { warn(ast, error); return false; }\n}\n\n/**\n * Toggle reactive `.class when <predicate>` bindings on an element. `raw` is the\n * JSON `[[name, ast], …]` from data-wd-class (global, item undefined) or\n * data-wd-each-class (per loop row); each ast is the same expression AST the loop\n * `where` uses.\n * @param {Element} el\n * @param {string | null} raw\n * @param {any} item Loop item for each-class; undefined for a global class.\n * @returns {void}\n */\nfunction classToggle(el, raw, item) {\n if (!raw) return;\n /** @type {[string, any[]][]} */\n let pairs;\n try { pairs = JSON.parse(raw); } catch { return; }\n for (const [name, ast] of pairs) el.classList.toggle(name, evalPredicate(ast, item));\n}\n\n/**\n * Stable per-render key for a loop row, disambiguating duplicates with `#n`.\n * @param {any} item\n * @param {Map<string, number>} counts\n * @returns {string}\n */\nfunction loopKeyOf(item, counts) {\n // Every literal `#` doubles, so the `#n` duplicate suffix lives in a namespace\n // no real key can reach: a genuine \"a#1\" keys as \"a##1\", never as a's duplicate.\n const base = (\n item && typeof item === \"object\"\n ? String(item.id ?? item.key ?? JSON.stringify(item))\n : String(item)\n ).replace(/#/g, \"##\");\n const seen = counts.get(base) || 0;\n counts.set(base, seen + 1);\n return seen ? `${base}#${seen}` : base;\n}\n\n/**\n * Fill a cloned loop row node: resolve per-item `:if` regions (item- and\n * meta-driven), text binds, and per-row meta markers.\n * @param {Element} node\n * @param {any} item\n * @param {Record<string, any>} [meta] Per-row meta values (index/number/…).\n * @returns {void}\n */\nfunction fillItem(node, item, meta = {}) {\n // Nested item-relative loops (data-wd-loop-item) own their descendants' binds;\n // when present, `skip` excludes a node living inside one so the outer row's\n // text/meta/each-if/class passes never overwrite an inner row's values.\n let inners = node.querySelectorAll(\"[data-wd-loop-item]\");\n const skip = (/** @type {Element} */ el) => inners.length && el.closest(\"[data-wd-loop-item]\");\n // querySelectorAll skips <template> content, so this sees only the outermost\n // regions; recursing into each injected branch fills the rest.\n for (const region of node.querySelectorAll(\"[data-wd-each-if]\")) {\n if (skip(region)) continue;\n const expr = region.getAttribute(\"data-wd-if-expr\");\n const m = region.getAttribute(\"data-wd-meta\");\n const value = expr ? evalPredicate(parseAst(expr), item) : (m ? meta[m] : getPath(item, region.getAttribute(\"data-wd-path\")));\n const output = region.querySelector(\"[data-wd-each-if-out]\");\n if (!output) continue;\n // swapBranch carries the same skip-when-unchanged guard the global :if has:\n // re-injecting an unchanged branch on every render would destroy whatever is\n // live inside it — a nested loop's reconciled rows, focus, a playing <video>.\n swapBranch(region, output, value, \"wd-if\");\n fillItem(output, item, meta);\n }\n // An each-if branch can REVEAL a nested loop, and the capture above predates\n // the injection — re-read it, or the passes below overwrite the inner rows\n // with the OUTER row's values.\n inners = node.querySelectorAll(\"[data-wd-loop-item]\");\n for (const target of selfAnd(node, \"[data-wd-each]\")) {\n if (skip(target)) continue;\n target.textContent = applyFmt(target, getPath(item, target.getAttribute(\"data-wd-path\"))) ?? \"\";\n }\n for (const marker of node.querySelectorAll(\"[data-wd-each-meta]\")) {\n if (skip(marker)) continue;\n marker.textContent = applyFmt(marker, meta[marker.getAttribute(\"data-wd-each-meta\") || \"\"]) ?? \"\";\n }\n for (const el of selfAnd(node, \"[data-wd-each-class]\")) { if (!skip(el)) classToggle(el, el.getAttribute(\"data-wd-each-class\"), item); }\n // Per-row bound attributes (a link/image destination reading the row). State\n // parts resolve here too, so a destination mixing the two stays correct.\n for (const el of selfAnd(node, \"[data-wd-each-attr]\")) { if (!skip(el)) applyAttr(el, el.getAttribute(\"data-wd-each-attr\"), item, meta); }\n // Reconcile nested loops last, against the now-settled outer row.\n for (const region of inners) reconcile(region, getPath(item, region.getAttribute(\"data-wd-loop-item\")));\n}\n\n/**\n * Read a loop offset/limit attribute that is a literal int or a `key:<name>`\n * referencing state. Returns null when the attribute is absent.\n * @param {Element} region\n * @param {string} name\n * @returns {number | null}\n */\nfunction loopNum(region, name) {\n const raw = region.getAttribute(name);\n if (raw == null) return null;\n return raw.startsWith(\"key:\") ? Number(state[raw.slice(4)] ?? 0) : Number(raw);\n}\n\n/**\n * Apply sort → reverse → offset → limit to an already-filtered list, reading\n * clause config off the region. Stable; numeric vs localeCompare comparator.\n * @param {any[]} list\n * @param {Element} region\n * @returns {any[]}\n */\nfunction pipeline(list, region) {\n const sortAttr = region.getAttribute(\"data-wd-loop-sort\");\n if (sortAttr != null) {\n // `key:<name>` reads the sort field / direction from state (reactive,\n // clickable-header sort); a plain value is the literal `sort by` clause.\n const reactive = sortAttr.startsWith(\"key:\");\n const sort = reactive ? String(state[sortAttr.slice(4)] ?? \"\") : sortAttr;\n // A reactive sort with no column chosen yet (state is \"\") leaves the order.\n if (!reactive || sort) {\n let dirAttr = region.getAttribute(\"data-wd-loop-sort-dir\") || \"asc\";\n if (dirAttr.startsWith(\"key:\")) dirAttr = String(state[dirAttr.slice(4)] ?? \"asc\");\n const dir = dirAttr === \"desc\" ? -1 : 1;\n list = list.map((value, index) => ({ value, index })).sort((a, b) => {\n const av = getPath(a.value, sort || null);\n const bv = getPath(b.value, sort || null);\n const c = typeof av === \"number\" && typeof bv === \"number\" ? av - bv : String(av).localeCompare(String(bv));\n return c ? c * dir : a.index - b.index;\n }).map((w) => w.value);\n }\n }\n if (region.hasAttribute(\"data-wd-loop-reverse\")) list.reverse();\n const off = loopNum(region, \"data-wd-loop-offset\");\n if (off) list = list.slice(off);\n const lim = loopNum(region, \"data-wd-loop-limit\");\n if (lim != null) list = list.slice(0, lim);\n return list;\n}\n\n/**\n * Swap a conditional region to the branch its truthiness selects, and report\n * whether anything actually moved. The unchanged-guard is the load-bearing part:\n * re-injecting the branch that is already showing would replace every live node\n * inside it on every render. One implementation for both conditionals — the\n * global `:if` (`prefix` \"wd\", templates `data-wd-true`/`-false`) and the per-row\n * `:if` inside a loop row (\"wd-if\", `data-wd-if-true`/`-false`).\n * @param {Element} region Carries data-wd-if-active, the last painted branch.\n * @param {Element} output The node whose contents the branch replaces.\n * @param {any} value\n * @param {string} prefix\n * @returns {boolean} true when the branch was swapped\n */\nfunction swapBranch(region, output, value, prefix) {\n const active = String(Boolean(value));\n if (region.getAttribute(\"data-wd-if-active\") === active) return false;\n region.setAttribute(\"data-wd-if-active\", active);\n const template = /** @type {HTMLTemplateElement | null} */ (region.querySelector(`template[data-${prefix}-${active}]`));\n unregister(output); // the branch being replaced takes its directives with it\n output.innerHTML = template?.innerHTML || \"\";\n // The branch may carry directives no query has ever seen.\n return (dirty = true);\n}\n\n/**\n * Evaluate one global `data-wd-if` region against current state and swap its\n * branch when the truthiness flips. `data-wd-if-expr` carries a compiled\n * predicate (richer `:if`); otherwise it is a bare-path truthiness test. On a\n * swap, the freshly injected branch can carry nested if-regions (a desugared\n * `:else if`) that were never in the live tree this pass — querySelectorAll\n * skips <template> content — so recurse to fill them. Unchanged regions are\n * left alone; their already-live nested regions are reached by renderNow's\n * document-wide query.\n * @param {Element} node\n * @returns {void}\n */\nfunction renderIf(node) {\n const expr = node.getAttribute(\"data-wd-if-expr\");\n const value = expr ? evalPredicate(parseAst(expr), undefined) : getPath(state[node.getAttribute(\"data-wd-if\") || \"\"], node.getAttribute(\"data-wd-path\"));\n const output = node.querySelector(\"[data-wd-if-out]\");\n if (!output || !swapBranch(node, output, value, \"wd\")) return;\n for (const nested of output.querySelectorAll(\"[data-wd-if]\")) renderIf(nested);\n}\n\n/**\n * Reconcile one loop region (top-level or nested) from a resolved row source:\n * filter (`where`) → pipeline (sort/reverse/offset/limit) → @empty → keyed\n * reconcile. Each surviving row is `fillItem`-ed, which itself recurses into any\n * nested item-relative loops. Shared by the global pass and `fillItem`.\n * @param {Element} region\n * @param {any} rows Source list (state-resolved globally; item-relative inside fillItem).\n * @returns {void}\n */\nfunction reconcile(region, rows) {\n const template = /** @type {HTMLTemplateElement | null} */ (region.querySelector(\"template[data-wd-loop-template]\"));\n const out = region.querySelector(\"[data-wd-loop-out]\");\n if (!template || !out) return;\n let list = Array.isArray(rows) ? rows.slice() : [];\n const where = region.getAttribute(\"data-wd-loop-where\");\n if (where) {\n const ast = parseAst(where);\n list = list.filter((/** @type {any} */ item) => evalPredicate(ast, item));\n }\n list = pipeline(list, region);\n\n // Empty branch: clone the [data-wd-loop-empty] template into the output.\n const emptyTpl = /** @type {HTMLTemplateElement | null} */ (region.querySelector(\"template[data-wd-loop-empty]\"));\n if (!list.length && emptyTpl) {\n if (out.getAttribute(\"data-wd-empty\") !== \"1\") {\n unregister(out); // the rows being wiped take their directives with them\n out.textContent = \"\";\n for (const child of [...emptyTpl.content.children]) out.appendChild(child.cloneNode(true));\n out.setAttribute(\"data-wd-empty\", \"1\");\n dirty = true;\n }\n return;\n }\n // The @empty branch is ONE branch, not a per-row clone, so it may legally hold\n // an :every/:effect. Emptying and refilling the list re-clones it, so the copy\n // leaving here must be dropped or the next empty state would tick twice.\n if (out.getAttribute(\"data-wd-empty\") === \"1\") { unregister(out); out.textContent = \"\"; out.removeAttribute(\"data-wd-empty\"); }\n\n /** @type {Map<string, WdRow>} */\n const existing = new Map();\n for (const child of [...out.children]) {\n existing.set(child.getAttribute(\"data-wd-loop-key\") || \"\", /** @type {WdRow} */ (child));\n }\n /** @type {Map<string, number>} */\n const counts = new Map();\n /** @type {Set<string>} */\n const used = new Set();\n const count = list.length;\n let moved = false;\n for (let i = 0; i < count; i++) {\n const item = list[i];\n const key = loopKeyOf(item, counts);\n let node = existing.get(key);\n if (!node || used.has(key)) {\n node = /** @type {WdRow} */ (template.content.firstElementChild?.cloneNode(true));\n if (!node) continue;\n node.setAttribute(\"data-wd-loop-key\", key);\n // A fresh row missed this pass's document-wide if/class/scan work.\n dirty = true;\n }\n used.add(key);\n fillItem(node, item, { index: i, number: i + 1, first: i === 0, last: i === count - 1, count });\n node.__wdItem = item; /** let per-row actions resolve which row was clicked */\n // Leave a row that is already in place ALONE. appendChild is a\n // remove-then-insert per spec, so appending unconditionally blurs the\n // focused field, resets scroll, restarts CSS transitions and reloads an\n // <iframe> in a row — on every render, even when the list did not change.\n // Once one row has had to move, every later row must be re-appended too, or\n // the ones left behind would sit in front of it.\n if (moved || out.children[i] !== node) { moved = true; out.appendChild(node); }\n }\n for (const [key, node] of existing) {\n if (!used.has(key)) { unregister(node); node.remove(); }\n }\n}\n\n/**\n * Synchronously render the whole document from current `state`:\n * (scan → computed → if-regions → reactive classes → keyed loop reconcile)\n * repeated until the structure settles → text/input binds → theme → effects.\n *\n * The repeat is the point. The if/class passes are document-wide queries, so a\n * row the loop reconcile CLONED during the same pass was not in the tree when\n * they ran and would keep the compile-time branch and classes baked into its\n * template — wrong until some unrelated state change. Symmetrically, a branch an\n * `:if` just injected can carry `:fetch`/`:computed`/`:effect`/`:every`/`:theme`\n * nodes that no query had ever seen. Either marks `dirty`, which buys one more\n * pass; two passes always suffice in practice and three is the hard cap.\n * @returns {void}\n */\nlet settleTries = 0;\nfunction renderNow() {\n for (let pass = 0; pass < 3; pass++) {\n if (dirty) scan();\n dirty = false;\n recompute();\n for (const node of document.querySelectorAll(\"[data-wd-if]\")) renderIf(node);\n\n // Reactive styling: toggle state-driven `.class when …` bindings. Loop-row\n // bindings (data-wd-each-class) are handled per item inside fillItem.\n for (const el of document.querySelectorAll(\"[data-wd-class]\")) classToggle(el, el.getAttribute(\"data-wd-class\"), undefined);\n\n // Nested item-relative loops use data-wd-loop-item, so this top-level query\n // skips them; fillItem reconciles those against their enclosing row.\n for (const region of document.querySelectorAll(\"[data-wd-loop]\")) {\n const key = region.getAttribute(\"data-wd-loop\");\n const data = region.getAttribute(\"data-wd-loop-data\");\n /** Source may be a dotted path (e.g. team.members) read off state via getPath. */\n const dot = key ? key.indexOf(\".\") : -1;\n reconcile(region, key ? (dot < 0 ? state[key] : getPath(state[key.slice(0, dot)], key.slice(dot + 1))) : (data ? JSON.parse(data) : []));\n }\n if (!dirty) break;\n }\n // Deeper than the cap (an `:if` whose branch declares the `:computed` a nested\n // `:if` reads, a few levels down) the loop used to exit still dirty and leave\n // the innermost region showing its compile-time branch until some unrelated\n // state change. Finish this paint, then schedule another pass; the counter\n // bounds a genuine cycle instead of spinning forever.\n if (dirty && ++settleTries < 10) {\n if (/** @type {any} */ (window).wd?.debug) console.warn(\"[wd] render did not settle; scheduling another pass\");\n render();\n } else settleTries = 0;\n\n for (const node of document.querySelectorAll(\"[data-wd-bind]\")) {\n node.textContent = applyFmt(node, getPath(state[node.getAttribute(\"data-wd-bind\") || \"\"], node.getAttribute(\"data-wd-path\"))) ?? \"\";\n }\n\n for (const node of document.querySelectorAll(\"[data-wd-attr]\")) applyAttr(node, node.getAttribute(\"data-wd-attr\"), undefined, {});\n\n // Bound controls, reflected back from state. A checkbox carries a boolean and\n // a radio carries whichever option equals the value, so neither can go through\n // `.value`; a text field keeps the focus guard, because overwriting what\n // someone is typing mid-keystroke moves their caret.\n for (const input of /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll(\"[data-wd-bind-input]\"))) {\n const value = state[input.getAttribute(\"data-wd-bind-input\") || \"\"] ?? \"\";\n if (input.type === \"checkbox\") input.checked = Boolean(value);\n else if (input.type === \"radio\") input.checked = input.value === String(value);\n else if (document.activeElement !== input) input.value = value;\n }\n\n reflectThemes();\n // side effects run last, against fully settled state\n runEffects();\n if (subscribers.size) notifySubscribers();\n}\n\n/** :theme — reflect a store value onto <html data-theme> so a manual light/dark\n * switch layers over the OS preference: `auto`/empty clears the attribute (follow\n * the system via the skin's `tokens dark` media query), anything else forces it. */\nfunction reflectThemes() {\n for (const node of document.querySelectorAll(\"[data-wd-theme]\")) {\n const value = state[node.getAttribute(\"data-wd-theme\") || \"\"];\n const root = document.documentElement;\n if (value == null || value === \"\" || value === \"auto\") root.removeAttribute(\"data-theme\");\n else root.setAttribute(\"data-theme\", String(value));\n }\n}\n\n/** @typedef {{ node: Element, watch: string, actions: { op: string, target?: string, value?: any }[], last: string }} Effect */\n/** @type {Effect[]} */\nconst effects = [];\nlet effectDepth = 0;\n/**\n * After a render, fire any `:effect` whose watched state changed, running its\n * actions through the shared action runner. If that mutates state, re-render and\n * re-check; a settle cap of 10 passes guards against effect→effect loops.\n * @returns {void}\n */\nfunction runEffects() {\n if (!effects.length) return;\n let changed = false;\n for (const fx of effects) {\n const now = JSON.stringify(getPath(state, fx.watch) ?? null);\n if (now === fx.last) continue;\n fx.last = now;\n runActions(fx.actions);\n changed = true;\n }\n if (!changed) { effectDepth = 0; return; }\n // An effect writes state exactly like a click does, so it persists and syncs\n // to the URL exactly like a click does. Without this an `:effect` that writes\n // a `from-url` key never reached the address bar at all.\n savePersisted();\n if (++effectDepth >= 10) { effectDepth = 0; console.warn(\"effect did not settle\"); return; }\n renderNow(); // reflect the effect's state changes, then re-check\n}\n\n/**\n * Coalesce rapid state mutations into one render on the next tick. Each scheduled\n * pass always reads the latest state, so the final update is never dropped.\n */\nconst schedule = typeof requestAnimationFrame === \"function\" ? requestAnimationFrame : queueMicrotask;\nlet scheduled = false;\nfunction render() {\n if (scheduled) return;\n scheduled = true;\n schedule(() => {\n scheduled = false;\n renderNow();\n });\n}\n\ndocument.addEventListener(\"input\", (event) => {\n const input = /** @type {HTMLInputElement | null} */ (/** @type {Element} */ (event.target)?.closest(\"[data-wd-bind-input]\"));\n if (!input) return;\n // A `:slider` binds a range input: keep state numeric so `:computed`/math see a\n // number, not the DOM's string `.value`. A standalone `:checkbox` binds the\n // boolean, not the label. Everything else stays a string, as before.\n state[input.getAttribute(\"data-wd-bind-input\") || \"\"] = input.type === \"range\" ? input.valueAsNumber : input.type === \"checkbox\" ? input.checked : input.value;\n savePersisted();\n render();\n checkRefetch();\n});\n\n/**\n * Resolve the loop source and row item for a clicked element, if inside a loop.\n * The clicked button's row is the nearest reconciled loop node; it carries its item.\n * @param {Element | null} el Null for effect-driven actions (no clicked row).\n * @returns {{ srcKey: string | null, item: any } | null}\n */\nfunction clickedRow(el) {\n if (!el) return null; // effect-driven actions have no clicked row → row ops no-op\n const row = /** @type {WdRow | null} */ (el.closest(\"[data-wd-loop-key]\"));\n const region = el.closest(\"[data-wd-loop]\");\n if (!row || !region) return null;\n return { srcKey: region.getAttribute(\"data-wd-loop\"), item: row.__wdItem };\n}\n\n/**\n * Apply one parsed action `{op,target,value}` against state. Targets are dotted\n * paths read via getPath / written via setPath; a bare name is a 1-segment path.\n * @param {Element | null} action The clicked element (for loop-row resolution); null for effects.\n * @param {string} op\n * @param {string} target\n * @param {any} value\n * @returns {void}\n */\nfunction applyAction(action, op, target, value) {\n /** @param {any} v */\n const put = (v) => setPath(state, target, v);\n const cur = getPath(state, target);\n const arr = () => (Array.isArray(cur) ? cur : []);\n if (op === \"inc\") put(Number(cur ?? 0) + 1);\n if (op === \"dec\") put(Number(cur ?? 0) - 1);\n if (op === \"add\") put(Number(cur ?? 0) + Number(value));\n if (op === \"sub\") put(Number(cur ?? 0) - Number(value));\n if (op === \"toggle\") put(!cur);\n if (op === \"set\") put(value);\n if (op === \"append\") put([...arr(), value]);\n if (op === \"prepend\") put([value, ...arr()]);\n if (op === \"member-toggle\") put(arr().includes(value) ? arr().filter((/** @type {any} */ x) => x !== value) : [...arr(), value]);\n if (op === \"remove-value\") put(arr().filter((/** @type {any} */ x) => x !== value));\n if (op === \"clear\") put(Array.isArray(cur) ? [] : {});\n if (op === \"merge\") put({ ...(cur && typeof cur === \"object\" ? cur : {}), ...(typeof value === \"string\" ? getPath(state, value) : value) });\n if (op === \"delete\") { if (cur && typeof cur === \"object\") { delete cur[value]; put(cur); } }\n if (op === \"reset\") put(structuredClone(getPath(initials, target)));\n if (op === \"refetch\") { const n = document.querySelector(`[data-wd-fetch-key=\"${target}\"]`); if (n) startFetch(n); }\n if (op === \"remove\") {\n const row = clickedRow(action);\n if (row && row.srcKey) state[row.srcKey] = (Array.isArray(state[row.srcKey]) ? state[row.srcKey] : []).filter((/** @type {any} */ x) => x !== row.item);\n }\n if (op === \"append-row\") {\n const row = clickedRow(action);\n // Clone so each appended line is a distinct object — else a shared ref\n // means a later remove (filter !== ref) deletes both lines.\n if (row && row.item !== undefined) {\n const copy = row.item && typeof row.item === \"object\" ? structuredClone(row.item) : row.item;\n put([...arr(), copy]);\n }\n }\n}\n\n/**\n * Run a parsed action sequence with no clicked row — the `:effect` and `:every`\n * paths. Their defs are parsed ONCE and re-fired, so an `append`/`prepend` would\n * push the SAME object reference every time and a later `remove` (which filters\n * by identity) would delete every copy at once. Clone per fire; the click path\n * re-parses its JSON attribute each time and needs no copy.\n * @param {{ op: string, target?: string, value?: any }[]} actions\n * @returns {void}\n */\nconst runActions = (actions) => {\n for (const a of actions) applyAction(null, a.op, a.target || \"\", structuredClone(a.value));\n};\n\ndocument.addEventListener(\"click\", (event) => {\n const action = /** @type {Element} */ (event.target)?.closest(\"[data-wd-action],[data-wd-actions]\");\n if (!action) return;\n const seq = action.getAttribute(\"data-wd-actions\");\n if (seq) {\n for (const a of JSON.parse(seq)) applyAction(action, a.op, a.target || \"\", a.value);\n } else {\n const rawValue = action.getAttribute(\"data-wd-value\");\n applyAction(action, action.getAttribute(\"data-wd-action\") || \"\", action.getAttribute(\"data-wd-target\") || \"\", rawValue === null ? undefined : JSON.parse(rawValue));\n }\n savePersisted();\n render();\n checkRefetch();\n});\n\ndocument.addEventListener(\"submit\", (event) => {\n const form = /** @type {HTMLFormElement | null} */ (/** @type {Element} */ (event.target)?.closest(\"[data-wd-form]\"));\n if (!form) return;\n event.preventDefault();\n const key = form.getAttribute(\"data-wd-form\") || \"\";\n const action = form.getAttribute(\"action\");\n\n if (!action) {\n const fd = new FormData(form);\n const data = /** @type {Record<string, any>} */ (Object.fromEntries(fd));\n // :checkbox groups share one name; collect every checked value as an array.\n for (const group of form.querySelectorAll(\"[data-wd-multi]\")) {\n const field = group.getAttribute(\"data-wd-multi\");\n if (field) data[field] = fd.getAll(field);\n }\n state[key] = data;\n savePersisted();\n render();\n return;\n }\n\n // A slow earlier submit must not land on top of a newer one's reply, and the\n // previous attempt's error must not sit on screen through the retry.\n const dead = genGuard(/** @type {any} */ (form));\n setError(key);\n render();\n // ONE FormData feeds both bodies. Bound controls (`:bind`, a bound `:select`)\n // carry no `name`, so FormData never saw them: they are appended by their\n // state key here, before the split, so the urlencoded path carries exactly\n // the same keys the multipart path does. A bound key that a real field\n // already occupies is left alone — sending both would hand the server the\n // same name twice with different values.\n const fd = new FormData(form);\n for (const el of form.querySelectorAll(\"[data-wd-bind-input]\")) {\n const bound = el.getAttribute(\"data-wd-bind-input\");\n if (bound && !el.getAttribute(\"name\") && !fd.has(bound)) fd.append(bound, state[bound] ?? \"\");\n }\n // A file cannot be urlencoded, so a form carrying one posts the FormData\n // itself and lets the browser set the multipart content-type WITH its\n // boundary, which is exactly why no content-type is passed here.\n const upload = form.querySelector(\"input[type=file]\");\n httpJson(action, {\n method: form.getAttribute(\"method\") || \"post\",\n headers: upload ? undefined : { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: upload ? fd : new URLSearchParams(/** @type {any} */ (fd)).toString()\n })\n .then((value) => {\n if (dead()) return;\n state[key] = value;\n setError(key);\n savePersisted();\n render();\n })\n .catch((error) => {\n if (dead()) return;\n setError(key, error);\n render();\n });\n});\n\n/**\n * Run the full `:fetch` lifecycle for a marker node: interpolate the URL from\n * state, skip when a dependency is empty, flip `*_loading`, fetch with an\n * AbortController timeout + flat retry, then write value/`*_empty` or `*_error`.\n * @param {FetchNode} node\n * @returns {void}\n */\nfunction startFetch(node) {\n // Per-node generation token: a refetch bumps it; superseded writes bail.\n const dead = genGuard(node);\n /** @param {string} n @returns {string} */\n const g = (n) => node.getAttribute(\"data-wd-fetch-\" + n) || \"\";\n const key = g(\"key\");\n /** @param {string} s @param {any} v */\n const set = (s, v) => { state[key + s] = v; render(); };\n let missing = false;\n const url = g(\"url\").replace(/\\{\\s*([\\w$.]+)\\s*\\}/g, (_, p) => {\n const v = getPath(state, p);\n if (v == null || v === \"\") missing = true;\n return encodeURIComponent(String(v ?? \"\"));\n });\n if (missing) return set(\"_loading\", false);\n\n const headers = g(\"headers\");\n const body = g(\"body\");\n const refresh = g(\"refresh\");\n const method = g(\"method\") || \"GET\";\n const timeout = Number(g(\"timeout\"));\n let tries = Number(g(\"retry\"));\n let refreshed = false; // a token refresh + retry happens at most once per request\n setError(key);\n set(\"_loading\", true);\n\n /** @param {any} error */\n const fail = (error) => { setError(key, error); set(\"_loading\", false); };\n\n const attempt = () => {\n /** @type {RequestInit} */\n const init = { method };\n if (headers) init.headers = state[headers] || {};\n if (body && method !== \"GET\") init.body = JSON.stringify(state[body] ?? null);\n const ctrl = typeof AbortController === \"function\" ? new AbortController() : null;\n if (ctrl) init.signal = ctrl.signal;\n const timer = timeout && ctrl ? setTimeout(() => ctrl.abort(), timeout) : 0;\n httpJson(url, init).then((value) => {\n clearTimeout(timer);\n if (dead()) return;\n state[key] = value;\n state[key + \"_empty\"] = isEmpty(value);\n setError(key);\n set(\"_loading\", false);\n }).catch((error) => {\n clearTimeout(timer);\n if (dead()) return;\n const is401 = error && error.status === 401;\n // Layer 2: a 401 with a refresh URL renews the token once, then retries.\n if (is401 && refresh && headers && !refreshed) {\n refreshed = true;\n return void refreshToken(refresh, headers).then((ok) => {\n if (dead()) return;\n ok ? attempt() : fail(error);\n });\n }\n if (is401 && refreshed) return void fail(error); // a second 401 after refresh → give up\n if (tries-- > 0) return void setTimeout(attempt, 200);\n fail(error);\n });\n };\n attempt();\n}\n\n/** @type {Map<string, Promise<boolean>>} In-flight token refreshes, keyed by URL. */\nconst refreshFlights = new Map();\n/**\n * Single-flight token refresh: POST the current header state to `refreshUrl`,\n * and on a 2xx JSON reply write the returned headers object back into the\n * `headersKey` state (persisting if it is a `:store`). Concurrent 401s sharing a\n * refresh URL await the same request, then each retries its own fetch.\n * @param {string} refreshUrl\n * @param {string} headersKey\n * @returns {Promise<boolean>} whether the token was renewed\n */\nfunction refreshToken(refreshUrl, headersKey) {\n const inflight = refreshFlights.get(refreshUrl);\n if (inflight) return inflight;\n const flight = httpJson(refreshUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(state[headersKey] ?? {})\n }).then((value) => {\n if (value && typeof value === \"object\") {\n state[headersKey] = value; // the new headers shape, e.g. { Authorization: \"Bearer …\" }\n savePersisted();\n return true;\n }\n return false;\n }).catch(() => false).finally(() => refreshFlights.delete(refreshUrl));\n refreshFlights.set(refreshUrl, flight);\n return flight;\n}\n\n/** A fetch marker carrying its last dependency snapshot for refetch diffing and\n * a generation token so a superseded in-flight response/retry bails on write. */\n/** @typedef {Element & { __wdSnap?: string, __wdGen?: number }} FetchNode */\n/** @type {FetchNode[]} */\nconst fetchNodes = [];\n/** @param {FetchNode} node @returns {string} */\nconst depSnapshot = (node) => (node.getAttribute(\"data-wd-fetch-deps\") || \"\").split(\",\").filter(Boolean).map((d) => JSON.stringify(getPath(state, d))).join(\"|\");\n\n/** @type {any} */\nlet refetchTimer = 0;\n/** @type {Set<FetchNode>} Nodes owed a refetch, accumulated across the window. */\nconst due = new Set();\n/**\n * After a mutation render, debounce-refetch any fetch node whose URL deps\n * changed. Snapshots live on the node, so no extra bookkeeping structure. The\n * due set OUTLIVES each debounce restart: a second mutation resets the timer,\n * and a node dropped there could never be re-detected — its snapshot has already\n * been advanced — so its refetch would be lost rather than merely delayed.\n * @returns {void}\n */\nfunction checkRefetch() {\n for (const node of fetchNodes) {\n const snap = depSnapshot(node);\n if (snap && snap !== node.__wdSnap) { node.__wdSnap = snap; due.add(node); }\n }\n if (!due.size) return;\n clearTimeout(refetchTimer);\n refetchTimer = setTimeout(() => { for (const node of due) startFetch(node); due.clear(); }, 150);\n}\n\n/**\n * Drop every registration belonging to a subtree that is about to leave the\n * document — a closed `:if` branch, a removed loop row.\n *\n * `scan()` claims a node once, and the claim marker lives ON the node while the\n * registries (`effects`, `fetchNodes`, `everyDefs`) are module-level. A branch\n * is re-cloned from its template every time it opens, so without this the NEXT\n * open registers a FRESH node while the detached one stays: an `:every` ticked\n * twice after one reopen, three times after two, and kept ticking with the\n * branch closed. Same shape for `:effect` (fired once per stale copy) and\n * `:fetch` (one request per stale copy, on a state change the reader cannot\n * even see).\n * @param {Element} root\n * @returns {void}\n */\nfunction unregister(root) {\n for (const node of selfAnd(root, \"[data-wd-fetch],[data-wd-effect],[data-wd-every]\")) {\n due.delete(/** @type {any} */ (node));\n // A fetch entry IS the node; an effect/every entry carries it on `.node`.\n for (const list of [/** @type {any[]} */ (fetchNodes), effects, everyDefs]) {\n for (let i = list.length - 1; i >= 0; i--) {\n if ((list[i].node || list[i]) !== node) continue;\n if (list[i].t) clearInterval(list[i].t);\n list.splice(i, 1);\n }\n }\n }\n}\n\n/**\n * Claim and wire up the directives that need ONE-TIME setup: `:fetch` (kick off\n * the request, snapshot its deps), `:effect` (parse the def, snapshot its watch)\n * and `:every` (start a timer). `querySelectorAll` cannot see into `<template>`\n * content, so building these at load permanently missed any that lived in an\n * unopened `:if` branch or a loop row template — they simply never ran. Called\n * again whenever a render changes structure; each node is claimed once\n * (`__wdReg`), so a re-scan costs the query and nothing else. (`:computed` and\n * `:theme` need no claim at all: both read the live tree on every render.)\n * @returns {void}\n */\nfunction scan() {\n // Seed scripts first: a branch that just opened may DECLARE the state the\n // directives below read (see hydrate).\n hydrate();\n for (const node of /** @type {any} */ (document.querySelectorAll(\"[data-wd-fetch],[data-wd-effect],[data-wd-every]\"))) {\n if (node.__wdReg) continue;\n node.__wdReg = 1;\n if (node.hasAttribute(\"data-wd-effect\")) {\n const def = JSON.parse(node.textContent || \"{}\");\n effects.push({ node, watch: def.watch, actions: def.actions || [], last: JSON.stringify(getPath(state, def.watch) ?? null) });\n }\n if (node.hasAttribute(\"data-wd-every\")) {\n if (!everyDefs.length) document.addEventListener(\"visibilitychange\", () => (document.hidden ? stopEvery() : startEvery()));\n everyDefs.push({ ...JSON.parse(node.textContent || \"{}\"), node });\n startEvery();\n }\n if (node.hasAttribute(\"data-wd-fetch\")) {\n fetchNodes.push(node);\n node.__wdSnap = depSnapshot(node);\n if (node.getAttribute(\"data-wd-fetch-when\") === \"visible\" && \"IntersectionObserver\" in window) {\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n startFetch(node);\n });\n observer.observe(node);\n } else {\n startFetch(node);\n }\n }\n }\n}\n\n// :every — run actions on a timer, paused while the tab is hidden so background\n// tabs don't poll or animate. Each tick runs the action sequence (no clicked row,\n// like :effect), then saves + renders + checks refetch deps, exactly like a click.\n/** @typedef {{ node: Element, ms: number, actions: { op: string, target?: string, value?: any }[], t?: any }} EveryDef */\n/** @type {EveryDef[]} */\nconst everyDefs = [];\n// The timer lives on the def, not in a shared list, so a def registered later\n// (one revealed by an :if flip) starts ticking without restarting the others.\nfunction startEvery() {\n if (document.hidden) return;\n for (const def of everyDefs) {\n if (def.t || !def.ms || !def.actions) continue;\n def.t = setInterval(() => {\n runActions(def.actions);\n savePersisted();\n render();\n checkRefetch();\n }, def.ms);\n }\n}\nfunction stopEvery() {\n for (const def of everyDefs) { clearInterval(def.t); def.t = 0; }\n}\n\n// Escape-hatch subscriptions: colocated `.js` \"behaviors\" (carousels, drag-and-\n// drop, charts, maps) react to state with `wd.subscribe(key, cb)`. Each subscribe\n// primes the callback with the current value, then fires on every settled change.\n/** @typedef {{ cbs: Set<(value: any) => void>, last: string }} SubEntry */\n/** @type {Map<string, SubEntry>} */\nconst subscribers = new Map();\nfunction notifySubscribers() {\n for (const [key, entry] of subscribers) {\n const now = JSON.stringify(state[key] ?? null);\n if (now === entry.last) continue;\n entry.last = now;\n for (const cb of entry.cbs) {\n try { cb(state[key]); } catch (error) { warn(`subscribe ${key}`, error); }\n }\n }\n}\n\n/**\n * Public escape hatch on `window.wd`. Set `window.wd.debug = true` to log failing\n * computed/where expressions to the console.\n */\n/** @type {any} */ (window).wd = {\n state,\n debug: false,\n /** @param {string} key */\n get: (key) => state[key],\n /** @param {string} key @param {any} value */\n set: (key, value) => {\n state[key] = value;\n savePersisted();\n render();\n // The input and click handlers both do; behaviors write only through here.\n checkRefetch();\n },\n /**\n * Run `cb(value)` now and on every future change to `state[key]`. Returns an\n * unsubscribe function. The bridge for colocated `.js` behaviors.\n * @param {string} key @param {(value: any) => void} cb\n */\n subscribe: (key, cb) => {\n let entry = subscribers.get(key);\n if (!entry) {\n entry = { cbs: new Set(), last: JSON.stringify(state[key] ?? null) };\n subscribers.set(key, entry);\n }\n entry.cbs.add(cb);\n try { cb(state[key]); } catch (error) { warn(`subscribe ${key}`, error); }\n return () => entry.cbs.delete(cb);\n },\n render: renderNow\n};\n\nrenderNow();\n"],"mappings":"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","names":["state","persistKeys","storeKeys","urlKeys","initials","ls","k","v","urlParam","key","hydrate","script","store","seed","value","lsKey","stored","jsonOr","readUrl","syncUrl","params","query","next","restore","only","raw","savePersisted","name","render","event","unsafeKey","segment","getPath","path","current","setPath","obj","segs","last","cur","seg","isEmpty","text","URL_UNSAFE","selfAnd","node","sel","applyAttr","el","item","meta","parts","out","plain","p","c","fmtNum","n","fmtList","fmtPick","row","f","fmtNums","list","r","fmtToDate","s","fmtDate","d","opts","fb","fmtTZ","FMT","l","ds","ts","suf","sg","pl","a","sep","applyFmt","stages","args","AGG","field","httpJson","url","init","response","body","said","err","setError","error","genGuard","gen","dirty","warn","expr","wd","recompute","evalAst","parseAst","containsFn","b","ev","astCache","cached","ast","evalPredicate","classToggle","pairs","loopKeyOf","counts","base","seen","fillItem","inners","skip","region","m","output","swapBranch","target","marker","reconcile","loopNum","pipeline","sortAttr","reactive","sort","dirAttr","dir","index","av","bv","w","off","lim","prefix","active","template","unregister","renderIf","nested","rows","where","emptyTpl","child","existing","used","count","moved","i","settleTries","renderNow","pass","scan","data","dot","input","reflectThemes","runEffects","subscribers","notifySubscribers","root","effects","effectDepth","changed","fx","now","runActions","schedule","scheduled","checkRefetch","clickedRow","applyAction","action","op","put","arr","x","startFetch","copy","actions","seq","rawValue","form","fd","group","dead","bound","upload","g","set","missing","_","headers","refresh","method","timeout","tries","refreshed","fail","attempt","ctrl","timer","is401","refreshToken","ok","refreshFlights","refreshUrl","headersKey","inflight","flight","fetchNodes","depSnapshot","refetchTimer","due","snap","everyDefs","def","stopEvery","startEvery","observer","entries","entry","cb"],"file":"runtime.js"}
|
|
1
|
+
{"version":3,"sources":["../src/runtime.js"],"sourcesContent":["/**\n * Darkmown's tiny reactive runtime. Browser-only: it reads `data-wd-*` attributes\n * emitted by the compiler, hydrates declared state, and re-renders on changes.\n * @module runtime\n */\n\n/**\n * A reconciled loop-row element. Carries its source row on `__wdItem` so per-row\n * button actions can resolve which row was clicked.\n * @typedef {Element & { __wdItem?: any }} WdRow\n */\n\n/** @type {Record<string, any>} */\nconst state = {};\n/** @type {Set<string>} */\nconst persistKeys = new Set();\n/** @type {Set<string>} Non-ephemeral :store names, persisted under wd:store:<name>. */\nconst storeKeys = new Set();\n\n/** @type {Set<string>} Keys a `:state … from-url` mirrors into the query string. */\nconst urlKeys = new Set();\n\n// Seed snapshot, recorded per key BEFORE its localStorage/URL override, so\n// `reset` deep-clones the DECLARED value — not the last persisted one.\n/** @type {Record<string, any>} */\nconst initials = {};\n\n/**\n * Best-effort localStorage. Touching it THROWS outright in a sandboxed iframe or\n * a browser set to block site data, and setItem throws on quota — persistence\n * must never abort the boot or stop a render, so every access is swallowed here.\n * `ls(k)` reads, `ls(k, v)` writes, `ls(k, null)` drops a corrupt entry.\n * @param {string} k\n * @param {string | null} [v]\n * @returns {any}\n */\nfunction ls(k, v) {\n try {\n return v === undefined ? localStorage.getItem(k) : v === null ? localStorage.removeItem(k) : localStorage.setItem(k, v);\n } catch { return null; }\n}\n\n/** The query-string parameter a state key mirrors to (`cart:items` → `cart.items`). */\nconst urlParam = (/** @type {string} */ key) => key.replace(\":\", \".\");\n\n/**\n * Claim every seed `<script>` in the LIVE tree: apply the declared value for any\n * key state does not already hold, record it as that key's reset seed, then let\n * localStorage and the query string override it in that order.\n *\n * Boot runs this over the document; `scan()` runs it again whenever a render\n * changes structure. That second call is the point: `querySelectorAll` cannot\n * see into `<template>` content, so a `:state`/`:store`/`:theme` declared inside\n * an unopened `:if` branch was never hydrated at all — it started undefined and\n * never persisted. Re-opening a branch injects FRESH script nodes (the branch is\n * cloned from a template each time), so the guard here is \"state does not have\n * this key yet\", not a per-node claim: re-opening a branch must not reset what\n * the reader already changed.\n * @returns {void}\n */\nfunction hydrate() {\n for (const script of document.querySelectorAll(\"script[data-wd-state],script[data-wd-store]\")) {\n const store = script.getAttribute(\"data-wd-store\");\n const seed = JSON.parse(script.textContent || \"null\");\n for (const [key, value] of store ? [[store, seed]] : Object.entries(seed || {})) {\n if (key in state) continue;\n // The seed is the reset baseline, so state gets a COPY: mutating a list\n // through an action must not rewrite what `reset` restores.\n state[key] = structuredClone((initials[key] = value));\n const durable = store ? !script.hasAttribute(\"data-wd-store-ephemeral\") : script.getAttribute(\"data-wd-persist\") === key;\n if (durable) {\n (store ? storeKeys : persistKeys).add(key);\n const lsKey = (store ? \"wd:store:\" : \"wd:\") + key;\n const stored = ls(lsKey);\n // A corrupt entry self-heals: drop it and keep the declared seed, rather\n // than re-reading the same garbage on every load. A :store with nothing\n // stored yet writes its seed so other tabs see it.\n if (stored === null) { if (store) ls(lsKey, JSON.stringify(value ?? null)); }\n else if (jsonOr(stored) === undefined) ls(lsKey, null);\n else state[key] = jsonOr(stored);\n }\n // `from-url` outranks both: a shared link must show what it says.\n if (script.getAttribute(\"data-wd-url\") === key) {\n urlKeys.add(key);\n readUrl(false, [key]);\n }\n }\n }\n}\n\n/**\n * Mirror every `from-url` key into the address bar. A value equal to its\n * declared seed DROPS its parameter, so the default page keeps a clean URL, and\n * the write is `replaceState`, so filtering never fills the back button.\n * @returns {void}\n */\nfunction syncUrl() {\n if (!urlKeys.size) return;\n const params = new URLSearchParams(location.search);\n for (const key of urlKeys) {\n const value = state[key];\n if (JSON.stringify(value ?? null) === JSON.stringify(initials[key] ?? null)) params.delete(urlParam(key));\n else params.set(urlParam(key), typeof value === \"string\" ? value : JSON.stringify(value));\n }\n const query = params.toString();\n const next = location.pathname + (query ? `?${query}` : \"\") + location.hash;\n if (next === location.pathname + location.search + location.hash) return;\n // about:srcdoc (the playground's preview iframe) answers replaceState with a\n // SecurityError. This call sits between the state write and the render in\n // every handler, so an unguarded throw froze the preview on the first\n // keystroke. Like `ls`, URL sync is best-effort and never blocks a paint.\n try { history.replaceState(history.state, \"\", next); } catch { /* history is not writable here */ }\n}\n\n/**\n * Re-read every `from-url` key from the address bar. `restore` is true for a\n * popstate, where a parameter that is GONE means \"back to the declared value\";\n * it is false while hydrating, where an absent parameter must leave the\n * persisted value the storage read just restored.\n * `only` narrows the read to the keys just claimed by `hydrate`: re-reading the\n * WHOLE set there would revert a write the page has already made but not yet\n * mirrored, which is exactly what happens when an `:effect` writes a `from-url`\n * key and the same action opens a branch declaring another one.\n * @param {boolean} [restore]\n * @param {string[]} [only]\n * @returns {void}\n */\nfunction readUrl(restore, only) {\n const params = new URLSearchParams(location.search);\n for (const key of only || urlKeys) {\n const raw = params.get(urlParam(key));\n const seed = initials[key];\n if (raw == null) { if (restore) state[key] = structuredClone(seed); }\n else state[key] = typeof seed === \"string\" ? raw : jsonOr(raw) ?? raw;\n }\n}\n\nfunction savePersisted() {\n for (const key of persistKeys) ls(`wd:${key}`, JSON.stringify(state[key] ?? null));\n for (const name of storeKeys) ls(`wd:store:${name}`, JSON.stringify(state[name] ?? null));\n syncUrl();\n}\n\nhydrate();\n\n// Back/forward across `replaceState` history entries, and any other navigation\n// that changes the query string without a reload.\nwindow.addEventListener(\"popstate\", () => {\n if (!urlKeys.size) return;\n readUrl(true);\n // Storage follows the URL. Boot reads URL > storage > seed, so leaving a\n // persisted value behind after a back to a parameter-less URL would show the\n // seed now and flip back to the stored value on the next reload.\n savePersisted();\n render();\n});\n\nwindow.addEventListener(\"storage\", (event) => {\n const name = event.key && event.key.startsWith(\"wd:store:\") ? event.key.slice(9) : \"\";\n if (!storeKeys.has(name) || event.newValue == null) return;\n const next = jsonOr(event.newValue);\n if (next === undefined) return;\n if (JSON.stringify(next) === JSON.stringify(state[name])) return;\n state[name] = next;\n render();\n});\n\n/**\n * True for prototype-pollution path segments that must never be read or written.\n * Single source of truth for the rejected-key list (used by getPath + setPath).\n * @param {string} segment\n * @returns {boolean}\n */\nconst unsafeKey = (segment) => segment === \"__proto__\" || segment === \"constructor\" || segment === \"prototype\";\n\n/**\n * Safely read a dotted path off a value, rejecting prototype-pollution segments.\n * @param {any} value\n * @param {string | null} path\n * @returns {any}\n */\nfunction getPath(value, path) {\n if (!path) return value;\n let current = value;\n for (const segment of path.split(\".\")) {\n if (current == null) return undefined;\n if (unsafeKey(segment)) return undefined;\n current = current[segment];\n }\n return current;\n}\n\n/**\n * Safely write a value at a dotted path, creating plain objects for missing\n * intermediates and rejecting prototype-pollution segments at every level.\n * @param {any} obj\n * @param {string} path\n * @param {any} value\n * @returns {void}\n */\nfunction setPath(obj, path, value) {\n const segs = path.split(\".\");\n const last = segs.pop() || \"\";\n let cur = obj;\n for (const seg of segs) {\n if (unsafeKey(seg)) return;\n if (cur[seg] == null || typeof cur[seg] !== \"object\") cur[seg] = {};\n cur = cur[seg];\n }\n if (unsafeKey(last)) return;\n cur[last] = value;\n}\n\n/**\n * Empty for fetch purposes: null/undefined, an empty array, or an object with\n * no own keys. Drives the `name_empty` lifecycle flag.\n * @param {any} v\n * @returns {boolean}\n */\nfunction isEmpty(v) {\n return v == null || (typeof v === \"object\" && (Array.isArray(v) ? v.length : Object.keys(v).length) === 0);\n}\n\n/**\n * Parse JSON, or undefined when the text is not JSON. `undefined` is a safe\n * sentinel: no JSON document parses to it, so a body of literal `null` is still\n * distinguishable from \"not JSON at all\".\n *\n * Every UNTRUSTED document (a fetch body, a `from-url` param, localStorage, a\n * cross-tab storage event) enters through here. The reviver drops own\n * `toString`/`valueOf` keys: `{\"toString\":1}` is an object `String()` cannot\n * convert, so it would throw on every render that paints it, and a persisted\n * copy would keep the page dead after a reload of the clean URL.\n * @param {string} text\n * @returns {any}\n */\nfunction jsonOr(text) {\n try { return JSON.parse(text, (k, v) => (k === \"toString\" || k === \"valueOf\" ? undefined : v)); } catch { return undefined; }\n}\n\n// The characters a value must not carry into a URL, mirroring `DEST_UNSAFE`\n// in src/compiler/markdown.js: an unencoded space or paren makes the paint and\n// the re-render disagree, and `tests/attr-binding.test.js` pins the two together.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — a control character in a URL has to be encoded, not passed through.\nconst URL_UNSAFE = /[\\u0000-\\u0020()<>\"'`\\\\\\u007F]/g;\n\n/**\n * Every node matching `sel` at or under `node` — the self-match matters because\n * a loop row's own element can carry the marker, not just its descendants.\n * @param {Element} node\n * @param {string} sel\n * @returns {Element[]}\n */\nconst selfAnd = (node, sel) => (node.matches(sel) ? [node, ...node.querySelectorAll(sel)] : [...node.querySelectorAll(sel)]);\n\n/**\n * Repaint one bound attribute (a markdown link/image destination that read\n * `:state`, `:computed`, or a loop row). The marker is `[attr, …parts]`, where a\n * part is either a literal string or a reader: `[\"s\", key, path]` for state,\n * `[\"m\", name]` for a row meta variable, `[\"i\", path]` for the row itself.\n *\n * The scheme guard is the load-bearing half. The compiler vets URLs the AUTHOR\n * wrote; the value arriving here came from state, a form field, or a fetched\n * payload, and nothing upstream has seen it. Control characters are stripped\n * before the test because a browser strips them out of a URL before resolving\n * it, so `java<TAB>script:` is a live javascript: URL. An unsafe value is not\n * applied — the attribute is emptied instead. Mirrored by `unsafeUrlValue` in\n * src/compiler/interpolation.js for the build-time paint.\n * @param {Element} el\n * @param {string | null} raw\n * @param {any} item Loop row, or undefined outside one.\n * @param {Record<string, any>} meta Per-row meta values.\n * @returns {void}\n */\nfunction applyAttr(el, raw, item, meta) {\n if (!raw) return;\n let parts;\n try { parts = JSON.parse(raw); } catch { return; }\n let out = \"\"; // encoded: what lands on the attribute\n let plain = \"\"; // raw: what the scheme guard tests\n for (let i = 1; i < parts.length; i++) {\n const p = parts[i];\n // A literal chunk is the author's own URL text, already vetted by the\n // compiler; only a READER value is encoded, and with exactly the character\n // set `encodeDest` uses in src/compiler/markdown.js, or the build-time paint\n // and the first re-render would disagree on every value holding a space.\n if (typeof p === \"string\") { out += p; plain += p; continue; }\n const v = String((p[0] === \"s\" ? getPath(state[p[1]], p[2]) : p[0] === \"m\" ? meta[p[1]] : getPath(item, p[1])) ?? \"\");\n plain += v;\n out += v.replace(URL_UNSAFE, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`);\n }\n // Defense in depth: the compiler only ever emits href (a link) or src (an\n // image), so anything else in a marker did not come from a compile.\n if (parts[0] !== \"href\" && parts[0] !== \"src\") return;\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — a browser strips these out of a URL, so the guard must too.\n el.setAttribute(parts[0], /^(javascript|data|vbscript):/i.test(plain.replace(/[\\u0000-\\u0020\\u007F]+/g, \"\")) ? \"\" : out);\n}\n\n// --- Format pipes -----------------------------------------------------------\n// A compact mirror of src/compiler/format.js: the same formatter math, so a\n// reactive `{ value | money }` re-formats in the browser exactly as the static\n// fold did at build time. tests/format-parity.test.js guards the two against\n// drift. Pure functions of (value, args) — no clock, no DOM — Intl does the work.\n\n/** @param {any} v @returns {number} */\nconst fmtNum = (v) => { const n = typeof v === \"number\" ? v : Number.parseFloat(v); return Number.isFinite(n) ? n : 0; };\n/** @param {any} v @returns {any[]} */\nconst fmtList = (v) => (Array.isArray(v) ? v : v == null ? [] : [v]);\n/** @param {any} row @param {string} [f] */\nconst fmtPick = (row, f) => (f != null && f !== \"\" ? (row == null ? undefined : row[f]) : row);\n/** @param {any[]} list @param {string} [f] */\nconst fmtNums = (list, f) => list.map((r) => fmtNum(fmtPick(r, f)));\n/** @param {any} v @returns {Date} */\nconst fmtToDate = (v) => {\n if (v instanceof Date) return v;\n if (typeof v === \"number\") return new Date(v);\n const s = String(v).trim();\n return /^-?\\d+$/.test(s) ? new Date(Number(s)) : new Date(s);\n};\n/** @param {Date} d @param {any} opts @param {any} fb */\nconst fmtDate = (d, opts, fb) => (Number.isNaN(d.getTime()) ? String(fb) : new Intl.DateTimeFormat(undefined, opts).format(d));\n/** Date-only strings (no time) format in UTC so the calendar date is stable. @param {any} v */\nconst fmtTZ = (v) => (typeof v === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(v.trim()) ? { timeZone: \"UTC\" } : undefined);\n\n/** @type {Record<string, (value: any, args: any[]) => any>} */\nconst FMT = {\n money: (v, [c = \"USD\", l]) => new Intl.NumberFormat(l || undefined, { style: \"currency\", currency: String(c || \"USD\") }).format(fmtNum(v)),\n number: (v, [d]) => new Intl.NumberFormat(undefined, d == null ? {} : { minimumFractionDigits: +d, maximumFractionDigits: +d }).format(fmtNum(v)),\n percent: (v, [d = 0]) => new Intl.NumberFormat(undefined, { style: \"percent\", minimumFractionDigits: +d, maximumFractionDigits: +d }).format(fmtNum(v)),\n round: (v, [d = 0]) => { const p = 10 ** +d; return Math.round(fmtNum(v) * p) / p; },\n date: (v, [s = \"medium\"]) => fmtDate(fmtToDate(v), { dateStyle: s, ...fmtTZ(v) }, v),\n time: (v, [s = \"short\"]) => fmtDate(fmtToDate(v), { timeStyle: s, ...fmtTZ(v) }, v),\n datetime: (v, [ds = \"medium\", ts = \"short\"]) => fmtDate(fmtToDate(v), { dateStyle: ds, timeStyle: ts, ...fmtTZ(v) }, v),\n upper: (v) => String(v ?? \"\").toUpperCase(),\n lower: (v) => String(v ?? \"\").toLowerCase(),\n capitalize: (v) => { const s = String(v ?? \"\"); return s ? s[0].toUpperCase() + s.slice(1) : s; },\n truncate: (v, [n = 50, suf = \"…\"]) => { const s = String(v ?? \"\"); return s.length > +n ? s.slice(0, +n).trimEnd() + String(suf) : s; },\n trim: (v) => String(v ?? \"\").trim(),\n pluralize: (v, [sg = \"\", pl]) => { const n = fmtNum(v); return `${new Intl.NumberFormat().format(n)} ${Math.abs(n) === 1 ? String(sg) : pl != null ? String(pl) : `${sg}s`}`; },\n default: (v, [fb = \"\"]) => (v == null || v === \"\" || (Array.isArray(v) && !v.length) ? String(fb) : v),\n sum: (v, [f]) => fmtNums(fmtList(v), f).reduce((a, n) => a + n, 0),\n avg: (v, [f]) => { const l = fmtList(v); return l.length ? FMT.sum(l, [f]) / l.length : 0; },\n // reduce, not spread: `Math.max(...list)` throws RangeError past ~100k items.\n min: (v, [f]) => { const l = fmtList(v); return l.length ? fmtNums(l, f).reduce((a, n) => Math.min(a, n)) : 0; },\n max: (v, [f]) => { const l = fmtList(v); return l.length ? fmtNums(l, f).reduce((a, n) => Math.max(a, n)) : 0; },\n count: (v) => fmtList(v).length,\n join: (v, [sep = \", \", f]) => fmtList(v).map((r) => fmtPick(r, f)).join(String(sep))\n};\n\n/**\n * Run a node's `data-wd-fmt` pipe chain over a resolved value; identity when the\n * node carries no chain. Shared by text binds, loop-row binds, and meta markers.\n * @param {Element} node\n * @param {any} value\n * @returns {any}\n */\nfunction applyFmt(node, value) {\n const raw = node.getAttribute(\"data-wd-fmt\");\n if (!raw) return value;\n let stages;\n try { stages = JSON.parse(raw); } catch { return value; }\n let out = value;\n for (const [name, args] of stages) { if (FMT[name]) out = FMT[name](out, args || []); }\n return out;\n}\n\n/** Aggregate helper exposed to `:computed` as `A(\"sum\", list, \"field\")`. */\nconst AGG = (/** @type {string} */ name, /** @type {any} */ list, /** @type {string} */ field) =>\n FMT[name] ? FMT[name](list, field == null ? [] : [field]) : undefined;\n\n/**\n * Shared HTTP→JSON core for `:fetch` and the round-trip `:form`. Sends the\n * request, throws on non-2xx, and parses the body as JSON (falling back to a\n * `{status,body}` wrapper for non-JSON responses). Unifying both callers keeps\n * the runtime under budget.\n * @param {string} url\n * @param {RequestInit} [init]\n * @returns {Promise<any>}\n */\nasync function httpJson(url, init) {\n const response = await fetch(url, init);\n const text = await response.text();\n const body = jsonOr(text);\n if (!response.ok) {\n // A real API says WHY it refused. Prefer the body's own `error`/`message`\n // string over the bare status line, and carry the parsed body along so a\n // page can render field-level errors from `<name>_error_body`.\n // The first NON-EMPTY string of the two: `{ \"error\": \"\", \"message\": \"…\" }`\n // is a real shape, and `??` would keep the empty string and render \"Error\".\n const said = body && [body.error, body.message].find((v) => typeof v === \"string\" && v);\n const err = /** @type {Error & { status?: number, body?: any }} */ (new Error(said || `HTTP ${response.status}`));\n err.status = response.status; // lets the :fetch lifecycle single out a 401 for token refresh\n err.body = body ?? null;\n throw err;\n }\n return body === undefined ? { status: response.status, body: text } : body;\n}\n\n/**\n * Write the `<name>_error` / `<name>_error_body` pair for a key: the server's\n * own message and its parsed JSON body, or null/null when there is no failure.\n * @param {string} key\n * @param {any} [error]\n * @returns {void}\n */\nfunction setError(key, error) {\n state[key + \"_error\"] = error ? error.message || String(error) : null;\n state[key + \"_error_body\"] = (error && error.body) ?? null;\n}\n\n/**\n * Bump a node's generation token and hand back a staleness probe. An in-flight\n * request that a newer one superseded must never write its result. Shared by the\n * `:fetch` lifecycle and the round-trip `:form` submit.\n * @param {{ __wdGen?: number }} node\n * @returns {() => boolean}\n */\nfunction genGuard(node) {\n const gen = (node.__wdGen = (node.__wdGen || 0) + 1);\n return () => gen !== node.__wdGen;\n}\n\n/**\n * Set whenever a render pass changes STRUCTURE — an `:if` branch injected, a loop\n * row cloned, an `@empty` branch filled. It buys one more pass (the document-wide\n * queries must reach the new nodes) and one more `scan()` (the new nodes may be\n * directives no query had ever seen). True at boot: nothing is registered yet.\n */\nlet dirty = true;\n\n/**\n * Log an expression failure when `window.wd.debug` is enabled.\n * @param {unknown} expr\n * @param {unknown} error\n * @returns {void}\n */\nfunction warn(expr, error) {\n const wd = /** @type {any} */ (window).wd;\n if (wd && wd.debug) console.warn(`[wd] expression failed: ${expr}`, error);\n}\n\n/**\n * Re-evaluate every `:computed` marker in the live tree into `state`. Read from\n * the DOM rather than a registry captured at load: `querySelectorAll` cannot see\n * into `<template>` content, so a registry would permanently miss a `:computed`\n * that only becomes live when an `:if` branch opens. `parseAst` caches by the raw\n * attribute text, so re-reading costs an attribute read, not a re-parse.\n * @returns {void}\n */\nfunction recompute() {\n for (const node of document.querySelectorAll(\"[data-wd-computed]\")) {\n const key = node.getAttribute(\"data-wd-computed-key\") || \"\";\n const expr = node.getAttribute(\"data-wd-computed-expr\") || \"null\";\n try {\n state[key] = evalAst(parseAst(expr), undefined);\n } catch (error) {\n state[key] = undefined;\n warn(expr, error);\n }\n }\n}\n\n/** @param {unknown} a @param {unknown} b @returns {boolean} */\nconst containsFn = (a, b) => String(a ?? \"\").toLowerCase().includes(String(b ?? \"\").toLowerCase());\n\n/**\n * Evaluate a compact expression AST (see src/compiler/expr-ast.js) against the\n * live state and an optional loop `item`. The compiler emits this serialized AST\n * into the `data-wd-*` attributes; the runtime WALKS it — no `new Function`, so\n * reactive pages run under a strict CSP with no `'unsafe-eval'`. The op vocabulary\n * is closed: every node tag is one of the readers (L/S/I/C/A), a unary (!/u-), or\n * a binary operator, and an unknown tag is a hard error, never a fallthrough.\n * Prototype-pollution segments are rejected by getPath, exactly as before.\n * @param {any[]} node\n * @param {any} item Loop row for I(); undefined otherwise.\n * @returns {any}\n */\nfunction evalAst(node, item) {\n /** @param {any[]} n @returns {any} */\n const ev = (n) => {\n switch (n[0]) {\n case \"L\": return n[1];\n case \"S\": return getPath(state[n[1]], n[2] ?? null);\n case \"I\": return getPath(item, n[1] ?? null);\n case \"C\": return containsFn(ev(n[1]), ev(n[2]));\n case \"A\": return AGG(n[1], ev(n[2]), n[3]);\n case \"!\": return !ev(n[1]);\n case \"u-\": return -ev(n[1]);\n case \"&&\": return ev(n[1]) && ev(n[2]);\n case \"||\": return ev(n[1]) || ev(n[2]);\n // biome-ignore lint/suspicious/noDoubleEquals: loose equality preserves prior `new Function` semantics exactly.\n case \"==\": return ev(n[1]) == ev(n[2]);\n // biome-ignore lint/suspicious/noDoubleEquals: loose inequality preserves prior `new Function` semantics exactly.\n case \"!=\": return ev(n[1]) != ev(n[2]);\n case \">\": return ev(n[1]) > ev(n[2]);\n case \"<\": return ev(n[1]) < ev(n[2]);\n case \">=\": return ev(n[1]) >= ev(n[2]);\n case \"<=\": return ev(n[1]) <= ev(n[2]);\n case \"+\": return ev(n[1]) + ev(n[2]);\n case \"-\": return ev(n[1]) - ev(n[2]);\n case \"*\": return ev(n[1]) * ev(n[2]);\n case \"/\": return ev(n[1]) / ev(n[2]);\n case \"%\": return ev(n[1]) % ev(n[2]);\n }\n throw new Error(`wd: unknown op ${n[0]}`);\n };\n return ev(node);\n}\n\n/** @type {Map<string, any[]>} Parsed-AST cache keyed by the raw attribute JSON. */\nconst astCache = new Map();\n/** @param {string} raw @returns {any[]} */\nfunction parseAst(raw) {\n const cached = astCache.get(raw);\n if (cached) return cached;\n const ast = JSON.parse(raw);\n astCache.set(raw, ast);\n return ast;\n}\n\n/**\n * Evaluate an expression AST to a boolean, swallowing failures (logs under\n * `wd.debug`). Shared by reactive classes (`.class when`), loop `where`, and\n * expression conditionals (`:if a > b`).\n * @param {any[]} ast\n * @param {any} item Loop row for per-row predicates, else undefined.\n * @returns {boolean}\n */\nfunction evalPredicate(ast, item) {\n try { return Boolean(evalAst(ast, item)); }\n catch (error) { warn(ast, error); return false; }\n}\n\n/**\n * Toggle reactive `.class when <predicate>` bindings on an element. `raw` is the\n * JSON `[[name, ast], …]` from data-wd-class (global, item undefined) or\n * data-wd-each-class (per loop row); each ast is the same expression AST the loop\n * `where` uses.\n * @param {Element} el\n * @param {string | null} raw\n * @param {any} item Loop item for each-class; undefined for a global class.\n * @returns {void}\n */\nfunction classToggle(el, raw, item) {\n if (!raw) return;\n /** @type {[string, any[]][]} */\n let pairs;\n try { pairs = JSON.parse(raw); } catch { return; }\n for (const [name, ast] of pairs) el.classList.toggle(name, evalPredicate(ast, item));\n}\n\n/**\n * Stable per-render key for a loop row, disambiguating duplicates with `#n`.\n * @param {any} item\n * @param {Map<string, number>} counts\n * @returns {string}\n */\nfunction loopKeyOf(item, counts) {\n // Every literal `#` doubles, so the `#n` duplicate suffix lives in a namespace\n // no real key can reach: a genuine \"a#1\" keys as \"a##1\", never as a's duplicate.\n const base = (\n item && typeof item === \"object\"\n ? String(item.id ?? item.key ?? JSON.stringify(item))\n : String(item)\n ).replace(/#/g, \"##\");\n const seen = counts.get(base) || 0;\n counts.set(base, seen + 1);\n return seen ? `${base}#${seen}` : base;\n}\n\n/**\n * Fill a cloned loop row node: resolve per-item `:if` regions (item- and\n * meta-driven), text binds, and per-row meta markers.\n * @param {Element} node\n * @param {any} item\n * @param {Record<string, any>} [meta] Per-row meta values (index/number/…).\n * @returns {void}\n */\nfunction fillItem(node, item, meta = {}) {\n // Nested item-relative loops (data-wd-loop-item) own their descendants' binds;\n // when present, `skip` excludes a node living inside one so the outer row's\n // text/meta/each-if/class passes never overwrite an inner row's values.\n let inners = node.querySelectorAll(\"[data-wd-loop-item]\");\n const skip = (/** @type {Element} */ el) => inners.length && el.closest(\"[data-wd-loop-item]\");\n // querySelectorAll skips <template> content, so this sees only the outermost\n // regions; recursing into each injected branch fills the rest.\n for (const region of node.querySelectorAll(\"[data-wd-each-if]\")) {\n if (skip(region)) continue;\n const expr = region.getAttribute(\"data-wd-if-expr\");\n const m = region.getAttribute(\"data-wd-meta\");\n const value = expr ? evalPredicate(parseAst(expr), item) : (m ? meta[m] : getPath(item, region.getAttribute(\"data-wd-path\")));\n const output = region.querySelector(\"[data-wd-each-if-out]\");\n if (!output) continue;\n // swapBranch carries the same skip-when-unchanged guard the global :if has:\n // re-injecting an unchanged branch on every render would destroy whatever is\n // live inside it — a nested loop's reconciled rows, focus, a playing <video>.\n swapBranch(region, output, value, \"wd-if\");\n fillItem(output, item, meta);\n }\n // An each-if branch can REVEAL a nested loop, and the capture above predates\n // the injection — re-read it, or the passes below overwrite the inner rows\n // with the OUTER row's values.\n inners = node.querySelectorAll(\"[data-wd-loop-item]\");\n for (const target of selfAnd(node, \"[data-wd-each]\")) {\n if (skip(target)) continue;\n target.textContent = applyFmt(target, getPath(item, target.getAttribute(\"data-wd-path\"))) ?? \"\";\n }\n for (const marker of node.querySelectorAll(\"[data-wd-each-meta]\")) {\n if (skip(marker)) continue;\n marker.textContent = applyFmt(marker, meta[marker.getAttribute(\"data-wd-each-meta\") || \"\"]) ?? \"\";\n }\n for (const el of selfAnd(node, \"[data-wd-each-class]\")) { if (!skip(el)) classToggle(el, el.getAttribute(\"data-wd-each-class\"), item); }\n // Per-row bound attributes (a link/image destination reading the row). State\n // parts resolve here too, so a destination mixing the two stays correct.\n for (const el of selfAnd(node, \"[data-wd-each-attr]\")) { if (!skip(el)) applyAttr(el, el.getAttribute(\"data-wd-each-attr\"), item, meta); }\n // Reconcile nested loops last, against the now-settled outer row.\n for (const region of inners) reconcile(region, getPath(item, region.getAttribute(\"data-wd-loop-item\")));\n}\n\n/**\n * Read a loop offset/limit attribute that is a literal int or a `key:<name>`\n * referencing state. Returns null when the attribute is absent.\n * @param {Element} region\n * @param {string} name\n * @returns {number | null}\n */\nfunction loopNum(region, name) {\n const raw = region.getAttribute(name);\n if (raw == null) return null;\n return raw.startsWith(\"key:\") ? Number(state[raw.slice(4)] ?? 0) : Number(raw);\n}\n\n/**\n * Apply sort → reverse → offset → limit to an already-filtered list, reading\n * clause config off the region. Stable; numeric vs localeCompare comparator.\n * @param {any[]} list\n * @param {Element} region\n * @returns {any[]}\n */\nfunction pipeline(list, region) {\n const sortAttr = region.getAttribute(\"data-wd-loop-sort\");\n if (sortAttr != null) {\n // `key:<name>` reads the sort field / direction from state (reactive,\n // clickable-header sort); a plain value is the literal `sort by` clause.\n const reactive = sortAttr.startsWith(\"key:\");\n const sort = reactive ? String(state[sortAttr.slice(4)] ?? \"\") : sortAttr;\n // A reactive sort with no column chosen yet (state is \"\") leaves the order.\n if (!reactive || sort) {\n let dirAttr = region.getAttribute(\"data-wd-loop-sort-dir\") || \"asc\";\n if (dirAttr.startsWith(\"key:\")) dirAttr = String(state[dirAttr.slice(4)] ?? \"asc\");\n const dir = dirAttr === \"desc\" ? -1 : 1;\n list = list.map((value, index) => ({ value, index })).sort((a, b) => {\n const av = getPath(a.value, sort || null);\n const bv = getPath(b.value, sort || null);\n const c = typeof av === \"number\" && typeof bv === \"number\" ? av - bv : String(av).localeCompare(String(bv));\n return c ? c * dir : a.index - b.index;\n }).map((w) => w.value);\n }\n }\n if (region.hasAttribute(\"data-wd-loop-reverse\")) list.reverse();\n const off = loopNum(region, \"data-wd-loop-offset\");\n if (off) list = list.slice(off);\n const lim = loopNum(region, \"data-wd-loop-limit\");\n if (lim != null) list = list.slice(0, lim);\n return list;\n}\n\n/**\n * Swap a conditional region to the branch its truthiness selects, and report\n * whether anything actually moved. The unchanged-guard is the load-bearing part:\n * re-injecting the branch that is already showing would replace every live node\n * inside it on every render. One implementation for both conditionals — the\n * global `:if` (`prefix` \"wd\", templates `data-wd-true`/`-false`) and the per-row\n * `:if` inside a loop row (\"wd-if\", `data-wd-if-true`/`-false`).\n * @param {Element} region Carries data-wd-if-active, the last painted branch.\n * @param {Element} output The node whose contents the branch replaces.\n * @param {any} value\n * @param {string} prefix\n * @returns {boolean} true when the branch was swapped\n */\nfunction swapBranch(region, output, value, prefix) {\n const active = String(Boolean(value));\n if (region.getAttribute(\"data-wd-if-active\") === active) return false;\n region.setAttribute(\"data-wd-if-active\", active);\n const template = /** @type {HTMLTemplateElement | null} */ (region.querySelector(`template[data-${prefix}-${active}]`));\n unregister(output); // the branch being replaced takes its directives with it\n output.innerHTML = template?.innerHTML || \"\";\n // The branch may carry directives no query has ever seen.\n return (dirty = true);\n}\n\n/**\n * Evaluate one global `data-wd-if` region against current state and swap its\n * branch when the truthiness flips. `data-wd-if-expr` carries a compiled\n * predicate (richer `:if`); otherwise it is a bare-path truthiness test. On a\n * swap, the freshly injected branch can carry nested if-regions (a desugared\n * `:else if`) that were never in the live tree this pass — querySelectorAll\n * skips <template> content — so recurse to fill them. Unchanged regions are\n * left alone; their already-live nested regions are reached by renderNow's\n * document-wide query.\n * @param {Element} node\n * @returns {void}\n */\nfunction renderIf(node) {\n const expr = node.getAttribute(\"data-wd-if-expr\");\n const value = expr ? evalPredicate(parseAst(expr), undefined) : getPath(state[node.getAttribute(\"data-wd-if\") || \"\"], node.getAttribute(\"data-wd-path\"));\n const output = node.querySelector(\"[data-wd-if-out]\");\n if (!output || !swapBranch(node, output, value, \"wd\")) return;\n for (const nested of output.querySelectorAll(\"[data-wd-if]\")) renderIf(nested);\n}\n\n/**\n * Reconcile one loop region (top-level or nested) from a resolved row source:\n * filter (`where`) → pipeline (sort/reverse/offset/limit) → @empty → keyed\n * reconcile. Each surviving row is `fillItem`-ed, which itself recurses into any\n * nested item-relative loops. Shared by the global pass and `fillItem`.\n * @param {Element} region\n * @param {any} rows Source list (state-resolved globally; item-relative inside fillItem).\n * @returns {void}\n */\nfunction reconcile(region, rows) {\n const template = /** @type {HTMLTemplateElement | null} */ (region.querySelector(\"template[data-wd-loop-template]\"));\n const out = region.querySelector(\"[data-wd-loop-out]\");\n if (!template || !out) return;\n let list = Array.isArray(rows) ? rows.slice() : [];\n const where = region.getAttribute(\"data-wd-loop-where\");\n if (where) {\n const ast = parseAst(where);\n list = list.filter((/** @type {any} */ item) => evalPredicate(ast, item));\n }\n list = pipeline(list, region);\n\n // Empty branch: clone the [data-wd-loop-empty] template into the output.\n const emptyTpl = /** @type {HTMLTemplateElement | null} */ (region.querySelector(\"template[data-wd-loop-empty]\"));\n if (!list.length && emptyTpl) {\n if (out.getAttribute(\"data-wd-empty\") !== \"1\") {\n unregister(out); // the rows being wiped take their directives with them\n out.textContent = \"\";\n for (const child of [...emptyTpl.content.children]) out.appendChild(child.cloneNode(true));\n out.setAttribute(\"data-wd-empty\", \"1\");\n dirty = true;\n }\n return;\n }\n // The @empty branch is ONE branch, not a per-row clone, so it may legally hold\n // an :every/:effect. Emptying and refilling the list re-clones it, so the copy\n // leaving here must be dropped or the next empty state would tick twice.\n if (out.getAttribute(\"data-wd-empty\") === \"1\") { unregister(out); out.textContent = \"\"; out.removeAttribute(\"data-wd-empty\"); }\n\n /** @type {Map<string, WdRow>} */\n const existing = new Map();\n for (const child of [...out.children]) {\n existing.set(child.getAttribute(\"data-wd-loop-key\") || \"\", /** @type {WdRow} */ (child));\n }\n /** @type {Map<string, number>} */\n const counts = new Map();\n /** @type {Set<string>} */\n const used = new Set();\n const count = list.length;\n let moved = false;\n for (let i = 0; i < count; i++) {\n const item = list[i];\n const key = loopKeyOf(item, counts);\n let node = existing.get(key);\n if (!node || used.has(key)) {\n node = /** @type {WdRow} */ (template.content.firstElementChild?.cloneNode(true));\n if (!node) continue;\n node.setAttribute(\"data-wd-loop-key\", key);\n // A fresh row missed this pass's document-wide if/class/scan work.\n dirty = true;\n }\n used.add(key);\n fillItem(node, item, { index: i, number: i + 1, first: i === 0, last: i === count - 1, count });\n node.__wdItem = item; /** let per-row actions resolve which row was clicked */\n // Leave a row that is already in place ALONE. appendChild is a\n // remove-then-insert per spec, so appending unconditionally blurs the\n // focused field, resets scroll, restarts CSS transitions and reloads an\n // <iframe> in a row — on every render, even when the list did not change.\n // Once one row has had to move, every later row must be re-appended too, or\n // the ones left behind would sit in front of it.\n if (moved || out.children[i] !== node) { moved = true; out.appendChild(node); }\n }\n for (const [key, node] of existing) {\n if (!used.has(key)) { unregister(node); node.remove(); }\n }\n}\n\n/**\n * Synchronously render the whole document from current `state`:\n * (scan → computed → if-regions → reactive classes → keyed loop reconcile)\n * repeated until the structure settles → text/input binds → theme → effects.\n *\n * The repeat is the point. The if/class passes are document-wide queries, so a\n * row the loop reconcile CLONED during the same pass was not in the tree when\n * they ran and would keep the compile-time branch and classes baked into its\n * template — wrong until some unrelated state change. Symmetrically, a branch an\n * `:if` just injected can carry `:fetch`/`:computed`/`:effect`/`:every`/`:theme`\n * nodes that no query had ever seen. Either marks `dirty`, which buys one more\n * pass; two passes always suffice in practice and three is the hard cap.\n * @returns {void}\n */\nlet settleTries = 0;\nfunction renderNow() {\n for (let pass = 0; pass < 3; pass++) {\n if (dirty) scan();\n dirty = false;\n recompute();\n for (const node of document.querySelectorAll(\"[data-wd-if]\")) renderIf(node);\n\n // Reactive styling: toggle state-driven `.class when …` bindings. Loop-row\n // bindings (data-wd-each-class) are handled per item inside fillItem.\n for (const el of document.querySelectorAll(\"[data-wd-class]\")) classToggle(el, el.getAttribute(\"data-wd-class\"), undefined);\n\n // Nested item-relative loops use data-wd-loop-item, so this top-level query\n // skips them; fillItem reconciles those against their enclosing row.\n for (const region of document.querySelectorAll(\"[data-wd-loop]\")) {\n const key = region.getAttribute(\"data-wd-loop\");\n const data = region.getAttribute(\"data-wd-loop-data\");\n /** Source may be a dotted path (e.g. team.members) read off state via getPath. */\n const dot = key ? key.indexOf(\".\") : -1;\n reconcile(region, key ? (dot < 0 ? state[key] : getPath(state[key.slice(0, dot)], key.slice(dot + 1))) : (data ? JSON.parse(data) : []));\n }\n if (!dirty) break;\n }\n // Deeper than the cap (an `:if` whose branch declares the `:computed` a nested\n // `:if` reads, a few levels down) the loop used to exit still dirty and leave\n // the innermost region showing its compile-time branch until some unrelated\n // state change. Finish this paint, then schedule another pass; the counter\n // bounds a genuine cycle instead of spinning forever.\n if (dirty && ++settleTries < 10) {\n if (/** @type {any} */ (window).wd?.debug) console.warn(\"[wd] render did not settle; scheduling another pass\");\n render();\n } else settleTries = 0;\n\n for (const node of document.querySelectorAll(\"[data-wd-bind]\")) {\n node.textContent = applyFmt(node, getPath(state[node.getAttribute(\"data-wd-bind\") || \"\"], node.getAttribute(\"data-wd-path\"))) ?? \"\";\n }\n\n for (const node of document.querySelectorAll(\"[data-wd-attr]\")) applyAttr(node, node.getAttribute(\"data-wd-attr\"), undefined, {});\n\n // Bound controls, reflected back from state. A checkbox carries a boolean and\n // a radio carries whichever option equals the value, so neither can go through\n // `.value`; a text field keeps the focus guard, because overwriting what\n // someone is typing mid-keystroke moves their caret.\n for (const input of /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll(\"[data-wd-bind-input]\"))) {\n const value = state[input.getAttribute(\"data-wd-bind-input\") || \"\"] ?? \"\";\n if (input.type === \"checkbox\") input.checked = Boolean(value);\n else if (input.type === \"radio\") input.checked = input.value === String(value);\n else if (document.activeElement !== input) input.value = value;\n }\n\n reflectThemes();\n // side effects run last, against fully settled state\n runEffects();\n if (subscribers.size) notifySubscribers();\n}\n\n/** :theme — reflect a store value onto <html data-theme> so a manual light/dark\n * switch layers over the OS preference: `auto`/empty clears the attribute (follow\n * the system via the skin's `tokens dark` media query), anything else forces it. */\nfunction reflectThemes() {\n for (const node of document.querySelectorAll(\"[data-wd-theme]\")) {\n const value = state[node.getAttribute(\"data-wd-theme\") || \"\"];\n const root = document.documentElement;\n if (value == null || value === \"\" || value === \"auto\") root.removeAttribute(\"data-theme\");\n else root.setAttribute(\"data-theme\", String(value));\n }\n}\n\n/** @typedef {{ node: Element, watch: string, actions: { op: string, target?: string, value?: any }[], last: string }} Effect */\n/** @type {Effect[]} */\nconst effects = [];\nlet effectDepth = 0;\n/**\n * After a render, fire any `:effect` whose watched state changed, running its\n * actions through the shared action runner. If that mutates state, re-render and\n * re-check; a settle cap of 10 passes guards against effect→effect loops.\n * @returns {void}\n */\nfunction runEffects() {\n if (!effects.length) return;\n let changed = false;\n for (const fx of effects) {\n const now = JSON.stringify(getPath(state, fx.watch) ?? null);\n if (now === fx.last) continue;\n fx.last = now;\n runActions(fx.actions);\n changed = true;\n }\n if (!changed) { effectDepth = 0; return; }\n // An effect writes state exactly like a click does, so it persists and syncs\n // to the URL exactly like a click does. Without this an `:effect` that writes\n // a `from-url` key never reached the address bar at all.\n savePersisted();\n if (++effectDepth >= 10) { effectDepth = 0; console.warn(\"effect did not settle\"); return; }\n renderNow(); // reflect the effect's state changes, then re-check\n}\n\n/**\n * Coalesce rapid state mutations into one render on the next tick. Each scheduled\n * pass always reads the latest state, so the final update is never dropped.\n */\nconst schedule = typeof requestAnimationFrame === \"function\" ? requestAnimationFrame : queueMicrotask;\nlet scheduled = false;\nfunction render() {\n if (scheduled) return;\n scheduled = true;\n schedule(() => {\n scheduled = false;\n renderNow();\n });\n}\n\ndocument.addEventListener(\"input\", (event) => {\n const input = /** @type {HTMLInputElement | null} */ (/** @type {Element} */ (event.target)?.closest(\"[data-wd-bind-input]\"));\n if (!input) return;\n // A `:slider` binds a range input: keep state numeric so `:computed`/math see a\n // number, not the DOM's string `.value`. A standalone `:checkbox` binds the\n // boolean, not the label. Everything else stays a string, as before.\n state[input.getAttribute(\"data-wd-bind-input\") || \"\"] = input.type === \"range\" ? input.valueAsNumber : input.type === \"checkbox\" ? input.checked : input.value;\n savePersisted();\n render();\n checkRefetch();\n});\n\n/**\n * Resolve the loop source and row item for a clicked element, if inside a loop.\n * The clicked button's row is the nearest reconciled loop node; it carries its item.\n * @param {Element | null} el Null for effect-driven actions (no clicked row).\n * @returns {{ srcKey: string | null, item: any } | null}\n */\nfunction clickedRow(el) {\n if (!el) return null; // effect-driven actions have no clicked row → row ops no-op\n const row = /** @type {WdRow | null} */ (el.closest(\"[data-wd-loop-key]\"));\n const region = el.closest(\"[data-wd-loop]\");\n if (!row || !region) return null;\n return { srcKey: region.getAttribute(\"data-wd-loop\"), item: row.__wdItem };\n}\n\n/**\n * Apply one parsed action `{op,target,value}` against state. Targets are dotted\n * paths read via getPath / written via setPath; a bare name is a 1-segment path.\n * @param {Element | null} action The clicked element (for loop-row resolution); null for effects.\n * @param {string} op\n * @param {string} target\n * @param {any} value\n * @returns {void}\n */\nfunction applyAction(action, op, target, value) {\n /** @param {any} v */\n const put = (v) => setPath(state, target, v);\n const cur = getPath(state, target);\n const arr = () => (Array.isArray(cur) ? cur : []);\n if (op === \"inc\") put(Number(cur ?? 0) + 1);\n if (op === \"dec\") put(Number(cur ?? 0) - 1);\n if (op === \"add\") put(Number(cur ?? 0) + Number(value));\n if (op === \"sub\") put(Number(cur ?? 0) - Number(value));\n if (op === \"toggle\") put(!cur);\n if (op === \"set\") put(value);\n if (op === \"append\") put([...arr(), value]);\n if (op === \"prepend\") put([value, ...arr()]);\n if (op === \"member-toggle\") put(arr().includes(value) ? arr().filter((/** @type {any} */ x) => x !== value) : [...arr(), value]);\n if (op === \"remove-value\") put(arr().filter((/** @type {any} */ x) => x !== value));\n if (op === \"clear\") put(Array.isArray(cur) ? [] : {});\n if (op === \"merge\") put({ ...(cur && typeof cur === \"object\" ? cur : {}), ...(typeof value === \"string\" ? getPath(state, value) : value) });\n if (op === \"delete\") { if (cur && typeof cur === \"object\") { delete cur[value]; put(cur); } }\n if (op === \"reset\") put(structuredClone(getPath(initials, target)));\n if (op === \"refetch\") { const n = document.querySelector(`[data-wd-fetch-key=\"${target}\"]`); if (n) startFetch(n); }\n if (op === \"remove\") {\n const row = clickedRow(action);\n if (row && row.srcKey) state[row.srcKey] = (Array.isArray(state[row.srcKey]) ? state[row.srcKey] : []).filter((/** @type {any} */ x) => x !== row.item);\n }\n if (op === \"append-row\") {\n const row = clickedRow(action);\n // Clone so each appended line is a distinct object — else a shared ref\n // means a later remove (filter !== ref) deletes both lines.\n if (row && row.item !== undefined) {\n const copy = row.item && typeof row.item === \"object\" ? structuredClone(row.item) : row.item;\n put([...arr(), copy]);\n }\n }\n}\n\n/**\n * Run a parsed action sequence with no clicked row — the `:effect` and `:every`\n * paths. Their defs are parsed ONCE and re-fired, so an `append`/`prepend` would\n * push the SAME object reference every time and a later `remove` (which filters\n * by identity) would delete every copy at once. Clone per fire; the click path\n * re-parses its JSON attribute each time and needs no copy.\n * @param {{ op: string, target?: string, value?: any }[]} actions\n * @returns {void}\n */\nconst runActions = (actions) => {\n for (const a of actions) applyAction(null, a.op, a.target || \"\", structuredClone(a.value));\n};\n\ndocument.addEventListener(\"click\", (event) => {\n const action = /** @type {Element} */ (event.target)?.closest(\"[data-wd-action],[data-wd-actions]\");\n if (!action) return;\n const seq = action.getAttribute(\"data-wd-actions\");\n if (seq) {\n for (const a of JSON.parse(seq)) applyAction(action, a.op, a.target || \"\", a.value);\n } else {\n const rawValue = action.getAttribute(\"data-wd-value\");\n applyAction(action, action.getAttribute(\"data-wd-action\") || \"\", action.getAttribute(\"data-wd-target\") || \"\", rawValue === null ? undefined : JSON.parse(rawValue));\n }\n savePersisted();\n render();\n checkRefetch();\n});\n\ndocument.addEventListener(\"submit\", (event) => {\n const form = /** @type {HTMLFormElement | null} */ (/** @type {Element} */ (event.target)?.closest(\"[data-wd-form]\"));\n if (!form) return;\n event.preventDefault();\n const key = form.getAttribute(\"data-wd-form\") || \"\";\n const action = form.getAttribute(\"action\");\n\n if (!action) {\n const fd = new FormData(form);\n const data = /** @type {Record<string, any>} */ (Object.fromEntries(fd));\n // :checkbox groups share one name; collect every checked value as an array.\n for (const group of form.querySelectorAll(\"[data-wd-multi]\")) {\n const field = group.getAttribute(\"data-wd-multi\");\n if (field) data[field] = fd.getAll(field);\n }\n state[key] = data;\n savePersisted();\n render();\n return;\n }\n\n // A slow earlier submit must not land on top of a newer one's reply, and the\n // previous attempt's error must not sit on screen through the retry.\n const dead = genGuard(/** @type {any} */ (form));\n setError(key);\n render();\n // ONE FormData feeds both bodies. Bound controls (`:bind`, a bound `:select`)\n // carry no `name`, so FormData never saw them: they are appended by their\n // state key here, before the split, so the urlencoded path carries exactly\n // the same keys the multipart path does. A bound key that a real field\n // already occupies is left alone — sending both would hand the server the\n // same name twice with different values.\n const fd = new FormData(form);\n for (const el of form.querySelectorAll(\"[data-wd-bind-input]\")) {\n const bound = el.getAttribute(\"data-wd-bind-input\");\n if (bound && !el.getAttribute(\"name\") && !fd.has(bound)) fd.append(bound, state[bound] ?? \"\");\n }\n // A file cannot be urlencoded, so a form carrying one posts the FormData\n // itself and lets the browser set the multipart content-type WITH its\n // boundary, which is exactly why no content-type is passed here.\n const upload = form.querySelector(\"input[type=file]\");\n httpJson(action, {\n method: form.getAttribute(\"method\") || \"post\",\n headers: upload ? undefined : { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: upload ? fd : new URLSearchParams(/** @type {any} */ (fd)).toString()\n })\n .then((value) => {\n if (dead()) return;\n state[key] = value;\n setError(key);\n savePersisted();\n render();\n })\n .catch((error) => {\n if (dead()) return;\n setError(key, error);\n render();\n });\n});\n\n/**\n * Run the full `:fetch` lifecycle for a marker node: interpolate the URL from\n * state, skip when a dependency is empty, flip `*_loading`, fetch with an\n * AbortController timeout + flat retry, then write value/`*_empty` or `*_error`.\n * @param {FetchNode} node\n * @returns {void}\n */\nfunction startFetch(node) {\n // Per-node generation token: a refetch bumps it; superseded writes bail.\n const dead = genGuard(node);\n /** @param {string} n @returns {string} */\n const g = (n) => node.getAttribute(\"data-wd-fetch-\" + n) || \"\";\n const key = g(\"key\");\n /** @param {string} s @param {any} v */\n const set = (s, v) => { state[key + s] = v; render(); };\n let missing = false;\n const url = g(\"url\").replace(/\\{\\s*([\\w$.]+)\\s*\\}/g, (_, p) => {\n const v = getPath(state, p);\n // `.`/`..` survive encodeURIComponent and the URL parser resolves them, so\n // `?id=..` would retarget `/api/users/{ id }/x` (headers and body included)\n // at `/api/x`. Treat them as missing, like an empty value.\n if (v == null || v === \"\" || v === \".\" || v === \"..\") missing = true;\n return encodeURIComponent(String(v ?? \"\"));\n });\n if (missing) return set(\"_loading\", false);\n\n const headers = g(\"headers\");\n const body = g(\"body\");\n const refresh = g(\"refresh\");\n const method = g(\"method\") || \"GET\";\n const timeout = Number(g(\"timeout\"));\n let tries = Number(g(\"retry\"));\n let refreshed = false; // a token refresh + retry happens at most once per request\n setError(key);\n set(\"_loading\", true);\n\n /** @param {any} error */\n const fail = (error) => { setError(key, error); set(\"_loading\", false); };\n\n const attempt = () => {\n /** @type {RequestInit} */\n const init = { method };\n if (headers) init.headers = state[headers] || {};\n if (body && method !== \"GET\") init.body = JSON.stringify(state[body] ?? null);\n const ctrl = typeof AbortController === \"function\" ? new AbortController() : null;\n if (ctrl) init.signal = ctrl.signal;\n const timer = timeout && ctrl ? setTimeout(() => ctrl.abort(), timeout) : 0;\n httpJson(url, init).then((value) => {\n clearTimeout(timer);\n if (dead()) return;\n state[key] = value;\n state[key + \"_empty\"] = isEmpty(value);\n setError(key);\n set(\"_loading\", false);\n }).catch((error) => {\n clearTimeout(timer);\n if (dead()) return;\n const is401 = error && error.status === 401;\n // Layer 2: a 401 with a refresh URL renews the token once, then retries.\n if (is401 && refresh && headers && !refreshed) {\n refreshed = true;\n return void refreshToken(refresh, headers).then((ok) => {\n if (dead()) return;\n ok ? attempt() : fail(error);\n });\n }\n if (is401 && refreshed) return void fail(error); // a second 401 after refresh → give up\n if (tries-- > 0) return void setTimeout(attempt, 200);\n fail(error);\n });\n };\n attempt();\n}\n\n/** @type {Map<string, Promise<boolean>>} In-flight token refreshes, keyed by URL. */\nconst refreshFlights = new Map();\n/**\n * Single-flight token refresh: POST the current header state to `refreshUrl`,\n * and on a 2xx JSON reply write the returned headers object back into the\n * `headersKey` state (persisting if it is a `:store`). Concurrent 401s sharing a\n * refresh URL await the same request, then each retries its own fetch.\n * @param {string} refreshUrl\n * @param {string} headersKey\n * @returns {Promise<boolean>} whether the token was renewed\n */\nfunction refreshToken(refreshUrl, headersKey) {\n const inflight = refreshFlights.get(refreshUrl);\n if (inflight) return inflight;\n const flight = httpJson(refreshUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(state[headersKey] ?? {})\n }).then((value) => {\n if (value && typeof value === \"object\") {\n state[headersKey] = value; // the new headers shape, e.g. { Authorization: \"Bearer …\" }\n savePersisted();\n return true;\n }\n return false;\n }).catch(() => false).finally(() => refreshFlights.delete(refreshUrl));\n refreshFlights.set(refreshUrl, flight);\n return flight;\n}\n\n/** A fetch marker carrying its last dependency snapshot for refetch diffing and\n * a generation token so a superseded in-flight response/retry bails on write. */\n/** @typedef {Element & { __wdSnap?: string, __wdGen?: number }} FetchNode */\n/** @type {FetchNode[]} */\nconst fetchNodes = [];\n/** @param {FetchNode} node @returns {string} */\nconst depSnapshot = (node) => (node.getAttribute(\"data-wd-fetch-deps\") || \"\").split(\",\").filter(Boolean).map((d) => JSON.stringify(getPath(state, d))).join(\"|\");\n\n/** @type {any} */\nlet refetchTimer = 0;\n/** @type {Set<FetchNode>} Nodes owed a refetch, accumulated across the window. */\nconst due = new Set();\n/**\n * After a mutation render, debounce-refetch any fetch node whose URL deps\n * changed. Snapshots live on the node, so no extra bookkeeping structure. The\n * due set OUTLIVES each debounce restart: a second mutation resets the timer,\n * and a node dropped there could never be re-detected — its snapshot has already\n * been advanced — so its refetch would be lost rather than merely delayed.\n * @returns {void}\n */\nfunction checkRefetch() {\n for (const node of fetchNodes) {\n const snap = depSnapshot(node);\n if (snap && snap !== node.__wdSnap) { node.__wdSnap = snap; due.add(node); }\n }\n if (!due.size) return;\n clearTimeout(refetchTimer);\n refetchTimer = setTimeout(() => { for (const node of due) startFetch(node); due.clear(); }, 150);\n}\n\n/**\n * Drop every registration belonging to a subtree that is about to leave the\n * document — a closed `:if` branch, a removed loop row.\n *\n * `scan()` claims a node once, and the claim marker lives ON the node while the\n * registries (`effects`, `fetchNodes`, `everyDefs`) are module-level. A branch\n * is re-cloned from its template every time it opens, so without this the NEXT\n * open registers a FRESH node while the detached one stays: an `:every` ticked\n * twice after one reopen, three times after two, and kept ticking with the\n * branch closed. Same shape for `:effect` (fired once per stale copy) and\n * `:fetch` (one request per stale copy, on a state change the reader cannot\n * even see).\n * @param {Element} root\n * @returns {void}\n */\nfunction unregister(root) {\n for (const node of selfAnd(root, \"[data-wd-fetch],[data-wd-effect],[data-wd-every]\")) {\n due.delete(/** @type {any} */ (node));\n // A fetch entry IS the node; an effect/every entry carries it on `.node`.\n for (const list of [/** @type {any[]} */ (fetchNodes), effects, everyDefs]) {\n for (let i = list.length - 1; i >= 0; i--) {\n if ((list[i].node || list[i]) !== node) continue;\n if (list[i].t) clearInterval(list[i].t);\n list.splice(i, 1);\n }\n }\n }\n}\n\n/**\n * Claim and wire up the directives that need ONE-TIME setup: `:fetch` (kick off\n * the request, snapshot its deps), `:effect` (parse the def, snapshot its watch)\n * and `:every` (start a timer). `querySelectorAll` cannot see into `<template>`\n * content, so building these at load permanently missed any that lived in an\n * unopened `:if` branch or a loop row template — they simply never ran. Called\n * again whenever a render changes structure; each node is claimed once\n * (`__wdReg`), so a re-scan costs the query and nothing else. (`:computed` and\n * `:theme` need no claim at all: both read the live tree on every render.)\n * @returns {void}\n */\nfunction scan() {\n // Seed scripts first: a branch that just opened may DECLARE the state the\n // directives below read (see hydrate).\n hydrate();\n for (const node of /** @type {any} */ (document.querySelectorAll(\"[data-wd-fetch],[data-wd-effect],[data-wd-every]\"))) {\n if (node.__wdReg) continue;\n node.__wdReg = 1;\n if (node.hasAttribute(\"data-wd-effect\")) {\n const def = JSON.parse(node.textContent || \"{}\");\n effects.push({ node, watch: def.watch, actions: def.actions || [], last: JSON.stringify(getPath(state, def.watch) ?? null) });\n }\n if (node.hasAttribute(\"data-wd-every\")) {\n if (!everyDefs.length) document.addEventListener(\"visibilitychange\", () => (document.hidden ? stopEvery() : startEvery()));\n everyDefs.push({ ...JSON.parse(node.textContent || \"{}\"), node });\n startEvery();\n }\n if (node.hasAttribute(\"data-wd-fetch\")) {\n fetchNodes.push(node);\n node.__wdSnap = depSnapshot(node);\n if (node.getAttribute(\"data-wd-fetch-when\") === \"visible\" && \"IntersectionObserver\" in window) {\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n startFetch(node);\n });\n observer.observe(node);\n } else {\n startFetch(node);\n }\n }\n }\n}\n\n// :every — run actions on a timer, paused while the tab is hidden so background\n// tabs don't poll or animate. Each tick runs the action sequence (no clicked row,\n// like :effect), then saves + renders + checks refetch deps, exactly like a click.\n/** @typedef {{ node: Element, ms: number, actions: { op: string, target?: string, value?: any }[], t?: any }} EveryDef */\n/** @type {EveryDef[]} */\nconst everyDefs = [];\n// The timer lives on the def, not in a shared list, so a def registered later\n// (one revealed by an :if flip) starts ticking without restarting the others.\nfunction startEvery() {\n if (document.hidden) return;\n for (const def of everyDefs) {\n if (def.t || !def.ms || !def.actions) continue;\n def.t = setInterval(() => {\n runActions(def.actions);\n savePersisted();\n render();\n checkRefetch();\n }, def.ms);\n }\n}\nfunction stopEvery() {\n for (const def of everyDefs) { clearInterval(def.t); def.t = 0; }\n}\n\n// Escape-hatch subscriptions: colocated `.js` \"behaviors\" (carousels, drag-and-\n// drop, charts, maps) react to state with `wd.subscribe(key, cb)`. Each subscribe\n// primes the callback with the current value, then fires on every settled change.\n/** @typedef {{ cbs: Set<(value: any) => void>, last: string }} SubEntry */\n/** @type {Map<string, SubEntry>} */\nconst subscribers = new Map();\nfunction notifySubscribers() {\n for (const [key, entry] of subscribers) {\n const now = JSON.stringify(state[key] ?? null);\n if (now === entry.last) continue;\n entry.last = now;\n for (const cb of entry.cbs) {\n try { cb(state[key]); } catch (error) { warn(`subscribe ${key}`, error); }\n }\n }\n}\n\n/**\n * Public escape hatch on `window.wd`. Set `window.wd.debug = true` to log failing\n * computed/where expressions to the console.\n */\n/** @type {any} */ (window).wd = {\n state,\n debug: false,\n /** @param {string} key */\n get: (key) => state[key],\n /** @param {string} key @param {any} value */\n set: (key, value) => {\n state[key] = value;\n savePersisted();\n render();\n // The input and click handlers both do; behaviors write only through here.\n checkRefetch();\n },\n /**\n * Run `cb(value)` now and on every future change to `state[key]`. Returns an\n * unsubscribe function. The bridge for colocated `.js` behaviors.\n * @param {string} key @param {(value: any) => void} cb\n */\n subscribe: (key, cb) => {\n let entry = subscribers.get(key);\n if (!entry) {\n entry = { cbs: new Set(), last: JSON.stringify(state[key] ?? null) };\n subscribers.set(key, entry);\n }\n entry.cbs.add(cb);\n try { cb(state[key]); } catch (error) { warn(`subscribe ${key}`, error); }\n return () => entry.cbs.delete(cb);\n },\n render: renderNow\n};\n\nrenderNow();\n"],"mappings":"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","names":["state","persistKeys","storeKeys","urlKeys","initials","ls","k","v","urlParam","key","hydrate","script","store","seed","value","lsKey","stored","jsonOr","readUrl","syncUrl","params","query","next","restore","only","raw","savePersisted","name","render","event","unsafeKey","segment","getPath","path","current","setPath","obj","segs","last","cur","seg","isEmpty","text","URL_UNSAFE","selfAnd","node","sel","applyAttr","el","item","meta","parts","out","plain","p","c","fmtNum","n","fmtList","fmtPick","row","f","fmtNums","list","r","fmtToDate","s","fmtDate","d","opts","fb","fmtTZ","FMT","l","ds","ts","suf","sg","pl","a","sep","applyFmt","stages","args","AGG","field","httpJson","url","init","response","body","said","err","setError","error","genGuard","gen","dirty","warn","expr","wd","recompute","evalAst","parseAst","containsFn","b","ev","astCache","cached","ast","evalPredicate","classToggle","pairs","loopKeyOf","counts","base","seen","fillItem","inners","skip","region","m","output","swapBranch","target","marker","reconcile","loopNum","pipeline","sortAttr","reactive","sort","dirAttr","dir","index","av","bv","w","off","lim","prefix","active","template","unregister","renderIf","nested","rows","where","emptyTpl","child","existing","used","count","moved","i","settleTries","renderNow","pass","scan","data","dot","input","reflectThemes","runEffects","subscribers","notifySubscribers","root","effects","effectDepth","changed","fx","now","runActions","schedule","scheduled","checkRefetch","clickedRow","applyAction","action","op","put","arr","x","startFetch","copy","actions","seq","rawValue","form","fd","group","dead","bound","upload","g","set","missing","_","headers","refresh","method","timeout","tries","refreshed","fail","attempt","ctrl","timer","is401","refreshToken","ok","refreshFlights","refreshUrl","headersKey","inflight","flight","fetchNodes","depSnapshot","refetchTimer","due","snap","everyDefs","def","stopEvery","startEvery","observer","entries","entry","cb"],"file":"runtime.js"}
|
package/src/scaffold.js
CHANGED
|
@@ -37,7 +37,9 @@ export function availableTemplates() {
|
|
|
37
37
|
export function initProject(root, options = {}) {
|
|
38
38
|
const template = options.template || "starter";
|
|
39
39
|
const templateDir = path.join(templatesDir, template);
|
|
40
|
-
|
|
40
|
+
// Only a shipped template name: `--template ../../somewhere` would otherwise
|
|
41
|
+
// copy an arbitrary directory into the new project.
|
|
42
|
+
if (!availableTemplates().includes(template) || !fs.statSync(templateDir).isDirectory()) {
|
|
41
43
|
const list = availableTemplates().join(", ");
|
|
42
44
|
throw wdError(
|
|
43
45
|
`Unknown template "${template}". Available templates: ${list || "(none)"}. ` +
|
|
@@ -120,11 +122,17 @@ export function initProject(root, options = {}) {
|
|
|
120
122
|
*/
|
|
121
123
|
function writeNew(root, file, content) {
|
|
122
124
|
const target = path.join(root, file);
|
|
123
|
-
|
|
125
|
+
// `existsSync` answers false for a DANGLING symlink, and a write then follows
|
|
126
|
+
// it out of `root`. lstat sees the link itself, on every OS (on Windows
|
|
127
|
+
// an exclusive `wx` create still follows the link); `wx` is the backstop.
|
|
128
|
+
if (present(target)) return;
|
|
124
129
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
125
|
-
fs.writeFileSync(target, content);
|
|
130
|
+
fs.writeFileSync(target, content, { flag: "wx" });
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
/** Whether anything, a dangling symlink included, already occupies `target`. @param {string} target */
|
|
134
|
+
const present = (target) => fs.lstatSync(target, { throwIfNoEntry: false }) !== undefined;
|
|
135
|
+
|
|
128
136
|
/**
|
|
129
137
|
* Copy a template file to `dest`, preserving bytes, only if `dest` is absent.
|
|
130
138
|
* @param {string} source
|
|
@@ -132,9 +140,9 @@ function writeNew(root, file, content) {
|
|
|
132
140
|
* @returns {void}
|
|
133
141
|
*/
|
|
134
142
|
function copyNew(source, dest) {
|
|
135
|
-
if (
|
|
143
|
+
if (present(dest)) return; // see writeNew: a dangling symlink counts as present
|
|
136
144
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
137
|
-
fs.copyFileSync(source, dest);
|
|
145
|
+
fs.copyFileSync(source, dest, fs.constants.COPYFILE_EXCL);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
/**
|
package/src/statics.js
CHANGED
|
@@ -139,7 +139,7 @@ export function serve(distRoot, url, res, options = {}) {
|
|
|
139
139
|
/** @type {Record<string, string>} */
|
|
140
140
|
const headers = type.startsWith("text/html")
|
|
141
141
|
? { "content-type": type, ...htmlSecurityHeaders(distRoot, url) }
|
|
142
|
-
: { "content-type": type };
|
|
142
|
+
: { "content-type": type, ...BASE_SECURITY_HEADERS }; // `_headers` `/*` covers every file
|
|
143
143
|
const compressible = isCompressibleType(type);
|
|
144
144
|
if (options.validators) {
|
|
145
145
|
const etag = strongEtag(stat);
|
|
@@ -402,6 +402,15 @@ export function pipeFile(file, res) {
|
|
|
402
402
|
stream.pipe(res);
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
/** Top-level dist entries a host consumes but never serves (see resolvePublicFile). */
|
|
406
|
+
const HOST_ONLY_FILES = new Set([
|
|
407
|
+
"_worker.js",
|
|
408
|
+
"_headers",
|
|
409
|
+
"_redirects",
|
|
410
|
+
"_routes.json",
|
|
411
|
+
".wd-dev-deps.json"
|
|
412
|
+
]);
|
|
413
|
+
|
|
405
414
|
/**
|
|
406
415
|
* Resolve a request URL to an absolute file inside `distRoot`, guarding against
|
|
407
416
|
* path traversal. Returns null when the resolved path escapes the root.
|
|
@@ -420,6 +429,12 @@ export function resolvePublicFile(distRoot, url) {
|
|
|
420
429
|
// resulting path throws ERR_INVALID_ARG_VALUE out of the request handler,
|
|
421
430
|
// killing the server. It can never name a built file, so it is a miss.
|
|
422
431
|
if (cleanUrl.includes("\0")) return null;
|
|
432
|
+
// Build-control files a host consumes but never serves: the Cloudflare worker
|
|
433
|
+
// bundle (`api/*` handler SOURCE), `_headers`/`_redirects`/`_routes.json`, and
|
|
434
|
+
// the dev dependency map (the project's file graph). Serve them locally and a
|
|
435
|
+
// preview leaks what production hides.
|
|
436
|
+
const head = path.posix.normalize(cleanUrl.replaceAll("\\", "/")).split("/")[1] ?? "";
|
|
437
|
+
if (HOST_ONLY_FILES.has(head)) return null;
|
|
423
438
|
const base = cleanUrl.startsWith("/__wd/")
|
|
424
439
|
? cleanUrl
|
|
425
440
|
: path.join(cleanUrl, path.extname(cleanUrl) ? "" : "index.html");
|
package/types/dev.d.ts
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local dev/preview servers bind to loopback, but loopback alone does not
|
|
3
|
+
* keep a website you are visiting out. Two attacks reach it from a browser:
|
|
4
|
+
*
|
|
5
|
+
* - DNS rebinding: `evil.example` re-resolves to 127.0.0.1, after which the
|
|
6
|
+
* page can READ every response (drafts, the dep map, the SSE stream, and
|
|
7
|
+
* `api/*` handlers, which run with the full `process.env`). The browser still
|
|
8
|
+
* sends `Host: evil.example`, so only hostnames that cannot be rebound pass:
|
|
9
|
+
* `localhost`/`*.localhost`, IP literals (a rebinding needs a domain), the
|
|
10
|
+
* bind address, and the `DARKMOWN_ALLOWED_HOSTS` list (a tunnel's domain).
|
|
11
|
+
* - Cross-site requests: any page can fire a simple POST at
|
|
12
|
+
* `http://127.0.0.1:5173/api/x` without reading the reply, so the handler's
|
|
13
|
+
* side effects run. `/api/*` therefore also refuses `Sec-Fetch-Site:
|
|
14
|
+
* cross-site` and an `Origin` that is not loopback, the request's own host,
|
|
15
|
+
* or allow-listed.
|
|
16
|
+
*
|
|
17
|
+
* `DARKMOWN_ALLOWED_HOSTS=*` turns the guard off.
|
|
18
|
+
* @param {{ url?: string, headers: Record<string, string | string[] | undefined> }} req
|
|
19
|
+
* @param {{ bindHost?: string, allowedHosts?: string }} [options]
|
|
20
|
+
* @returns {string | null} why the request is refused, or null to serve it
|
|
21
|
+
*/
|
|
22
|
+
export function refuseLocalRequest(req: {
|
|
23
|
+
url?: string;
|
|
24
|
+
headers: Record<string, string | string[] | undefined>;
|
|
25
|
+
}, { bindHost, allowedHosts }?: {
|
|
26
|
+
bindHost?: string;
|
|
27
|
+
allowedHosts?: string;
|
|
28
|
+
}): string | null;
|
|
1
29
|
/**
|
|
2
30
|
* Serialize the dev server's rebuilds: at most ONE child build runs at a time.
|
|
3
31
|
* Every build read-modify-writes the dependency map (`dist/.wd-dev-deps.json`),
|