pub const PATCH_CLIENT: &str = "// The patch interpreter, the router, the input capture and the id source. Shared by both modes.\n//\n// Mode A receives these ops from the server; Mode B\'s kernel produces them in the browser from its\n// own two renders. Same vocabulary, same interpreter \u{2014} which is not a convenience, it is the claim:\n// \"hand-written JavaScript never appears in the source \u{2014} it\'s compiler residue\", and there is one\n// piece of residue rather than one per mode.\n(() => {\n // An element belongs to a namespace, and `createElement` only ever guesses HTML.\n //\n // Server-side rendering goes through the browser\'s own HTML parser, which knows that `<svg>`\n // opens a different namespace and that `<foreignObject>` closes it again. This interpreter is the\n // other half of the same page and has to know the same thing: an `svg` built as HTML is not an\n // `SVGElement`, so it lays out as nothing and a chart that paints on first load vanishes the\n // first time its data changes. `createElementNS` is also what keeps `linearGradient` and\n // `clipPath` their own case, which `createElement` lowercases in an HTML document.\n const HTML = \"http://www.w3.org/1999/xhtml\";\n const SVG = \"http://www.w3.org/2000/svg\";\n\n // Which namespace a *child* of this node is built in. Inherited, except at the two edges.\n const within = (node) =>\n node && node.namespaceURI === SVG && node.localName !== \"foreignObject\" ? SVG : HTML;\n\n const build = (h, ns) => {\n if (typeof h === \"string\") return document.createTextNode(h);\n const tag = h[0];\n // A patch can carry a subtree whose root is `svg` or a subtree that starts inside one, so the\n // namespace comes from the tag when the tag opens one and from the destination otherwise.\n const here = tag === \"svg\" ? SVG : ns || HTML;\n const el = document.createElementNS(here, tag);\n // Pairs, in the order the server wrote them: an element rebuilt here carries its attributes in\n // the order the same element has in the server-rendered document.\n const attrs = h[1];\n for (let a = 0; a < attrs.length; a++) el.setAttribute(attrs[a][0], attrs[a][1]);\n const kids = h[2];\n const inner = here === SVG && tag !== \"foreignObject\" ? SVG : HTML;\n for (let i = 0; i < kids.length; i++) el.appendChild(build(kids[i], inner));\n return el;\n };\n\n // What this client has sent, received and applied. Read by the devtools panel and by nothing\n // else \u{2014} no behaviour depends on it, which is the point: a counter that changes what the page\n // does is a second implementation of the page.\n const stats = {\n frames: 0,\n ops: 0,\n bytes_in: 0,\n bytes_out: 0,\n sent: 0,\n navigations: 0,\n pending: 0,\n connected: false,\n };\n\n // ---- where the caret and the scroll are, across a patch --------------------------------------\n //\n // A patch that replaces an ancestor of the focused element destroys it, and the browser\'s answer\n // to \"what is focused now\" is `body`. So the page loses the caret in the middle of typing, and\n // the list somebody had scrolled jumps back to the top. Neither is a defect of the diff \u{2014} the\n // page really did change \u{2014} and neither is the program\'s to think about, so it is handled here,\n // once, for both modes.\n //\n // The cost is proportional to *the patch* and not to the page: nothing is walked except the\n // subtrees a replace is about to destroy, and the focused element is one lookup.\n\n const indexPath = (from, node) => {\n const path = [];\n let at = node;\n while (at && at !== from) {\n const parent = at.parentNode;\n if (!parent) return null;\n path.unshift(Array.prototype.indexOf.call(parent.childNodes, at));\n at = parent;\n }\n return at === from ? path : null;\n };\n\n const descend = (from, path) => {\n let node = from;\n for (let i = 0; i < path.length && node; i++) node = node.childNodes[path[i]];\n return node;\n };\n\n // Enough of an element\'s identity to refuse to restore the caret into a *different* element that\n // happens to have taken its place. A key, a name or an id is the program\'s own answer; the tag is\n // the floor.\n const identity = (el) =>\n el.tagName +\n \"|\" +\n (el.getAttribute(\"data-b-k\") || \"\") +\n \"|\" +\n (el.getAttribute(\"name\") || \"\") +\n \"|\" +\n (el.id || \"\");\n\n const caret = (root) => {\n const el = document.activeElement;\n if (!el || el === document.body || !root.contains(el)) return null;\n const path = indexPath(root, el);\n if (!path) return null;\n const range =\n typeof el.selectionStart === \"number\"\n ? { start: el.selectionStart, end: el.selectionEnd, dir: el.selectionDirection }\n : null;\n return { el, path, range, id: identity(el), top: el.scrollTop, left: el.scrollLeft };\n };\n\n const restoreCaret = (root, was) => {\n // Still there: the patch did not touch it, and re-focusing would move the caret for nothing.\n if (!was || was.el.isConnected) return;\n const now = descend(root, was.path);\n if (!now || now.nodeType !== 1 || identity(now) !== was.id || !now.focus) return;\n now.focus();\n if (was.range && now.setSelectionRange) {\n try {\n now.setSelectionRange(was.range.start, was.range.end, was.range.dir);\n } catch (e) {\n // A type whose selection cannot be set (`number`, `email` in some browsers). The focus is\n // the part that matters and it is already restored.\n }\n }\n now.scrollTop = was.top;\n now.scrollLeft = was.left;\n };\n\n // Scroll offsets inside a subtree about to be replaced, by position within it. Best effort by\n // construction \u{2014} a replaced subtree is one whose shape may have changed \u{2014} so it is keyed by\n // position and restored only where the position still holds something scrollable.\n const scrollsIn = (node, path, out) => {\n if (node.nodeType !== 1) return;\n if (node.scrollTop || node.scrollLeft) {\n out.push({ path, top: node.scrollTop, left: node.scrollLeft });\n }\n for (let i = 0; i < node.childNodes.length; i++) {\n scrollsIn(node.childNodes[i], path.concat(i), out);\n }\n };\n\n const apply = (root, ops) => {\n const at = (path) => {\n let node = root.firstElementChild;\n for (let i = 0; i < path.length; i++) node = node.childNodes[path[i]];\n return node;\n };\n const was = caret(root);\n const scrolled = [];\n for (let i = 0; i < ops.length; i++) {\n if (ops[i][0] !== 0) continue; // only a replace rebuilds what was scrolled\n const victim = at(ops[i][1]);\n if (victim) scrollsIn(victim, [], (scrolled[i] = []));\n }\n\n for (let i = 0; i < ops.length; i++) {\n const op = ops[i];\n const path = op[1];\n switch (op[0]) {\n case 0: { // replace\n const node = at(path);\n // The namespace of what is being built comes from where it is going, which is the parent\n // of what it replaces \u{2014} an op whose root is a `rect` says nothing about namespaces itself.\n const next = build(op[2], within(node ? node.parentNode : root));\n if (node) node.replaceWith(next);\n else root.appendChild(next);\n const kept = scrolled[i];\n for (let s = 0; kept && s < kept.length; s++) {\n const target = descend(next, kept[s].path);\n if (target && target.nodeType === 1) {\n target.scrollTop = kept[s].top;\n target.scrollLeft = kept[s].left;\n }\n }\n break;\n }\n case 1: at(path).textContent = op[2]; break; // set text\n case 2: at(path).setAttribute(op[2], op[3]); break; // set attribute\n case 3: at(path).removeAttribute(op[2]); break; // remove attribute\n case 4: { // insert child\n const parent = at(path);\n parent.insertBefore(build(op[3], within(parent)), parent.childNodes[op[2]] || null);\n break;\n }\n case 5: { // remove child\n const parent = at(path);\n parent.removeChild(parent.childNodes[op[2]]);\n break;\n }\n case 6: { // move child (from > to), which is what preserves focus and scroll on reorder\n const parent = at(path);\n parent.insertBefore(parent.childNodes[op[2]], parent.childNodes[op[3]]);\n break;\n }\n }\n }\n restoreCaret(root, was);\n stats.frames += 1;\n stats.ops += ops.length;\n announce(root, \"beck:traffic\", stats);\n };\n\n // \"Client-generated UUIDs are the small tell that browsers here are replicas, not terminals\":\n // the client must be able to name a todo before the server confirms it exists.\n const uuid7 = () => {\n const now = Date.now();\n const b = crypto.getRandomValues(new Uint8Array(16));\n b[0] = now / 2 ** 40; b[1] = now / 2 ** 32; b[2] = now / 2 ** 24;\n b[3] = now / 2 ** 16; b[4] = now / 2 ** 8; b[5] = now;\n b[6] = (b[6] & 0x0f) | 0x70;\n b[8] = (b[8] & 0x3f) | 0x80;\n let hex = \"\";\n for (let i = 0; i < 16; i++) hex += b[i].toString(16).padStart(2, \"0\");\n return hex.slice(0, 8) + \"-\" + hex.slice(8, 12) + \"-\" + hex.slice(12, 16) + \"-\" +\n hex.slice(16, 20) + \"-\" + hex.slice(20);\n };\n\n // Handlers in `view` compiled to attributes, so no user JavaScript runs and `script-src` can\n // stay near-empty. Three holes, and a command is filled *recursively* \u{2014} a command whose field is\n // a record has its holes one level down, and a filler that only looked at the top would leave\n // the literal `\"$id\"` in the log.\n //\n // | hole | filled with |\n // |---|---|\n // | `$id` | a fresh UUIDv7, minted here so the client can name a thing before the server has it |\n // | `$value` | the value of the element the handler is on |\n // | `$field:name` | the value of the form control called `name`, in the form being submitted |\n const fill = (template, value, form) => {\n const hole = (v) => {\n if (v === \"$id\") return uuid7();\n if (v === \"$value\") return value === null ? \"\" : value;\n if (typeof v === \"string\" && v.startsWith(\"$field:\")) {\n const named = form && form.elements[v.slice(\"$field:\".length)];\n if (!named) return \"\";\n if (named.type === \"checkbox\") return named.checked;\n return named.value === undefined ? \"\" : named.value;\n }\n return v;\n };\n const walk = (node) => {\n if (typeof node === \"string\") return hole(node);\n if (Array.isArray(node)) return node.map(walk);\n if (node && typeof node === \"object\") {\n const out = {};\n for (const key in node) out[key] = walk(node[key]);\n return out;\n }\n return node;\n };\n return walk(JSON.parse(template));\n };\n\n // Declared handlers, captured once. `send` is whatever the mode does with a command: post it up\n // the socket (Mode A), or apply it locally first and then post it (Mode B).\n //\n // Every event listened for is a `data-b-<event>` attribute the `ui:` macro wrote from an\n // `on_<event>=` in the program, so this file names events and never commands.\n const capture = (send) => {\n const on = (kind, attribute, handler) =>\n document.addEventListener(kind, (event) => {\n const el = event.target.closest && event.target.closest(\"[\" + attribute + \"]\");\n if (el) handler(el, event);\n });\n\n on(\"click\", \"data-b-click\", (el) => send(fill(el.getAttribute(\"data-b-click\"), null)));\n\n on(\"keydown\", \"data-b-enter\", (el, event) => {\n if (event.key !== \"Enter\") return;\n const value = el.value || \"\";\n if (!value.trim()) return;\n send(fill(el.getAttribute(\"data-b-enter\"), value));\n el.value = \"\";\n });\n\n // A form. The browser\'s own submit \u{2014} a button, or Enter in a single-line field \u{2014} so a page\n // built out of `form:` and `input(name=\u{2026})` is one a keyboard and a screen reader already know\n // how to drive, and `$field:name` is how the program names what was typed.\n on(\"submit\", \"data-b-submit\", (el, event) => {\n event.preventDefault();\n send(fill(el.getAttribute(\"data-b-submit\"), null, el));\n el.reset();\n });\n\n // A control that reports as it changes. `input` fires per keystroke and `change` on commit,\n // which is the browser\'s distinction rather than one invented here.\n on(\"input\", \"data-b-input\", (el) =>\n send(fill(el.getAttribute(\"data-b-input\"), controlValue(el), el.form)));\n on(\"change\", \"data-b-change\", (el) =>\n send(fill(el.getAttribute(\"data-b-change\"), controlValue(el), el.form)));\n };\n\n const controlValue = (el) =>\n el.type === \"checkbox\" ? el.checked : el.value === undefined ? \"\" : el.value;\n\n // ---- the router -----------------------------------------------------------------------------\n //\n // A route is `session.path`, so navigating is not a fetch and not a route table: it is the same\n // page function of a different session, and the only thing this has to do is (a) keep the address\n // bar honest and (b) say where the client is. In Mode A that is a message and a patch back; in\n // Mode B the kernel re-renders locally and the server is told only so that the `Session` it hands\n // `validate` is the one the client\'s own `validate` saw.\n //\n // An ordinary `<a href>` \u{2014} no `data-b-` attribute, no `onclick`, nothing in the program that\n // knows a router exists. Which is the point: a link that this file did not intercept is still a\n // link, and the page it lands on is server-rendered at that path.\n\n // Does this document have a URL of its own?\n //\n // A `srcdoc` iframe does not \u{2014} which is what the playground\'s clients are (docs/98) \u{2014} and neither\n // does a blob. Their `location.pathname` is `srcdoc` or a UUID, so a client that read a route off\n // it would report one no program could ever match, and nothing would say so.\n const addressed = () => location.protocol === \"http:\" || location.protocol === \"https:\";\n\n // Where this document is, as a route. The application\'s root when there is no address bar.\n const here = () => (addressed() ? location.pathname : \"/\");\n\n const route = (go) => {\n // A document with no URL cannot navigate: `pushState` has no address bar to move, and the\n // links inside it are not this page\'s to intercept.\n if (!addressed()) return;\n document.addEventListener(\"click\", (event) => {\n if (event.defaultPrevented || event.button !== 0) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n const a = event.target.closest && event.target.closest(\"a[href]\");\n // `target`, `download` and `rel=external` are three ways of writing \"not an in-page\n // navigation\", and each is the author\'s statement rather than a guess about intent.\n if (!a || a.target || a.hasAttribute(\"download\") || a.getAttribute(\"rel\") === \"external\") {\n return;\n }\n const url = new URL(a.getAttribute(\"href\"), location.href);\n if (url.origin !== location.origin) return;\n if (url.pathname === location.pathname && url.hash) return; // an anchor on this page\n event.preventDefault();\n if (url.pathname === location.pathname && url.search === location.search) return;\n history.pushState(null, \"\", url.pathname + url.search + url.hash);\n go(location.pathname);\n });\n // Back and forward. The address bar has already moved by the time this fires, so the route is\n // read off `location` rather than carried in the history entry \u{2014} one source for where the\n // client is, and it is the browser\'s.\n window.addEventListener(\"popstate\", () => go(location.pathname));\n };\n\n // The transport, as a seam. A deployment opens a websocket to the origin it was served from;\n // the playground hands the identical protocol a `MessageChannel` port to a worker in the same\n // tab (docs/17 \u{a7}17.2). Everything above and below this function \u{2014} the frames, the resumption\n // rule, the outbox \u{2014} is the same code either way, which is what makes a tab a *host* rather\n // than a simulation.\n //\n // The contract, for whoever writes the third one: `dial(handlers)` returns `{send, ready}` and\n // optionally `close`, and calls `handlers.open` *after* returning \u{2014} a transport that is ready\n // the moment it is dialled has to defer, because `open` is where the `hello` frame is sent and\n // it sends it through the object `dial` has not handed back yet. A transport without `close`\n // is dropped rather than closed, so its connection lives until whatever owns it goes away.\n //\n // Where the mode\'s own artefacts come from, as a seam \u{2014} the kernel module and the component\'s\n // bundle in Mode B. A deployment fetches them from the origin it was served from, on the two\n // reserved routes `beck_rt::http` answers; the playground has no server behind the frame, so it\n // hands over the bundle its worker derived and reads the kernel from the directory the page was\n // deployed to (docs/98).\n //\n // The contract: `asset(name)` returns a promise of a `Response`, because that is what\n // `WebAssembly.instantiateStreaming` takes and a synthetic one is a constructor call.\n const asset = (name) => fetch(\"/\" + name);\n\n const websocket = (handlers) => {\n const url = (location.protocol === \"https:\" ? \"wss://\" : \"ws://\") + location.host + \"/socket\";\n const socket = new WebSocket(url);\n socket.onopen = handlers.open;\n // The bytes are counted here rather than in `connect`, because they are a fact about *this*\n // transport: a port transport hands over objects and never encodes one. A devtools panel\n // reading a zero is therefore reading the truth about a playground tab rather than a bug.\n socket.onmessage = (event) => {\n stats.bytes_in += event.data.length;\n handlers.message(JSON.parse(event.data));\n };\n socket.onclose = handlers.close;\n socket.onerror = () => socket.close();\n return {\n send: (frame) => {\n const text = JSON.stringify(frame);\n stats.bytes_out += text.length;\n socket.send(text);\n },\n ready: () => socket.readyState === 1,\n close: () => socket.close(),\n };\n };\n\n // One connection, resumable by `(subscription, seq)`. `on` is the frame handler the mode\n // supplies.\n //\n // `state` is read at every open rather than at the first one, so a caller that keeps\n // `state.seq` current resumes from where it actually is. A snapshot would make every reconnect\n // ask for the gap since first paint and then apply it to a DOM that had already moved.\n //\n // A null `state.seq` is sent as an *absent* field, which is the protocol\'s \"I hold nothing\".\n // Zero would mean \"I hold the frame as of zero\", which is true of a server-rendered document\n // and false of a client that has only just started.\n //\n // `state.path` rides on the `hello` for a related reason: a route established by a second frame\n // would leave every reconnection rendering the root\'s page until that frame arrived.\n //\n // The returned sender carries a `close`, because the reconnect below has to be stoppable by\n // something other than the frame being destroyed. Nothing else holds the retry timer.\n const connect = (state, on, opened) => {\n let backoff = 250;\n const outbox = [];\n let link = null;\n let retry = null;\n let stopped = false;\n const open = () => {\n retry = null;\n link = (beck.dial || websocket)({\n open: () => {\n backoff = 250;\n stats.connected = true;\n // The route rides on the `hello` for the reason above: a route established by a second\n // frame would leave every reconnection rendering the root\'s page until it arrived.\n const hello = {\n t: \"hello\",\n sub: state.sub,\n actor: state.actor,\n path: here(),\n };\n if (state.seq !== null && state.seq !== undefined) hello.seq = state.seq;\n link.send(hello);\n // Commands sent while disconnected are safe to repeat: each carries an id, and the\n // server de-duplicates by it.\n while (outbox.length) link.send(outbox.shift());\n // Every open, not only the first: a client that has been away has a queue that predates\n // this connection, and possibly this page load (`beck-mode-b.js`).\n if (opened) opened();\n },\n message: on,\n close: () => {\n link = null;\n stats.connected = false;\n if (stopped) return;\n retry = setTimeout(open, backoff);\n backoff = Math.min(backoff * 2, 5000);\n },\n });\n };\n open();\n const send = (frame) => {\n // Counted here rather than in the transport, because a frame is a frame whichever one is\n // under it. The *bytes* are not: they are counted in `websocket` below, since a port\n // transport moves objects and has none.\n stats.sent += 1;\n if (link && link.ready()) link.send(frame);\n else outbox.push(frame);\n };\n // Stop dialling. Both lines are load-bearing, for the two ways a client is closed: after the\n // connection dropped there is a retry already armed, which is the `clearTimeout`; while it is\n // still up the close travels through the transport and the handler above runs on the way out,\n // which is the flag. A socket reports its own closing and cannot say whether it was asked for.\n send.close = () => {\n stopped = true;\n clearTimeout(retry);\n retry = null;\n const closing = link;\n link = null;\n stats.connected = false;\n if (closing && closing.close) closing.close();\n };\n return send;\n };\n\n // An event anybody can listen for, on any ancestor. `bubbles` because the natural place to\n // listen is `document` \u{2014} a page showing \"reconnecting\u{2026}\" should not have to know which element\n // the residue chose as its frame root.\n const announce = (root, kind, detail) =>\n root.dispatchEvent(new CustomEvent(kind, { detail, bubbles: true }));\n\n // \"This component is live.\" `data-b-ready` is the mode\'s letter, so a stylesheet can hide a\n // spinner with a selector and a script can wait for one attribute whichever mode it is in.\n const ready = (root, mode) => {\n if (root.dataset.bReady === mode) return;\n root.dataset.bReady = mode;\n announce(root, \"beck:ready\", { mode });\n };\n\n // What a devtools panel is allowed to know, and the only way it is allowed to know it: a mode\n // registers what it can say about itself, and the panel reads. Nothing here computes a second\n // account of anything.\n const inspect = { stats, describe: () => ({}) };\n\n // The panel is loaded on request, not on every page. `?devtools` turns it on and leaves the\n // switch behind, so a reload \u{2014} and a route change \u{2014} keeps it; `?devtools=off` clears it.\n const devtools = () => {\n let want = null;\n try {\n const asked = new URL(location.href).searchParams.get(\"devtools\");\n if (asked !== null) {\n want = asked !== \"off\" && asked !== \"0\";\n localStorage.setItem(\"beck:devtools\", want ? \"1\" : \"\");\n } else {\n want = localStorage.getItem(\"beck:devtools\") === \"1\";\n }\n } catch (e) {\n want = false; // no `localStorage` in this context; the page is unaffected\n }\n if (!want) return;\n const script = document.createElement(\"script\");\n script.src = \"/beck-devtools.js\";\n document.body.appendChild(script);\n };\n\n // `dial` is deliberately absent rather than null: a page that wants a different transport sets\n // it on this object before the mode\'s script runs, and one that does not gets a websocket.\n // `asset` is present and overridable for the same reason, and `shell` says whether this document\n // may cache itself \u{2014} a frame with no origin of its own may not, and says so rather than failing a\n // registration nobody reads.\n window.beck = {\n build, apply, uuid7, fill, capture, connect, announce, ready, route, here, stats, inspect,\n devtools, asset, shell: true,\n };\n})();\n";Expand description
The patch interpreter and the socket, shared by both rendering modes (§5.1).
“Hand-written JavaScript never appears in the source — it’s compiler residue: the patch interpreter plus the compiled view. You stopped writing it the moment the page became a function.” These three files are that residue, and they hold no application logic.