From 1f26894c4d7dc8a4d870e8aedbd56f4901835e7f Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Wed, 16 Sep 2026 07:56:15 +0900 Subject: [PATCH] fix(site-kit): StatusBanner reads its children once, so a second banner hydrates (D-028) `{props.children &&
{props.children}
}` read a compiled JSX getter twice. The first read built the child (a list and every
  • under it) and threw it away, consuming hydration keys the server never wrote. Solid's production build clones a template when a key is missing, so counters stayed aligned and nothing showed; the development build throws Hydration Mismatch, and BookLinq's /status fell to the error layout in `vite dev` (jlc488/booklinq#51). One createMemo is now the check and the insert. The new test renders two banners with inline `"], ssrHydrationKey(), escape(body)); +const li = (text) => ssr(["", "
  • "], ssrHydrationKey(), escape(text)); +const html = renderToString(() => page({ createComponent, For, ul, li, StatusBanner, sections: ${JSON.stringify(SECTIONS)} })); +process.stdout.write(JSON.stringify({ bootstrap: generateHydrationScript(), html })); +`; + +const hydrateScript = () => ` +process.on("uncaughtException", (e) => { process.stderr.write("UNCAUGHT " + (e && e.stack || e) + String.fromCharCode(10)); process.exit(1); }); +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +const require = createRequire(${JSON.stringify(pathToFileURL(join(kit, "package.json")).href)}); +const { JSDOM } = require("jsdom"); +const { bootstrap, html } = JSON.parse(readFileSync(new URL("./ssr.json", import.meta.url), "utf8")); +const dom = new JSDOM("" + bootstrap + "
    " + html + "
    ", { runScripts: "dangerously", pretendToBeVisual: true, url: "https://example.test/" }); +for (const key of ["window", "document", "Node", "HTMLElement", "SVGElement", "MutationObserver", "navigator", "requestAnimationFrame", "localStorage", "matchMedia"]) { + Object.defineProperty(globalThis, key, { value: dom.window[key], configurable: true, writable: true }); +} +Object.defineProperty(globalThis, "_$HY", { value: dom.window._$HY, configurable: true, writable: true }); +const host = document.querySelector("#root"); +const serverKeys = [...host.querySelectorAll("[data-hk]")].map((element) => element.getAttribute("data-hk")); +const { hydrate, template, getNextElement, insert } = await import("solid-js/web"); +const { createComponent, For } = await import("solid-js"); +const { StatusBanner } = await import(${JSON.stringify(pathToFileURL(join(kit, "dist", "solid.js")).href)}); +const { page } = await import("./page.mjs"); +const ulTemplate = template('