From fe3cc602ae608fe1465bc5874b79ee55a6ad4a44 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 00:38:06 +0200 Subject: [PATCH 1/7] fix(build): embed the declarative evaluator worker in compiled binaries The declarative config evaluator spawns its worker through a sibling URL whose extension is computed at runtime: const extension = import.meta.url.endsWith(".ts") ? ".ts" : ".js"; return new URL(`./declarative-evaluator-worker-entry${extension}`, import.meta.url); `deno compile` embeds a worker only when it can statically read the specifier, so the entry was never packaged. Nothing caught it: running from source resolves the file from disk, so the build and all 3794 unit tests pass. The binary then started, served traffic, and died on the first request that evaluated declarative config: Module not found: file:///tmp/deno-compile-veryfront/src/config/declarative-evaluator-worker-entry.ts Unhandled error in child worker. In production that crash-looped veryfront-server to 1 of 4 replicas ready. Staging runs the same binary and stayed green because its traffic never reached that path, which is why promotion looked safe. DEFAULT_INCLUDES already carried two other workers for this reason; this is the third and the first to reach production. A bare list entry would invite a fourth, so the entry lives in UNTRACEABLE_WORKER_INCLUDES with two guards: - Every sibling-URL worker specifier in a file that constructs a Worker must be covered by an include. Static import specifiers are excluded, since compile traces those. Emptying the constant fails this test naming the module from the crash above. - The full profile must carry those includes. The proxy profile deliberately does not. cli/proxy-main.ts pulls the evaluator's runner into its module graph, so this looked at first like the same latent crash in the proxy binary. It is not: embedding the worker entry drags the evaluator's babel tree into the frozen scripts/build/proxy-deno.lock and the proxy build fails outright. That lock has never carried those packages, so the proxy cannot have been spawning this worker. A third guard pins that exclusion so it is not silently "fixed" into a broken proxy build. Verified against real binaries rather than the argument list: built with the include, the embedded VFS contains declarative-evaluator-worker-entry.ts; built without it, the same VFS contains no reference to it. The proxy binary compiles. --- scripts/build/compile-binary.ts | 27 +++++ .../build/compile-binary-includes.test.ts | 98 ++++++++++++++++++- 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/scripts/build/compile-binary.ts b/scripts/build/compile-binary.ts index ba57874005..64693813e8 100644 --- a/scripts/build/compile-binary.ts +++ b/scripts/build/compile-binary.ts @@ -7,7 +7,27 @@ import { parseArgs } from "#std/flags"; import { fromFileUrl, isAbsolute, join } from "#std/path.ts"; const PROJECT_ROOT = fromFileUrl(new URL("../..", import.meta.url)); + +/** + * Worker entrypoints `deno compile` cannot discover. + * + * These are spawned through a sibling URL whose extension is computed at + * runtime. Compile embeds a worker only when it can statically read the + * specifier, so these are invisible to it and must be listed explicitly. + * + * Omitting one fails nothing that runs from source -- the file resolves from + * disk, so the build and every test pass. The binary then starts, serves + * traffic, and dies on the first request that spawns the worker. The + * declarative config evaluator reached production this way and crash-looped it. + * + * See PROXY_INCLUDES for why the proxy profile does not carry these. + */ +export const UNTRACEABLE_WORKER_INCLUDES = [ + "src/config/declarative-evaluator-worker-entry.ts", +]; + export const DEFAULT_INCLUDES = [ + ...UNTRACEABLE_WORKER_INCLUDES, "src/platform/polyfills", "src/proxy/main.ts", "src/security/sandbox/worker-script.ts", @@ -50,6 +70,13 @@ export const DEFAULT_INCLUDES = [ ]; export const PROXY_INCLUDES = [ + // Deliberately omits UNTRACEABLE_WORKER_INCLUDES. `cli/proxy-main.ts` pulls + // the declarative evaluator's *runner* into its module graph, but embedding + // the worker entry drags the evaluator's babel dependency tree into + // scripts/build/proxy-deno.lock, which --frozen rejects. That lock has never + // carried those packages, so the proxy cannot have been spawning this worker. + // If it ever needs to, both this list and the proxy lock must change together. + // // The proxy runtime is loaded after provider activation. Providers are // statically referenced by cli/proxy-main.ts so --include does not embed the // workspace file tree for each extension. diff --git a/tests/unit/build/compile-binary-includes.test.ts b/tests/unit/build/compile-binary-includes.test.ts index 0efc744b9b..c6a5901b8b 100644 --- a/tests/unit/build/compile-binary-includes.test.ts +++ b/tests/unit/build/compile-binary-includes.test.ts @@ -1,14 +1,18 @@ import "../../_helpers/contract-init.ts"; import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; -import { createCompileArgs } from "../../../scripts/build/compile-binary.ts"; +import { + createCompileArgs, + UNTRACEABLE_WORKER_INCLUDES, +} from "../../../scripts/build/compile-binary.ts"; describe("compile-binary includes", () => { - function getIncludeFlags(): string[] { + function getIncludeFlags(profile: "full" | "proxy" = "full"): string[] { const args = createCompileArgs({ - entrypoint: "cli/main.ts", + entrypoint: profile === "proxy" ? "cli/proxy-main.ts" : "cli/main.ts", extraIncludes: [], output: "/tmp/test-veryfront", + profile, }); const includeFlags: string[] = []; @@ -22,6 +26,94 @@ describe("compile-binary includes", () => { return includeFlags; } + it("should embed the untraceable worker entrypoints in the full profile", () => { + const includeFlags = getIncludeFlags("full"); + for (const workerInclude of UNTRACEABLE_WORKER_INCLUDES) { + assertEquals( + includeFlags.includes(workerInclude), + true, + `${workerInclude} must be embedded in the full profile, got includes: ${ + JSON.stringify(includeFlags) + }`, + ); + } + }); + + it("should keep untraceable workers out of the frozen proxy profile", () => { + // Not an accident worth "fixing": embedding these drags the declarative + // evaluator's babel tree into scripts/build/proxy-deno.lock, which --frozen + // rejects, so the proxy binary fails to build. If the proxy ever needs to + // spawn one, the lock has to be regenerated in the same change. + const includeFlags = getIncludeFlags("proxy"); + for (const workerInclude of UNTRACEABLE_WORKER_INCLUDES) { + assertEquals( + includeFlags.includes(workerInclude), + false, + `${workerInclude} in the proxy profile breaks the frozen proxy lock; regenerate scripts/build/proxy-deno.lock in the same change`, + ); + } + }); + + it("should include every worker entrypoint spawned from a sibling URL", async () => { + // `deno compile` embeds a worker only when it can statically read the + // specifier. Every worker here resolves one relative to `import.meta.url`, + // and two of them compute the extension, so none are traceable. A missing + // entry does not fail the build or any test that runs from source: the + // binary starts, serves traffic, and dies on the first request that spawns + // the worker. That is how the declarative config evaluator reached + // production and crash-looped it. + const projectRoot = new URL("../../../", import.meta.url); + const siblingWorkerSpecifier = /["'`](\.\/[^"'`\n]*[Ww]orker[^"'`\n]*)["'`]/g; + + async function* sourceFiles(dir: string): AsyncGenerator { + for await (const entry of Deno.readDir(new URL(dir, projectRoot))) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory) { + if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; + yield* sourceFiles(path); + } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")) { + yield path; + } + } + } + + const includeFlags = getIncludeFlags(); + const isCovered = (path: string) => + includeFlags.some((include) => path === include || path.startsWith(`${include}/`)); + + const uncovered: string[] = []; + for (const root of ["src", "extensions"]) { + for await (const file of sourceFiles(root)) { + const source = await Deno.readTextFile(new URL(file, projectRoot)); + // Only files that actually construct a Worker; a sibling URL elsewhere + // is not a worker entrypoint and does not need embedding. + if (!source.includes("new Worker(")) continue; + + for (const match of source.matchAll(siblingWorkerSpecifier)) { + const specifier = match[1] ?? ""; + // A static `import`/`export ... from` specifier is traced by compile + // and needs no include. Only a specifier reached some other way -- + // assigned to a variable, or built inline for `new URL` -- is opaque. + if (/(?:from|import)\s*\(?\s*$/.test(source.slice(0, match.index))) continue; + // Normalise `./name${extension}` and `./name.js` to the `.ts` source. + const entry = specifier + .replace(/\$\{[^}]*\}/g, "") + .replace(/\.(?:ts|js)$/, ""); + const resolved = `${file.slice(0, file.lastIndexOf("/"))}/${entry.slice(2)}.ts`; + if (!isCovered(resolved)) uncovered.push(`${file} -> ${resolved}`); + } + } + } + + assertEquals( + uncovered, + [], + `Worker entrypoints spawned via a sibling URL must be listed in compile-binary.ts DEFAULT_INCLUDES, or they are absent from the compiled binary and crash it at runtime:\n${ + uncovered.join("\n") + }`, + ); + }); + it("should include src/rendering/rsc for client hydration scripts", () => { // Regression: client-boot.ts and client-dom.ts must be embedded in the // compiled binary, otherwise RSC hydration fails with From 335373d179fd211767aa102fdd0059e0be965335 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:02:54 +0200 Subject: [PATCH 2/7] release: v0.1.1203 Cuts a release carrying the compiled-binary worker fix, so a fixed artifact exists to promote. Production currently runs 20260727215958-09eda661f829 after the rollback, and deploys keep failing on the release-asset manifest schema skew until a newer artifact ships. --- deno.json | 2 +- .../hydration-script-builder/hydration-runtime.generated.ts | 2 +- src/utils/version-constant.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deno.json b/deno.json index b7702d7b89..0c8f6c50e3 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "veryfront", - "version": "0.1.1202", + "version": "0.1.1203", "license": "Apache-2.0", "nodeModulesDir": "auto", "minimumDependencyAge": { diff --git a/src/html/hydration-script-builder/hydration-runtime.generated.ts b/src/html/hydration-script-builder/hydration-runtime.generated.ts index d9db2a88d2..1e9b2dc796 100644 --- a/src/html/hydration-script-builder/hydration-runtime.generated.ts +++ b/src/html/hydration-script-builder/hydration-runtime.generated.ts @@ -8,4 +8,4 @@ */ export const HYDRATION_RUNTIME_BUNDLE: string = - '// src/html/hydration-script-builder/runtime/main.ts\nimport * as React from "react";\nimport { createRoot } from "react-dom/client";\nimport { RouterProvider, useRouter as useRouterFromModule } from "veryfront/router";\nimport * as RouterRuntime from "veryfront/router";\nimport { PageContextProvider } from "veryfront/context";\n\n// src/routing/flatten-route-params.ts\nfunction flattenRouteParams(params) {\n if (!params) return {};\n const flat = {};\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0) continue;\n flat[key] = Array.isArray(value) ? value.join("/") : value;\n }\n return flat;\n}\n\n// src/html/hydration-script-builder/runtime/shared.ts\nfunction moduleServerUrl(window) {\n return window.location.origin + "/_vf_modules";\n}\nfunction createLogging(window) {\n const DEBUG = Boolean(\n window.__VERYFRONT_DEBUG__ || new URLSearchParams(window.location.search).has("vf_debug")\n );\n const log = DEBUG ? console.log.bind(console, "[Veryfront]") : () => {\n };\n const logError = console.error.bind(console, "[Veryfront]");\n function logBackgroundFetchFailure(reason, path, error) {\n const message = error?.message ?? String(error);\n log(reason + " failed:", path, message);\n }\n const perfTimers = /* @__PURE__ */ new Map();\n const perfStart = DEBUG ? (label) => {\n perfTimers.set(label, performance.now());\n } : () => {\n };\n const perfEnd = DEBUG ? (label) => {\n const start = perfTimers.get(label);\n if (start === void 0) return 0;\n const duration = performance.now() - start;\n perfTimers.delete(label);\n console.log(\n "[Veryfront Perf] %c" + label + ": %c" + duration.toFixed(2) + "ms",\n "color: #888",\n duration > 100 ? "color: #f00; font-weight: bold" : "color: #0a0"\n );\n return duration;\n } : () => 0;\n return { DEBUG, log, logError, logBackgroundFetchFailure, perfStart, perfEnd };\n}\nfunction isAbortError(error) {\n return error?.name === "AbortError";\n}\nfunction resolveDocumentNavigationUrl(target, origin) {\n try {\n const url = new URL(target, origin);\n if (url.protocol === "http:" || url.protocol === "https:") return url.href;\n } catch (_) {\n }\n return null;\n}\nfunction getDocumentNonce(document2) {\n const element = document2.querySelector("script[nonce], style[nonce], link[nonce]");\n if (!element) return void 0;\n return element.nonce || element.getAttribute("nonce") || void 0;\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/html/hydration-script-builder/runtime/hydration-data.ts\nfunction readInitialHydrationData(document2) {\n try {\n const element = findServerHydrationDataElement(document2);\n return JSON.parse(element && element.textContent ? element.textContent : "{}") || {};\n } catch (_) {\n return {};\n }\n}\nfunction readDocumentDependencyPinningCacheKey(initialHydrationData2) {\n return typeof initialHydrationData2.dependencyPinningCacheKey === "string" && initialHydrationData2.dependencyPinningCacheKey.startsWith("on:") ? initialHydrationData2.dependencyPinningCacheKey : null;\n}\n\n// src/html/hydration-script-builder/runtime/snapshot-modules.ts\nvar RECOVERY_STATE_KEY = "__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";\nasync function isDependencySnapshotConflictResponse(response) {\n if (!response || response.status !== 409) return false;\n try {\n const clone = response.clone?.() ?? response;\n const body = (await clone.text?.() ?? "").trim();\n return body === "Unknown dependency snapshot" || body === "export default null; // Unknown dependency snapshot";\n } catch (_) {\n return false;\n }\n}\nfunction createSnapshotModuleImporter(deps) {\n async function recoverFromSnapshotBoundModuleFailure(moduleUrl, allowDocumentReload = true) {\n try {\n const parsedUrl = new URL(moduleUrl, "http://veryfront.local");\n const snapshotKeys = parsedUrl.searchParams.getAll("pins");\n const pathMatch = parsedUrl.pathname.match(\n /^\\/_vf_modules\\/_pins\\/([^/]+)(?:\\/|$)/\n );\n if (pathMatch) {\n try {\n snapshotKeys.push(decodeURIComponent(pathMatch[1]));\n } catch (_) {\n return false;\n }\n }\n if (snapshotKeys.length !== 1 || !/^on:[A-Za-z0-9._-]+$/.test(snapshotKeys[0])) return false;\n const response = await deps.fetchModule(moduleUrl, { cache: "no-store" });\n if (!await isDependencySnapshotConflictResponse(response)) return false;\n if (!allowDocumentReload) return true;\n if (deps.recoveryState[RECOVERY_STATE_KEY] === true) return true;\n deps.recoveryState[RECOVERY_STATE_KEY] = true;\n try {\n deps.reloadDocument();\n } catch (_) {\n delete deps.recoveryState[RECOVERY_STATE_KEY];\n return false;\n }\n return true;\n } catch (_) {\n return false;\n }\n }\n async function importSnapshotBoundModule(moduleUrl, allowDocumentReload = true) {\n try {\n return await deps.importModule(moduleUrl);\n } catch (error) {\n const snapshotConflict = await recoverFromSnapshotBoundModuleFailure(\n moduleUrl,\n allowDocumentReload\n );\n if (snapshotConflict && !allowDocumentReload) {\n const conflictError = new Error(\n "Dependency snapshot is unavailable during speculative module prefetch"\n );\n conflictError.name = "DependencySnapshotConflictError";\n conflictError.dependencySnapshotConflict = true;\n conflictError.cause = error;\n throw conflictError;\n }\n throw error;\n }\n }\n return { importSnapshotBoundModule, recoverFromSnapshotBoundModuleFailure };\n}\nfunction isDependencySnapshotConflict(error) {\n return Boolean(error?.dependencySnapshotConflict);\n}\n\n// src/utils/version-constant.ts\nvar VERSION = "0.1.1202";\n\n// src/html/hydration-script-builder/runtime/module-urls.ts\nfunction appendQueryParam(url, key, value) {\n return url + (url.includes("?") ? "&" : "?") + key + "=" + value;\n}\nfunction appendDependencyPinningVersion(url, moduleData) {\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n if (typeof pinKey !== "string" || !pinKey.startsWith("on:")) return url;\n const hashIndex = url.indexOf("#");\n const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";\n const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;\n const queryIndex = withoutHash.indexOf("?");\n const base = queryIndex >= 0 ? withoutHash.slice(0, queryIndex) : withoutHash;\n const params = new URLSearchParams(queryIndex >= 0 ? withoutHash.slice(queryIndex + 1) : "");\n const modulePrefix = "/_vf_modules/";\n const prefixIndex = base.indexOf(modulePrefix);\n const origin = prefixIndex >= 0 ? base.slice(0, prefixIndex) : "";\n if (prefixIndex >= 0 && (origin === "" || /^https?:\\/\\/[^/]+$/i.test(origin))) {\n const pathStart = prefixIndex + modulePrefix.length;\n let modulePath = base.slice(pathStart);\n if (modulePath.startsWith("_pins/")) {\n const existingKeyEnd = modulePath.indexOf("/", "_pins/".length);\n const encodedExistingKey = existingKeyEnd < 0 ? modulePath.slice("_pins/".length) : modulePath.slice("_pins/".length, existingKeyEnd);\n let existingKey;\n try {\n existingKey = decodeURIComponent(encodedExistingKey);\n } catch {\n existingKey = void 0;\n }\n if (existingKey && /^on:[A-Za-z0-9._-]+$/.test(existingKey)) {\n if (existingKeyEnd < 0) return url;\n modulePath = modulePath.slice(existingKeyEnd + 1);\n }\n }\n params.delete("pins");\n const query = params.toString();\n return base.slice(0, pathStart) + "_pins/" + encodeURIComponent(pinKey) + "/" + modulePath + (query ? "?" + query : "") + hash;\n }\n params.set("pins", pinKey);\n return base + "?" + params.toString() + hash;\n}\nfunction componentCacheKey(path, moduleData) {\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n return typeof pinKey === "string" && pinKey.startsWith("on:") ? path + "|vf_pins|" + pinKey : path;\n}\nfunction normalizeReleaseAssetModulePath(path) {\n return String(path || "").replace(/^\\/?_vf_modules\\//, "").replace(/^\\/+/, "").replace(/[?#].*$/, "");\n}\nfunction buildPinnedRscModuleUrl(path, moduleData) {\n let moduleUrl = "/_veryfront/rsc/module?rel=" + encodeURIComponent(path);\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n if (typeof pinKey === "string" && pinKey.startsWith("on:")) {\n moduleUrl += "&pins=" + encodeURIComponent(pinKey);\n }\n return moduleUrl;\n}\nfunction buildPageDataEndpoint(path, origin) {\n const targetUrl = new URL(path, origin);\n const normalizedPath = targetUrl.pathname === "/" ? "" : targetUrl.pathname.replace(/^\\//, "");\n const endpointUrl = new URL(\n "/_veryfront/page-data/" + normalizedPath + ".json",\n origin\n );\n endpointUrl.search = targetUrl.search;\n return endpointUrl.pathname + endpointUrl.search;\n}\nfunction pageDataCacheIdentity(path, documentDependencyPinningCacheKey2) {\n return documentDependencyPinningCacheKey2 ? documentDependencyPinningCacheKey2 + "|path:" + path : path;\n}\nfunction assertPageDataMatchesDocumentSnapshot(path, data, documentDependencyPinningCacheKey2) {\n if (!documentDependencyPinningCacheKey2) return data;\n if (data && data.dependencyPinningCacheKey === documentDependencyPinningCacheKey2) {\n return data;\n }\n const error = new Error("Page data dependency snapshot does not match the document");\n error.status = 409;\n error.dependencySnapshotMismatch = true;\n error.path = path;\n throw error;\n}\n\n// src/html/hydration-script-builder/runtime/component-loader.ts\nvar VERYFRONT_RUNTIME_VERSION = VERSION;\nfunction createComponentLoader(deps) {\n const { window, moduleServerUrl: moduleServerUrl2 } = deps;\n const { DEBUG, log, logError } = deps.logging;\n const componentCache = /* @__PURE__ */ new Map();\n const loadingPromises = /* @__PURE__ */ new Map();\n let releaseId = null;\n let releaseAssetModules = null;\n let studioEmbed = false;\n let hmrRefreshTimestamp = null;\n function clearComponentCache(path) {\n if (!path) {\n componentCache.clear();\n loadingPromises.clear();\n log("Cleared all component caches");\n return;\n }\n for (const key of componentCache.keys()) {\n if (key === path || key.startsWith(path + "|vf_pins|")) {\n componentCache.delete(key);\n }\n }\n for (const key of loadingPromises.keys()) {\n if (key === path || key.startsWith(path + "|vf_pins|")) {\n loadingPromises.delete(key);\n }\n }\n log("Cleared component cache for:", path);\n }\n function setReleaseId(value) {\n releaseId = typeof value === "string" && value ? value : null;\n window.__veryfrontReleaseId = releaseId;\n }\n function appendReleaseModuleVersion(url) {\n if (!releaseId || url.includes("vf_release=")) return url;\n let versionedUrl = appendQueryParam(url, "vf_release", encodeURIComponent(releaseId));\n versionedUrl = appendQueryParam(\n versionedUrl,\n "vf_runtime",\n encodeURIComponent(VERYFRONT_RUNTIME_VERSION)\n );\n return versionedUrl;\n }\n function setReleaseAssetModules(value) {\n releaseAssetModules = value && typeof value === "object" && !Array.isArray(value) ? value : null;\n window.__veryfrontReleaseAssetModules = releaseAssetModules;\n }\n function resolveReleaseAssetModuleUrl(path) {\n if (!releaseAssetModules || studioEmbed || hmrRefreshTimestamp) return null;\n const key = normalizeReleaseAssetModulePath(path);\n if (releaseAssetModules[key]) return releaseAssetModules[key];\n const withoutExt = key.replace(/\\.(tsx|ts|jsx|mdx|js|mjs)$/, "");\n const extensions = [".tsx", ".ts", ".jsx", ".mdx", ".js"];\n for (const ext of extensions) {\n const candidate = withoutExt + ext;\n if (releaseAssetModules[candidate]) return releaseAssetModules[candidate];\n }\n return null;\n }\n function pathToModuleUrl(path, embedInStudio, moduleData) {\n const releaseAssetUrl = resolveReleaseAssetModuleUrl(path);\n if (releaseAssetUrl) return releaseAssetUrl;\n const pattern = /(pages|components|app|lib|layouts|shared|features)\\/(.+)\\.(tsx|ts|jsx|mdx)$/;\n const match = path.match(new RegExp("/" + pattern.source)) || path.match(new RegExp("^" + pattern.source));\n let url;\n if (match) {\n url = moduleServerUrl2 + "/" + match[1] + "/" + match[2] + ".js";\n } else {\n const hasKnownExt = /\\.(tsx|ts|jsx|mdx|js|mjs)$/.test(path);\n url = moduleServerUrl2 + "/" + (hasKnownExt ? path.replace(/\\.(tsx|ts|jsx|mdx)$/, ".js") : path + ".js");\n }\n if (embedInStudio) url = appendQueryParam(url, "studio_embed", "true");\n if (hmrRefreshTimestamp) url = appendQueryParam(url, "t", hmrRefreshTimestamp);\n if (!embedInStudio && !hmrRefreshTimestamp) url = appendReleaseModuleVersion(url);\n url = appendDependencyPinningVersion(url, moduleData);\n return url;\n }\n function setStudioEmbed(value) {\n studioEmbed = value;\n window.__veryfrontStudioEmbed = value;\n }\n function setHMRRefreshTimestamp(timestamp) {\n hmrRefreshTimestamp = timestamp;\n window.__veryfrontHMRRefreshTimestamp = timestamp;\n }\n async function loadComponent(path, moduleData, options = {}) {\n if (!path) return null;\n const cacheKey = componentCacheKey(path, moduleData);\n if (componentCache.has(cacheKey)) {\n log("Component cached:", path);\n return componentCache.get(cacheKey);\n }\n const existingPromise = loadingPromises.get(cacheKey);\n if (existingPromise) return existingPromise;\n const loadPromise = (async () => {\n try {\n const moduleUrl = pathToModuleUrl(path, studioEmbed, moduleData);\n const start = DEBUG ? performance.now() : 0;\n log("Loading component:", moduleUrl);\n const module = await deps.snapshotModules.importSnapshotBoundModule(\n moduleUrl,\n options.allowDocumentReload !== false\n );\n const component = module.MDXLayout || module.MainLayout || module.default || module;\n if (DEBUG) {\n const duration = performance.now() - start;\n console.log(\n "[Veryfront Perf] %cimport:" + path.split("/").pop() + ": %c" + duration.toFixed(2) + "ms",\n "color: #888",\n duration > 50 ? "color: #f00; font-weight: bold" : "color: #0a0"\n );\n }\n componentCache.set(cacheKey, component);\n return component;\n } catch (error) {\n if (isDependencySnapshotConflict(error)) throw error;\n logError("Failed to load component:", path, error);\n return null;\n } finally {\n loadingPromises.delete(cacheKey);\n }\n })();\n loadingPromises.set(cacheKey, loadPromise);\n return loadPromise;\n }\n return {\n loadComponent,\n pathToModuleUrl,\n clearComponentCache,\n setStudioEmbed,\n setReleaseId,\n setReleaseAssetModules,\n setHMRRefreshTimestamp\n };\n}\n\n// src/html/hydration-script-builder/runtime/route-timing.ts\nvar MAX_ROUTE_TIMINGS = 100;\nvar MAX_SERVER_TIMING_LENGTH = 1024;\nfunction routeTimingNow() {\n return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();\n}\nfunction sanitizeServerTimingMetricName(name) {\n return String(name || "").trim().replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 128);\n}\nfunction sanitizeServerTimingHeader(value) {\n if (!value) return null;\n const metrics = [];\n const printable = String(value).replace(/[^\\x20-\\x7E]/g, " ").trim();\n if (!printable) return null;\n for (const item of printable.split(",")) {\n const segments = item.split(";").map((segment) => segment.trim()).filter(Boolean);\n const name = sanitizeServerTimingMetricName(segments[0]);\n if (!name) continue;\n for (const segment of segments.slice(1)) {\n const [key, rawValue = ""] = segment.split("=");\n if ((key ?? "").trim().toLowerCase() !== "dur") continue;\n const duration = Number(rawValue.trim().replace(/^"|"$/g, ""));\n if (!Number.isFinite(duration) || duration < 0) continue;\n metrics.push(name + ";dur=" + (Math.round(duration * 100) / 100).toFixed(2));\n break;\n }\n }\n const sanitized = metrics.join(", ");\n return sanitized ? sanitized.slice(0, MAX_SERVER_TIMING_LENGTH) : null;\n}\nfunction parseServerTimingMetrics(value) {\n const header = sanitizeServerTimingHeader(value);\n if (!header) return null;\n const metrics = {};\n for (const item of header.split(",")) {\n const segments = item.split(";").map((segment) => segment.trim()).filter(Boolean);\n const name = sanitizeServerTimingMetricName(segments[0]);\n if (!name) continue;\n for (const segment of segments.slice(1)) {\n const [key, rawValue = ""] = segment.split("=");\n if ((key ?? "").trim().toLowerCase() !== "dur") continue;\n const duration = Number(rawValue.trim().replace(/^"|"$/g, ""));\n if (Number.isFinite(duration) && duration >= 0) {\n metrics[name] = Math.round(duration * 100) / 100;\n }\n }\n }\n return Object.keys(metrics).length ? metrics : null;\n}\nfunction readResponseServerTiming(response) {\n try {\n return sanitizeServerTimingHeader(response.headers?.get("server-timing"));\n } catch (_) {\n return null;\n }\n}\nfunction roundRouteTimingValue(value) {\n return Math.round(value * 100) / 100;\n}\nfunction extractResourceTiming(entry) {\n const fields = [\n "startTime",\n "requestStart",\n "responseStart",\n "responseEnd",\n "duration",\n "transferSize",\n "encodedBodySize",\n "decodedBodySize"\n ];\n const timing = {};\n for (const field of fields) {\n const value = entry?.[field];\n if (typeof value === "number" && Number.isFinite(value) && value >= 0) {\n timing[field] = roundRouteTimingValue(value);\n }\n }\n return Object.keys(timing).length ? timing : null;\n}\nfunction createRouteTimingRecorder(window, logging2) {\n const { log } = logging2;\n function emitRouteTiming(phase, path, startedAt, detail = {}) {\n const entry = {\n phase,\n path,\n duration: Math.max(0, routeTimingNow() - startedAt),\n timestamp: Date.now(),\n ...detail\n };\n const timings = Array.isArray(window.__veryfrontRouteTimings) ? window.__veryfrontRouteTimings : [];\n timings.push(entry);\n if (timings.length > MAX_ROUTE_TIMINGS) {\n timings.splice(0, timings.length - MAX_ROUTE_TIMINGS);\n }\n window.__veryfrontRouteTimings = timings;\n try {\n window.dispatchEvent(new CustomEvent("veryfront:route-timing", { detail: entry }));\n } catch (_) {\n }\n log("Route timing:", entry);\n return entry;\n }\n function getPageDataResourceTiming(endpoint, fetchStartedAt) {\n try {\n if (typeof performance === "undefined" || typeof performance.getEntriesByName !== "function") {\n return null;\n }\n const href = new URL(endpoint, window.location.href).href;\n const entries = performance.getEntriesByName(href, "resource");\n if (!entries.length) return null;\n for (let index = entries.length - 1; index >= 0; index--) {\n const entry = entries[index];\n const responseEnd = entry?.responseEnd;\n if (typeof responseEnd === "number" && Number.isFinite(responseEnd) && responseEnd + 1 >= fetchStartedAt) {\n return extractResourceTiming(entry);\n }\n }\n return null;\n } catch (_) {\n return null;\n }\n }\n function buildPageDataTimingDetail(response, endpoint, fetchStartedAt, source) {\n const detail = { source, status: response.status };\n const serverTiming = readResponseServerTiming(response);\n if (serverTiming) {\n detail.serverTiming = serverTiming;\n const serverTimingMetrics = parseServerTimingMetrics(serverTiming);\n if (serverTimingMetrics) detail.serverTimingMetrics = serverTimingMetrics;\n }\n const resourceTiming = getPageDataResourceTiming(response.url || endpoint, fetchStartedAt);\n if (resourceTiming) detail.resourceTiming = resourceTiming;\n return detail;\n }\n return { emitRouteTiming, buildPageDataTimingDetail };\n}\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\nfunction handoffClientRouteMetadata(metadata, targetDocument = document) {\n const retainedTitle = targetDocument.title;\n retireClientHeadOwnership(targetDocument);\n updateRouteTitle(\n typeof metadata.title === "string" && metadata.title ? metadata.title : retainedTitle,\n targetDocument\n );\n updateRouteMetaTags(metadata, targetDocument);\n}\n\n// src/html/hydration-script-builder/runtime/router.ts\nvar FETCH_TIMEOUT_MS = 1e4;\nvar MAX_RETRIES = 2;\nvar MAX_CACHE_SIZE = 50;\nvar CACHE_TTL_MS = 5 * 60 * 1e3;\nvar BACKGROUND_REFRESH_INTERVAL_MS = 30 * 1e3;\nvar PREFETCH_DELAY_MS = 100;\nvar MAX_PREFETCH_PATHS = 100;\nvar IDLE_PREFETCH_DELAY_MS = 1200;\nvar IDLE_PREFETCH_MAX_LINKS = 4;\nvar VIEWPORT_PREFETCH_MAX_LINKS = 8;\nvar PAGE_DATA_PREFETCH_CONCURRENCY = 2;\nvar VIEWPORT_PREFETCH_ROOT_MARGIN = "200px";\nvar MAX_SCROLL_POSITIONS = 100;\nfunction createRouterRuntime(deps) {\n const { env: env2, logging: logging2, routeTiming: routeTiming2, componentLoader: componentLoader2, snapshotModules: snapshotModules2 } = deps;\n const { window, document: document2, React: React2, RouterProvider: RouterProvider2, PageContextProvider: PageContextProvider2 } = env2;\n const { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = env2;\n const { log, logError, logBackgroundFetchFailure, perfStart, perfEnd } = logging2;\n const { emitRouteTiming, buildPageDataTimingDetail } = routeTiming2;\n const { loadComponent } = componentLoader2;\n const documentPinKey = deps.documentDependencyPinningCacheKey;\n let hydrationResolve;\n let hydrationReject;\n const hydrationPromise = new Promise((resolve, reject) => {\n hydrationResolve = resolve;\n hydrationReject = reject;\n });\n let hydrationCompleted = false;\n let hydrationFailed = false;\n function signalHydrationComplete() {\n hydrationCompleted = true;\n hydrationResolve();\n log("Hydration complete signal received");\n }\n function signalHydrationFailed(error) {\n hydrationFailed = true;\n hydrationReject(error);\n logError("Hydration failed signal received:", error);\n }\n window.__veryfrontHydrationComplete = signalHydrationComplete;\n window.__veryfrontHydrationFailed = signalHydrationFailed;\n function pageDataCacheIdentity2(path) {\n return pageDataCacheIdentity(path, documentPinKey);\n }\n function navigateDocument(target) {\n const safeUrl = resolveDocumentNavigationUrl(target, window.location.origin);\n if (safeUrl) {\n window.location.href = safeUrl;\n return;\n }\n logError("Refusing an unsafe document navigation:", target);\n window.location.reload();\n }\n let clientBuildVersion = null;\n function checkVersionMismatch(newVersion) {\n if (!clientBuildVersion) {\n clientBuildVersion = newVersion;\n log("Build version initialized:", newVersion);\n return false;\n }\n if (newVersion.serverStart !== clientBuildVersion.serverStart) {\n log("Server restarted, reloading...", {\n old: clientBuildVersion.serverStart,\n new: newVersion.serverStart\n });\n return true;\n }\n if (newVersion.framework !== clientBuildVersion.framework) {\n log("Framework version changed, reloading...", {\n old: clientBuildVersion.framework,\n new: newVersion.framework\n });\n return true;\n }\n if (newVersion.projectUpdated && clientBuildVersion.projectUpdated && newVersion.projectUpdated !== clientBuildVersion.projectUpdated) {\n log("Project content updated, reloading...", {\n old: clientBuildVersion.projectUpdated,\n new: newVersion.projectUpdated\n });\n return true;\n }\n return false;\n }\n const pageDataCache = /* @__PURE__ */ new Map();\n const pendingPageDataFetches = /* @__PURE__ */ new Map();\n const backgroundRefreshTimestamps = /* @__PURE__ */ new Map();\n function getCachedPageData(path) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const entry = pageDataCache.get(cacheIdentity);\n if (!entry) return null;\n if (Date.now() - entry.timestamp < CACHE_TTL_MS) return entry.data;\n pageDataCache.delete(cacheIdentity);\n backgroundRefreshTimestamps.delete(cacheIdentity);\n return null;\n }\n function setCachedPageData(path, data) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n if (pageDataCache.size >= MAX_CACHE_SIZE) {\n const oldest = pageDataCache.keys().next().value;\n if (oldest) {\n pageDataCache.delete(oldest);\n backgroundRefreshTimestamps.delete(oldest);\n }\n }\n pageDataCache.set(cacheIdentity, { data, timestamp: Date.now() });\n }\n const scrollPositions = /* @__PURE__ */ new Map();\n function saveScrollPosition(path) {\n if (scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = scrollPositions.keys().next().value;\n if (oldest) scrollPositions.delete(oldest);\n }\n scrollPositions.set(path, window.scrollY);\n }\n function restoreScrollPosition(path) {\n const savedY = scrollPositions.get(path);\n if (savedY === void 0) return false;\n requestAnimationFrame(() => window.scrollTo(0, savedY));\n return true;\n }\n let progressBar = null;\n let progressTimeout = null;\n function showNavigationProgress() {\n if (!progressBar) {\n progressBar = document2.createElement("div");\n progressBar.id = "vf-nav-progress";\n progressBar.style.cssText = "position:fixed;top:0;left:0;height:3px;width:0;background:linear-gradient(90deg,#0066ff,#00aaff);z-index:99999;transition:width 0.3s ease-out,opacity 0.2s;opacity:1;";\n document2.body.prepend(progressBar);\n }\n progressBar.style.opacity = "1";\n progressBar.style.width = "30%";\n progressTimeout = setTimeout2(() => {\n if (progressBar?.style) progressBar.style.width = "70%";\n }, 300);\n document2.body.setAttribute("aria-busy", "true");\n }\n function hideNavigationProgress() {\n if (progressTimeout) {\n clearTimeout2(progressTimeout);\n progressTimeout = null;\n }\n if (progressBar) {\n progressBar.style.width = "100%";\n setTimeout2(() => {\n if (!progressBar) return;\n progressBar.style.opacity = "0";\n setTimeout2(() => {\n if (progressBar) progressBar.style.width = "0";\n }, 200);\n }, 150);\n }\n document2.body.removeAttribute("aria-busy");\n }\n let currentAbortController = null;\n function sleep(ms) {\n return new Promise((resolve) => setTimeout2(resolve, ms));\n }\n async function fetchWithRetry(url, options, maxRetries = MAX_RETRIES) {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const callerSignal = options.signal;\n const abortFromCaller = () => controller.abort();\n if (callerSignal?.aborted) controller.abort();\n callerSignal?.addEventListener("abort", abortFromCaller, { once: true });\n const timeout = setTimeout2(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const response = await env2.fetch(url, { ...options, signal: controller.signal });\n clearTimeout2(timeout);\n callerSignal?.removeEventListener("abort", abortFromCaller);\n if (response.ok) return response;\n if (response.status >= 500 && attempt < maxRetries) {\n log("Server error, retrying...", response.status);\n await sleep(Math.pow(2, attempt) * 500);\n continue;\n }\n return response;\n } catch (error) {\n clearTimeout2(timeout);\n callerSignal?.removeEventListener("abort", abortFromCaller);\n if (error.name === "AbortError" && callerSignal?.aborted) throw error;\n if (attempt === maxRetries) throw error;\n log("Fetch failed, retrying...", error.message);\n await sleep(Math.pow(2, attempt) * 500);\n }\n }\n throw new Error("Failed to fetch page data");\n }\n async function fetchPageDataFresh(path, signal, options = {}) {\n const {\n triggerReloadOnVersionMismatch = false,\n recordRouteTiming = false,\n timingSource = "network"\n } = options;\n const endpoint = buildPageDataEndpoint(path, window.location.origin);\n const startedAt = recordRouteTiming ? routeTimingNow() : 0;\n log("Fetching page data:", path);\n perfStart("fetch:" + path);\n const headers = options.prefetch ? { "X-Veryfront-Prefetch": "1" } : { "X-Veryfront-Navigation": "spa" };\n if (documentPinKey) {\n headers["X-Veryfront-Dependency-Pins"] = documentPinKey;\n }\n const response = await fetchWithRetry(endpoint, {\n headers,\n signal\n }, options.prefetch ? 0 : MAX_RETRIES);\n if (!response.ok) {\n perfEnd("fetch:" + path);\n if (recordRouteTiming) {\n emitRouteTiming(\n "page-data",\n path,\n startedAt,\n buildPageDataTimingDetail(response, endpoint, startedAt, timingSource)\n );\n }\n const error = new Error("Failed to fetch page data: " + response.status);\n error.status = response.status;\n throw error;\n }\n perfStart("parse:" + path);\n const data = assertPageDataMatchesDocumentSnapshot(\n path,\n await response.json(),\n documentPinKey\n );\n perfEnd("parse:" + path);\n perfEnd("fetch:" + path);\n if (recordRouteTiming) {\n emitRouteTiming(\n "page-data",\n path,\n startedAt,\n buildPageDataTimingDetail(response, endpoint, startedAt, timingSource)\n );\n }\n if (triggerReloadOnVersionMismatch) {\n const checkedData = handlePageDataVersionMismatch(path, data);\n if (checkedData !== data) return checkedData;\n }\n setCachedPageData(path, data);\n return data;\n }\n function handlePageDataVersionMismatch(path, data) {\n if (data.buildVersion && checkVersionMismatch(data.buildVersion)) {\n log("Version mismatch detected, performing full page reload to:", path);\n navigateDocument(path);\n return new Promise(() => {\n });\n }\n return data;\n }\n function startPageDataFetch(path, signal, options = {}) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const request = fetchPageDataFresh(path, signal, options).finally(() => {\n if (options.trackPending !== false && pendingPageDataFetches.get(cacheIdentity) === request) {\n pendingPageDataFetches.delete(cacheIdentity);\n }\n });\n if (options.trackPending !== false) {\n pendingPageDataFetches.set(cacheIdentity, request);\n }\n return request;\n }\n function fetchPageDataDeduped(path) {\n const pending = pendingPageDataFetches.get(pageDataCacheIdentity2(path));\n if (pending) return pending;\n return startPageDataFetch(path, null);\n }\n function refreshPageDataInBackground(path) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const lastRefreshAt = backgroundRefreshTimestamps.get(cacheIdentity) || 0;\n const now = Date.now();\n if (now - lastRefreshAt < BACKGROUND_REFRESH_INTERVAL_MS) return;\n backgroundRefreshTimestamps.set(cacheIdentity, now);\n fetchPageDataDeduped(path).catch((error) => {\n logBackgroundFetchFailure("Stale page data refresh", path, error);\n });\n }\n async function fetchPageDataForNavigation(path, signal) {\n const startedAt = routeTimingNow();\n const cached = getCachedPageData(path);\n if (cached) {\n log("Using cached page data:", path);\n refreshPageDataInBackground(path);\n emitRouteTiming("page-data", path, startedAt, { source: "cache" });\n return cached;\n }\n const pending = pendingPageDataFetches.get(pageDataCacheIdentity2(path));\n if (pending) {\n log("Reusing pending page data fetch for navigation:", path);\n const data = await pending;\n emitRouteTiming("page-data", path, startedAt, { source: "deduped" });\n return handlePageDataVersionMismatch(path, data);\n }\n return startPageDataFetch(path, signal, {\n triggerReloadOnVersionMismatch: true,\n recordRouteTiming: true,\n timingSource: "network"\n });\n }\n function fetchPageDataForPrefetch(path, signal) {\n if (getCachedPageData(path)) return Promise.resolve();\n return startPageDataFetch(path, signal, { prefetch: true, trackPending: false }).then((data) => preloadModulesForPageData(data, path)).catch((error) => {\n if (!isAbortError(error)) {\n logBackgroundFetchFailure("Page data prefetch", path, error);\n }\n throw error;\n });\n }\n let currentPath = window.location.pathname;\n let isNavigating = false;\n async function navigateSPA(href, historyMode = "push", restoreScroll = false) {\n currentAbortController?.abort();\n if (isNavigating) return;\n isNavigating = true;\n const [navigationPath] = href.split("#");\n removeQueuedPrefetch(navigationPath || href);\n abortActiveSpeculativePrefetches();\n currentAbortController = new AbortController();\n const signal = currentAbortController.signal;\n const navigationStartedAt = routeTimingNow();\n showNavigationProgress();\n perfStart("nav:total:" + href);\n try {\n log("SPA navigating to:", href);\n saveScrollPosition(currentPath);\n const [path, hash] = href.split("#");\n const targetPath = path || currentPath;\n perfStart("nav:fetchData:" + href);\n const pageData = await fetchPageDataForNavigation(targetPath, signal);\n perfEnd("nav:fetchData:" + href);\n if (signal.aborted) return;\n if (pageData && pageData.redirect && typeof pageData.redirect.destination === "string") {\n const redirectUrl = resolveDocumentNavigationUrl(\n pageData.redirect.destination,\n window.location.origin\n );\n if (redirectUrl) {\n log("SPA navigation redirect -> " + redirectUrl);\n window.location.href = redirectUrl;\n return;\n }\n }\n if (historyMode === "push") {\n window.history.pushState({ pageData, scrollY: 0 }, "", href);\n } else if (historyMode === "replace") {\n window.history.replaceState({ pageData, scrollY: 0 }, "", href);\n }\n currentPath = targetPath;\n router.pathname = targetPath;\n router.query = Object.fromEntries(new URLSearchParams(window.location.search));\n router.params = flattenRouteParams(pageData.params);\n perfStart("nav:render:" + href);\n await renderPageFromData(pageData, targetPath);\n perfEnd("nav:render:" + href);\n if (restoreScroll) {\n restoreScrollPosition(targetPath);\n } else if (hash) {\n requestAnimationFrame(() => {\n const target = document2.getElementById(hash);\n if (target) {\n target.scrollIntoView({ behavior: "smooth" });\n return;\n }\n window.scrollTo(0, 0);\n });\n } else {\n window.scrollTo(0, 0);\n }\n hideNavigationProgress();\n perfEnd("nav:total:" + href);\n emitRouteTiming("total", targetPath, navigationStartedAt, {\n href,\n historyMode,\n restoreScroll\n });\n log("SPA navigation complete");\n } catch (error) {\n hideNavigationProgress();\n if (error.name === "AbortError") {\n log("Navigation aborted");\n return;\n }\n logError("SPA navigation failed:", error.message);\n if (error.status === 404) {\n logError("Page not found:", href);\n }\n navigateDocument(href);\n } finally {\n isNavigating = false;\n currentAbortController = null;\n processPageDataPrefetchQueue();\n }\n }\n async function loadPageDataComponent(pageData, path, options = {}) {\n if (!pageData.isolatedClientPage) return loadComponent(path, pageData, options);\n const moduleUrl = buildPinnedRscModuleUrl(path, pageData);\n const module = await snapshotModules2.importSnapshotBoundModule(\n moduleUrl,\n options.allowDocumentReload !== false\n );\n return module.MDXLayout || module.MainLayout || module.default || module;\n }\n async function renderPageFromData(pageData, targetPath) {\n if (pageData.requiresFullDocumentNavigation) {\n throw new Error("Server layout requires full document navigation");\n }\n if (window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(pageData.releaseId || null);\n }\n if (window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(pageData.releaseAssetModules || null);\n }\n perfStart("render:loadAll");\n const allPaths = getPageDataModulePaths(pageData);\n const modulesStartedAt = routeTimingNow();\n const components = await Promise.all(\n allPaths.map((path) => loadPageDataComponent(pageData, path))\n );\n emitRouteTiming("modules", targetPath, modulesStartedAt, { count: allPaths.length });\n perfEnd("render:loadAll");\n const [PageComponent, ...rest] = components;\n const ErrorComponent = pageData.errorPath ? rest.pop() : null;\n const AppComponent = pageData.appPath ? rest.pop() : null;\n const LayoutComponents = rest;\n if (!PageComponent) {\n throw new Error("Failed to load page component: " + pageData.pagePath);\n }\n handoffClientRouteMetadata(\n pageData.frontmatter ?? {},\n document2\n );\n if (pageData.css) {\n const existingStyle = document2.getElementById("veryfront-spa-css");\n if (existingStyle) {\n existingStyle.textContent = pageData.css;\n } else {\n const styleEl = document2.createElement("style");\n const nonce = getDocumentNonce(document2);\n if (nonce) styleEl.setAttribute("nonce", nonce);\n styleEl.id = "veryfront-spa-css";\n styleEl.textContent = pageData.css;\n document2.head.appendChild(styleEl);\n }\n log("Injected CSS for SPA navigation", { cssLength: pageData.css.length });\n } else if (pageData.cssAction === "clear") {\n const existingStyle = document2.getElementById("veryfront-spa-css");\n if (existingStyle) {\n existingStyle.remove();\n log("Cleared SPA CSS for release stylesheet navigation");\n }\n }\n const normalizedParams = flattenRouteParams(pageData.params);\n let tree = React2.createElement(PageComponent, {\n ...pageData.props,\n params: normalizedParams\n });\n if (pageData.layouts?.length) {\n for (let i = pageData.layouts.length - 1; i >= 0; i--) {\n const layout = pageData.layouts[i];\n const LayoutComponent = LayoutComponents[i];\n if (!LayoutComponent || !layout) continue;\n const layoutProps = pageData.layoutProps?.[layout.path] || {};\n tree = React2.createElement(LayoutComponent, { ...layoutProps, children: tree });\n }\n }\n if (AppComponent) {\n tree = React2.createElement(AppComponent, { children: tree });\n log("Wrapped with App component for SPA navigation");\n }\n if (ErrorComponent) {\n class AppRouterErrorBoundary extends React2.Component {\n constructor(props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n static getDerivedStateFromError(error) {\n return { hasError: true, error };\n }\n render() {\n if (this.state.hasError) {\n return React2.createElement(ErrorComponent, {\n error: this.state.error,\n reset: () => this.setState({ hasError: false, error: null })\n });\n }\n return this.props.children;\n }\n }\n tree = React2.createElement(AppRouterErrorBoundary, null, tree);\n }\n const headingsArray = pageData.headings || [];\n const pageContext = {\n slug: pageData.slug || "",\n path: pageData.pagePath || targetPath,\n params: normalizedParams,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n frontmatter: pageData.frontmatter || {},\n data: pageData.props || {},\n headings: headingsArray,\n mdxHeadings: headingsArray\n };\n tree = React2.createElement(PageContextProvider2, { pageContext, children: tree });\n tree = React2.createElement(RouterProvider2, { router, children: tree });\n const container = pageData.isolatedClientPage ? document2.getElementById("veryfront-page-island") : document2.getElementById("root");\n if (!hydrationCompleted && !hydrationFailed) {\n log("Waiting for hydration to complete before SPA render...");\n try {\n await Promise.race([\n hydrationPromise,\n new Promise(\n (_, reject) => setTimeout2(() => reject(new Error("Hydration timeout")), 1e4)\n )\n ]);\n } catch (waitError) {\n log("Hydration wait failed:", waitError.message);\n }\n }\n if (container?.__reactRoot) {\n perfStart("render:reactRender");\n container.__reactRoot.render(tree);\n perfEnd("render:reactRender");\n log("Page re-rendered via SPA");\n scheduleRoutePrefetchRefresh();\n return;\n }\n if (hydrationFailed) {\n throw new Error(\n "React root not found - hydration failed, falling back to full page navigation"\n );\n }\n throw new Error("React root not found");\n }\n let prefetchTimeout = null;\n let currentHoverLink = null;\n let routePrefetchRefreshPending = false;\n let viewportPrefetchObserver = null;\n const observedPrefetchLinks = /* @__PURE__ */ new WeakSet();\n const prefetchedPaths = /* @__PURE__ */ new Set();\n const inFlightPrefetches = /* @__PURE__ */ new Set();\n const queuedPrefetchPaths = /* @__PURE__ */ new Set();\n const pageDataPrefetchQueue = [];\n const activePageDataPrefetchControllers = /* @__PURE__ */ new Map();\n function cancelScheduledPrefetch() {\n if (prefetchTimeout) {\n clearTimeout2(prefetchTimeout);\n prefetchTimeout = null;\n }\n currentHoverLink = null;\n }\n function getPageDataModulePaths(pageData) {\n const layoutPaths = (pageData.layouts || []).map((l) => l.path).filter(Boolean);\n const allPaths = [pageData.pagePath, ...layoutPaths].filter(Boolean);\n if (pageData.appPath) allPaths.push(pageData.appPath);\n if (pageData.errorPath) allPaths.push(pageData.errorPath);\n return allPaths;\n }\n function getCurrentRouteHref() {\n return window.location.pathname + window.location.search;\n }\n function getInternalRouteHrefFromLink(link) {\n if (!link || link.target === "_blank" || link.hasAttribute("download") || link.getAttribute("data-prefetch") === "false") {\n return null;\n }\n const href = link.getAttribute("href");\n if (!href || href.startsWith("#") || href.startsWith("//") || !href.startsWith("/")) {\n return null;\n }\n try {\n const url = new URL(href, window.location.origin);\n if (url.origin !== window.location.origin) return null;\n const routeHref = url.pathname + url.search;\n return routeHref === getCurrentRouteHref() ? null : routeHref;\n } catch (_) {\n return null;\n }\n }\n function getEligiblePrefetchLinks(limit) {\n const links = [];\n const seenHrefs = /* @__PURE__ */ new Set();\n for (const link of document2.querySelectorAll("a[href]")) {\n const href = getInternalRouteHrefFromLink(link);\n if (!href || seenHrefs.has(href)) continue;\n seenHrefs.add(href);\n links.push({ link, href });\n if (links.length >= limit) break;\n }\n return links;\n }\n async function preloadModulesForPageData(pageData, path) {\n if (!pageData || pageData.requiresFullDocumentNavigation) return;\n if (pageData.releaseId && window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(pageData.releaseId);\n }\n if (pageData.releaseAssetModules && window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(pageData.releaseAssetModules);\n }\n const modulePaths = getPageDataModulePaths(pageData);\n if (modulePaths.length === 0) return;\n try {\n await Promise.all(\n modulePaths.map(\n (modulePath) => loadPageDataComponent(pageData, modulePath, { allowDocumentReload: false })\n )\n );\n } catch (error) {\n if (isDependencySnapshotConflict(error)) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n pageDataCache.delete(cacheIdentity);\n backgroundRefreshTimestamps.delete(cacheIdentity);\n prefetchedPaths.delete(path);\n throw error;\n }\n logBackgroundFetchFailure("Module prefetch", path, error);\n }\n }\n function removeQueuedPrefetch(path) {\n queuedPrefetchPaths.delete(path);\n for (let i = pageDataPrefetchQueue.length - 1; i >= 0; i--) {\n if (pageDataPrefetchQueue[i] === path) pageDataPrefetchQueue.splice(i, 1);\n }\n }\n function abortActiveSpeculativePrefetches() {\n for (const controller of activePageDataPrefetchControllers.values()) {\n controller.abort();\n }\n }\n function processPageDataPrefetchQueue() {\n if (isNavigating) return;\n while (activePageDataPrefetchControllers.size < PAGE_DATA_PREFETCH_CONCURRENCY && pageDataPrefetchQueue.length > 0) {\n const href = pageDataPrefetchQueue.shift();\n queuedPrefetchPaths.delete(href);\n if (prefetchedPaths.has(href) || inFlightPrefetches.has(href) || getCachedPageData(href)) {\n continue;\n }\n if (prefetchedPaths.size >= MAX_PREFETCH_PATHS) {\n const oldest = prefetchedPaths.values().next().value;\n if (oldest) prefetchedPaths.delete(oldest);\n }\n const controller = new AbortController();\n prefetchedPaths.add(href);\n inFlightPrefetches.add(href);\n activePageDataPrefetchControllers.set(href, controller);\n fetchPageDataForPrefetch(href, controller.signal).catch((error) => {\n prefetchedPaths.delete(href);\n if (isDependencySnapshotConflict(error)) {\n logBackgroundFetchFailure("Module prefetch", href, error);\n }\n }).finally(() => {\n inFlightPrefetches.delete(href);\n activePageDataPrefetchControllers.delete(href);\n processPageDataPrefetchQueue();\n });\n }\n }\n function prefetchPage(href) {\n if (isNavigating) return;\n if (prefetchedPaths.has(href) || inFlightPrefetches.has(href) || queuedPrefetchPaths.has(href)) return;\n const cachedPageData = getCachedPageData(href);\n if (cachedPageData) {\n preloadModulesForPageData(cachedPageData, href).catch((error) => {\n logBackgroundFetchFailure("Module prefetch", href, error);\n });\n return;\n }\n queuedPrefetchPaths.add(href);\n pageDataPrefetchQueue.push(href);\n processPageDataPrefetchQueue();\n }\n function prefetchEligibleRouteLinks(limit) {\n for (const { href } of getEligiblePrefetchLinks(limit)) {\n prefetchPage(href);\n }\n }\n function ensureViewportPrefetchObserver() {\n if (viewportPrefetchObserver || typeof IntersectionObserver !== "function") {\n return viewportPrefetchObserver;\n }\n viewportPrefetchObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n viewportPrefetchObserver?.unobserve(entry.target);\n const href = getInternalRouteHrefFromLink(\n entry.target\n );\n if (href) prefetchPage(href);\n }\n }, { rootMargin: VIEWPORT_PREFETCH_ROOT_MARGIN });\n return viewportPrefetchObserver;\n }\n function observeViewportPrefetchLinks() {\n const observer = ensureViewportPrefetchObserver();\n if (!observer) return;\n for (const { link } of getEligiblePrefetchLinks(VIEWPORT_PREFETCH_MAX_LINKS)) {\n if (observedPrefetchLinks.has(link)) continue;\n observedPrefetchLinks.add(link);\n observer.observe(link);\n }\n }\n function runRoutePrefetchRefresh() {\n routePrefetchRefreshPending = false;\n prefetchEligibleRouteLinks(IDLE_PREFETCH_MAX_LINKS);\n observeViewportPrefetchLinks();\n }\n function scheduleRoutePrefetchRefresh() {\n if (routePrefetchRefreshPending) return;\n routePrefetchRefreshPending = true;\n setTimeout2(() => {\n if (typeof requestIdleCallback === "function") {\n requestIdleCallback(runRoutePrefetchRefresh, { timeout: IDLE_PREFETCH_DELAY_MS });\n return;\n }\n runRoutePrefetchRefresh();\n }, IDLE_PREFETCH_DELAY_MS);\n }\n const router = {\n domain: window.location.origin,\n path: window.location.pathname,\n push: (path) => {\n void navigateSPA(path, "push");\n },\n replace: (path) => {\n void navigateSPA(path, "replace");\n },\n back: () => {\n window.history.back();\n },\n forward: () => {\n window.history.forward();\n },\n prefetch: (path) => {\n prefetchPage(path);\n },\n pathname: window.location.pathname,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n // Seed route params from the hydration data (issue #2741). Catch-all\n // segments arrive as arrays and are joined so no path info is lost.\n params: flattenRouteParams(deps.initialHydrationData.params || {}),\n isPreview: false,\n isMounted: true,\n navigate: (path) => navigateSPA(path, "push"),\n reload: () => window.location.reload()\n };\n window.__veryfrontRouter = router;\n if (deps.navigationStoreUsesRegistryFallback) {\n log("Router runtime does not export getNavigationStore; using shared v1 registry fallback");\n }\n if (typeof deps.getNavigationStore === "function") {\n deps.getNavigationStore().setNavigator((href, options) => {\n const mode = options && options.history;\n const historyMode = mode === "replace" ? "replace" : mode === "none" ? "none" : "push";\n return navigateSPA(href, historyMode);\n });\n }\n window.addEventListener("popstate", async (e) => {\n const path = window.location.pathname;\n log("Popstate:", path);\n saveScrollPosition(currentPath);\n if (!e.state?.pageData) {\n await navigateSPA(path, "none", true);\n return;\n }\n showNavigationProgress();\n try {\n currentPath = path;\n router.pathname = path;\n router.query = Object.fromEntries(new URLSearchParams(window.location.search));\n router.params = flattenRouteParams(e.state.pageData.params);\n await renderPageFromData(e.state.pageData, path);\n restoreScrollPosition(path);\n hideNavigationProgress();\n } catch (error) {\n hideNavigationProgress();\n logError("Popstate render failed:", error.message);\n window.location.reload();\n }\n });\n document2.addEventListener("click", (e) => {\n const link = e.target?.closest("a[href]");\n if (!link) return;\n const href = link.getAttribute("href");\n if (!href) return;\n if (href.startsWith("#")) {\n const target = document2.getElementById(href.slice(1));\n if (!target) return;\n e.preventDefault();\n target.scrollIntoView({ behavior: "smooth" });\n window.history.pushState(null, "", href);\n return;\n }\n if (link.target === "_blank" || link.hasAttribute("download") || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || !href.startsWith("/") || href.startsWith("//")) {\n return;\n }\n e.preventDefault();\n cancelScheduledPrefetch();\n void navigateSPA(href, "push");\n });\n document2.addEventListener(\n "mouseenter",\n (e) => {\n if (!e.target || typeof e.target.closest !== "function") return;\n const link = e.target.closest("a[href]");\n if (!link) return;\n const href = getInternalRouteHrefFromLink(link);\n if (!href) return;\n if (currentHoverLink === link) return;\n if (prefetchTimeout) {\n clearTimeout2(prefetchTimeout);\n prefetchTimeout = null;\n }\n currentHoverLink = link;\n prefetchTimeout = setTimeout2(() => {\n prefetchPage(href);\n prefetchTimeout = null;\n }, PREFETCH_DELAY_MS);\n },\n true\n );\n document2.addEventListener(\n "mouseleave",\n (e) => {\n if (!e.target || typeof e.target.closest !== "function") return;\n const relatedTarget = e.relatedTarget;\n if (currentHoverLink && relatedTarget && currentHoverLink.contains(relatedTarget)) return;\n cancelScheduledPrefetch();\n },\n true\n );\n if (document2.readyState === "loading") {\n document2.addEventListener("DOMContentLoaded", scheduleRoutePrefetchRefresh, { once: true });\n } else {\n scheduleRoutePrefetchRefresh();\n }\n window.useRouter = () => {\n try {\n return env2.useRouterFromModule();\n } catch (_) {\n return window.__veryfrontRouter;\n }\n };\n return {\n router,\n navigateSPA,\n renderPageFromData,\n prefetchPage,\n signalHydrationComplete,\n signalHydrationFailed\n };\n}\n\n// src/html/hydration-script-builder/runtime/renderer.ts\nfunction isModuleNotFoundError(error) {\n if (!error) return false;\n if (error instanceof SyntaxError) return false;\n const message = String(error.message || error);\n return /(?:dynamically imported module|Importing a module script failed|Failed to load module script)/i.test(message);\n}\nfunction preferReachedModuleError(earlier, later) {\n if (!earlier) return later;\n if (!later) return earlier;\n if (isModuleNotFoundError(earlier) && !isModuleNotFoundError(later)) return later;\n return earlier;\n}\nasync function loadPageModuleWithIndexFallback(basePath, pageSlug, pageModuleError, importModule) {\n try {\n return await importModule(basePath + ".js");\n } catch (error) {\n const routeError = preferReachedModuleError(pageModuleError, error);\n if (pageSlug === "index" || pageSlug.endsWith("/index")) throw routeError;\n try {\n return await importModule(basePath + "/index.js");\n } catch (indexError) {\n throw preferReachedModuleError(routeError, indexError);\n }\n }\n}\nfunction isAppRouterPath(path, appRouterRoot) {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n return normalizedPath === appRouterRoot || normalizedPath.startsWith(appRouterRoot + "/");\n}\nfunction isRootAppLayoutPath(path, appRouterRoot) {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n const pathWithoutExtension = normalizedPath.replace(/\\.(?:tsx|jsx|ts|js)$/, "");\n return pathWithoutExtension === appRouterRoot + "/layout";\n}\nfunction unwrapAppRouterDocumentLayout(LayoutComponent, React2) {\n return function AppRouterDocumentLayout(props) {\n const element = LayoutComponent(props);\n const asElement = element;\n if (!React2.isValidElement(element) || asElement.type !== "html") {\n return element;\n }\n const body = React2.Children.toArray(asElement.props?.children).find(\n (child) => React2.isValidElement(child) && child.type === "body"\n );\n return body?.props?.children ?? props.children;\n };\n}\nfunction createHydrationRenderer(deps) {\n const { env: env2, logging: logging2, componentLoader: componentLoader2, snapshotModules: snapshotModules2, moduleServerUrl: moduleServerUrl2 } = deps;\n const { window, document: document2, React: React2, RouterProvider: RouterProvider2, PageContextProvider: PageContextProvider2 } = env2;\n const { DEBUG, log, logError } = logging2;\n const { loadComponent, pathToModuleUrl } = componentLoader2;\n const { importSnapshotBoundModule } = snapshotModules2;\n async function renderPage(pathname) {\n const resolvedPathname = (() => {\n const input = typeof pathname === "string" ? pathname : window.location.pathname;\n try {\n return new URL(input, window.location.origin).pathname || "/";\n } catch (_) {\n const [pathOnly] = String(input || "/").split(/[?#]/);\n return pathOnly || "/";\n }\n })();\n const dataScript = findServerHydrationDataElement(document2);\n if (!dataScript) {\n logError("Hydration data not found");\n return;\n }\n let data = {};\n try {\n data = JSON.parse(dataScript.textContent || "{}");\n } catch (parseError) {\n logError("Failed to parse hydration data:", parseError);\n return;\n }\n log("Hydration data:", data);\n if (data.studioEmbed && window.__veryfrontSetStudioEmbed) {\n window.__veryfrontSetStudioEmbed(true);\n }\n if (window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(data.releaseId || null);\n }\n if (data.releaseAssetModules && window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(data.releaseAssetModules);\n }\n try {\n let pageModule;\n const pagePath = typeof data.pagePath === "string" ? data.pagePath : "";\n const normalizedPagePath = pagePath.replace(/^\\/+/, "");\n const normalizedAppRouterRoot = typeof data.appRouterRoot === "string" && data.appRouterRoot.replace(/^\\/+|\\/+$/g, "") ? data.appRouterRoot.replace(/^\\/+|\\/+$/g, "") : "app";\n const hasReleaseAssetModules = data.releaseAssetModules && Object.keys(data.releaseAssetModules).length > 0;\n const shouldRenderRscClientPage = data.clientModuleStrategy === "rsc-module" && !hasReleaseAssetModules && isAppRouterPath(normalizedPagePath, normalizedAppRouterRoot);\n const isolatedClientPage = data.isolatedClientPage === true;\n const loadHydrationComponent = async (path, preferRscModule) => {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n if (preferRscModule && isAppRouterPath(normalizedPath, normalizedAppRouterRoot)) {\n const moduleUrl = buildPinnedRscModuleUrl(path, data);\n log("Loading App Router component from RSC module:", moduleUrl);\n const module = await importSnapshotBoundModule(moduleUrl);\n return module.default || module;\n }\n return loadComponent(path, data);\n };\n let pageModuleError = null;\n if (data.pagePath) {\n const moduleUrl = shouldRenderRscClientPage ? buildPinnedRscModuleUrl(data.pagePath, data) : pathToModuleUrl(data.pagePath, data.studioEmbed, data);\n log("Loading page from hydration data:", moduleUrl);\n try {\n pageModule = await importSnapshotBoundModule(moduleUrl);\n } catch (error) {\n pageModuleError = error;\n logError("Failed to load page from hydration data:", error);\n }\n }\n if (!pageModule) {\n const pageSlug = resolvedPathname === "/" ? "index" : resolvedPathname.slice(1);\n log("Falling back to Pages Router pattern:", pageSlug);\n const prefix = pageSlug.startsWith("@/") ? "" : "/pages";\n const basePath = moduleServerUrl2 + prefix + "/" + pageSlug;\n pageModule = await loadPageModuleWithIndexFallback(\n basePath,\n pageSlug,\n pageModuleError,\n (moduleUrl) => importSnapshotBoundModule(appendDependencyPinningVersion(moduleUrl, data))\n );\n }\n if (!pageModule) {\n logError("Page module failed to load");\n return;\n }\n const PageComponent = pageModule.default || pageModule;\n if (!PageComponent) {\n logError("Page component not found");\n return;\n }\n const normalizedParams = flattenRouteParams(data.params);\n const pageProps = { ...data.props || {}, params: normalizedParams };\n let tree = React2.createElement(PageComponent, pageProps);\n const layouts = data.layouts;\n if (layouts?.length) {\n for (let i = layouts.length - 1; i >= 0; i--) {\n const layout = layouts[i];\n if (!layout) continue;\n const LayoutComponent = await loadHydrationComponent(\n layout.path,\n shouldRenderRscClientPage\n );\n if (LayoutComponent) {\n const WrappedLayoutComponent = shouldRenderRscClientPage && isRootAppLayoutPath(layout.path, normalizedAppRouterRoot) ? unwrapAppRouterDocumentLayout(LayoutComponent, React2) : LayoutComponent;\n const layoutProps = data.layoutProps?.[layout.path] || {};\n tree = React2.createElement(\n WrappedLayoutComponent,\n { ...layoutProps, children: tree }\n );\n }\n }\n }\n if (data.appPath && !isolatedClientPage) {\n const AppComponent = await loadHydrationComponent(data.appPath, shouldRenderRscClientPage);\n if (AppComponent) {\n tree = React2.createElement(AppComponent, { children: tree });\n }\n }\n if (data.errorPath) {\n const ErrorComponent = await loadHydrationComponent(\n data.errorPath,\n shouldRenderRscClientPage\n );\n if (ErrorComponent) {\n class AppRouterErrorBoundary extends React2.Component {\n constructor(props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n static getDerivedStateFromError(error) {\n return { hasError: true, error };\n }\n render() {\n if (this.state.hasError) {\n return React2.createElement(ErrorComponent, {\n error: this.state.error,\n reset: () => this.setState({ hasError: false, error: null })\n });\n }\n return this.props.children;\n }\n }\n tree = React2.createElement(AppRouterErrorBoundary, null, tree);\n }\n }\n const headings = data.headings || [];\n const pageContext = {\n slug: data.slug || "",\n path: data.pagePath || resolvedPathname,\n params: normalizedParams,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n frontmatter: data.frontmatter || {},\n data: data.props || {},\n headings,\n mdxHeadings: headings\n // Alias for backwards compatibility\n };\n tree = React2.createElement(PageContextProvider2, { pageContext, children: tree });\n tree = React2.createElement(RouterProvider2, { router: deps.router, children: tree });\n const container = isolatedClientPage ? document2.getElementById("veryfront-page-island") : document2.getElementById("root");\n if (!container) {\n if (isolatedClientPage) {\n throw new Error("Isolated client page root not found");\n }\n return;\n }\n if (container.__reactRoot) {\n container.__reactRoot.render(tree);\n log("Page re-rendered");\n return;\n }\n if (shouldRenderRscClientPage) {\n container.__reactRoot = env2.createRoot(container);\n container.__reactRoot.render(tree);\n log("Client-side React app rendered successfully");\n } else {\n const { hydrateRoot } = await import("react-dom/client");\n const options = {\n identifierPrefix: "vf",\n onRecoverableError: (error) => {\n if (data.dev && DEBUG) {\n log("Hydration mismatch (suppressed):", error.message);\n }\n }\n };\n container.__reactRoot = hydrateRoot(container, tree, options);\n log("Client-side React app hydrated successfully");\n }\n if (window.__veryfrontHydrationComplete) {\n window.__veryfrontHydrationComplete();\n }\n } catch (error) {\n logError("Client initialization error:", error);\n if (window.__veryfrontHydrationFailed) {\n window.__veryfrontHydrationFailed(error);\n }\n }\n }\n function start() {\n window.__veryfrontRenderPage = renderPage;\n void renderPage(window.location.pathname);\n const initialDataScript = findServerHydrationDataElement(document2);\n if (initialDataScript) {\n try {\n const pageData = JSON.parse(initialDataScript.textContent || "{}");\n if (pageData.pagePath) {\n window.history.replaceState({ pageData, scrollY: 0 }, "", window.location.href);\n log("Stored initial page data in history state");\n }\n } catch (_) {\n }\n }\n }\n return { renderPage, start };\n}\n\n// src/html/hydration-script-builder/runtime/navigation-store.ts\nvar NAVIGATION_STORE_REGISTRY_KEY = "veryfront.navigation.store.v1";\nfunction resolveNavigationStore(RouterRuntime2) {\n const usesRegistryFallback2 = typeof RouterRuntime2.getNavigationStore !== "function";\n if (!usesRegistryFallback2) {\n return {\n usesRegistryFallback: usesRegistryFallback2,\n getNavigationStore: RouterRuntime2.getNavigationStore\n };\n }\n return {\n usesRegistryFallback: usesRegistryFallback2,\n getNavigationStore: () => {\n const storeKey = Symbol.for(NAVIGATION_STORE_REGISTRY_KEY);\n const registry = globalThis;\n const existing = registry[storeKey];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? loc.pathname + loc.search + loc.hash : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n registry[storeKey] = store;\n return store;\n }\n };\n}\n\n// src/html/hydration-script-builder/runtime/main.ts\nvar runtimeWindow = globalThis;\nvar runtimeDocument = globalThis.document;\nvar env = {\n window: runtimeWindow,\n document: runtimeDocument,\n fetch: (url, init) => fetch(url, init),\n React,\n RouterProvider,\n PageContextProvider,\n createRoot: (container) => createRoot(container),\n importModule: (moduleUrl) => import(moduleUrl),\n useRouterFromModule,\n setTimeout: (handler, timeout) => setTimeout(handler, timeout),\n clearTimeout: (id) => clearTimeout(id)\n};\nvar logging = createLogging(runtimeWindow);\nvar initialHydrationData = readInitialHydrationData(runtimeDocument);\nvar documentDependencyPinningCacheKey = readDocumentDependencyPinningCacheKey(\n initialHydrationData\n);\nvar routeTiming = createRouteTimingRecorder(runtimeWindow, logging);\nvar snapshotModules = createSnapshotModuleImporter({\n importModule: env.importModule,\n fetchModule: env.fetch,\n reloadDocument: () => runtimeWindow.location.reload(),\n recoveryState: runtimeWindow\n});\nvar componentLoader = createComponentLoader({\n window: runtimeWindow,\n logging,\n moduleServerUrl: moduleServerUrl(runtimeWindow),\n snapshotModules\n});\nruntimeWindow.__veryfrontClearComponentCache = componentLoader.clearComponentCache;\nruntimeWindow.__veryfrontSetStudioEmbed = componentLoader.setStudioEmbed;\nruntimeWindow.__veryfrontSetReleaseId = componentLoader.setReleaseId;\nruntimeWindow.__veryfrontSetReleaseAssetModules = componentLoader.setReleaseAssetModules;\nruntimeWindow.__veryfrontSetHMRRefreshTimestamp = componentLoader.setHMRRefreshTimestamp;\nvar { usesRegistryFallback, getNavigationStore } = resolveNavigationStore(RouterRuntime);\nvar routerRuntime = createRouterRuntime({\n env,\n logging,\n routeTiming,\n componentLoader,\n snapshotModules,\n initialHydrationData,\n documentDependencyPinningCacheKey,\n getNavigationStore,\n navigationStoreUsesRegistryFallback: usesRegistryFallback\n});\ncreateHydrationRenderer({\n env,\n logging,\n componentLoader,\n snapshotModules,\n moduleServerUrl: moduleServerUrl(runtimeWindow),\n router: routerRuntime.router\n}).start();\n'; + '// src/html/hydration-script-builder/runtime/main.ts\nimport * as React from "react";\nimport { createRoot } from "react-dom/client";\nimport { RouterProvider, useRouter as useRouterFromModule } from "veryfront/router";\nimport * as RouterRuntime from "veryfront/router";\nimport { PageContextProvider } from "veryfront/context";\n\n// src/routing/flatten-route-params.ts\nfunction flattenRouteParams(params) {\n if (!params) return {};\n const flat = {};\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0) continue;\n flat[key] = Array.isArray(value) ? value.join("/") : value;\n }\n return flat;\n}\n\n// src/html/hydration-script-builder/runtime/shared.ts\nfunction moduleServerUrl(window) {\n return window.location.origin + "/_vf_modules";\n}\nfunction createLogging(window) {\n const DEBUG = Boolean(\n window.__VERYFRONT_DEBUG__ || new URLSearchParams(window.location.search).has("vf_debug")\n );\n const log = DEBUG ? console.log.bind(console, "[Veryfront]") : () => {\n };\n const logError = console.error.bind(console, "[Veryfront]");\n function logBackgroundFetchFailure(reason, path, error) {\n const message = error?.message ?? String(error);\n log(reason + " failed:", path, message);\n }\n const perfTimers = /* @__PURE__ */ new Map();\n const perfStart = DEBUG ? (label) => {\n perfTimers.set(label, performance.now());\n } : () => {\n };\n const perfEnd = DEBUG ? (label) => {\n const start = perfTimers.get(label);\n if (start === void 0) return 0;\n const duration = performance.now() - start;\n perfTimers.delete(label);\n console.log(\n "[Veryfront Perf] %c" + label + ": %c" + duration.toFixed(2) + "ms",\n "color: #888",\n duration > 100 ? "color: #f00; font-weight: bold" : "color: #0a0"\n );\n return duration;\n } : () => 0;\n return { DEBUG, log, logError, logBackgroundFetchFailure, perfStart, perfEnd };\n}\nfunction isAbortError(error) {\n return error?.name === "AbortError";\n}\nfunction resolveDocumentNavigationUrl(target, origin) {\n try {\n const url = new URL(target, origin);\n if (url.protocol === "http:" || url.protocol === "https:") return url.href;\n } catch (_) {\n }\n return null;\n}\nfunction getDocumentNonce(document2) {\n const element = document2.querySelector("script[nonce], style[nonce], link[nonce]");\n if (!element) return void 0;\n return element.nonce || element.getAttribute("nonce") || void 0;\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/html/hydration-script-builder/runtime/hydration-data.ts\nfunction readInitialHydrationData(document2) {\n try {\n const element = findServerHydrationDataElement(document2);\n return JSON.parse(element && element.textContent ? element.textContent : "{}") || {};\n } catch (_) {\n return {};\n }\n}\nfunction readDocumentDependencyPinningCacheKey(initialHydrationData2) {\n return typeof initialHydrationData2.dependencyPinningCacheKey === "string" && initialHydrationData2.dependencyPinningCacheKey.startsWith("on:") ? initialHydrationData2.dependencyPinningCacheKey : null;\n}\n\n// src/html/hydration-script-builder/runtime/snapshot-modules.ts\nvar RECOVERY_STATE_KEY = "__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";\nasync function isDependencySnapshotConflictResponse(response) {\n if (!response || response.status !== 409) return false;\n try {\n const clone = response.clone?.() ?? response;\n const body = (await clone.text?.() ?? "").trim();\n return body === "Unknown dependency snapshot" || body === "export default null; // Unknown dependency snapshot";\n } catch (_) {\n return false;\n }\n}\nfunction createSnapshotModuleImporter(deps) {\n async function recoverFromSnapshotBoundModuleFailure(moduleUrl, allowDocumentReload = true) {\n try {\n const parsedUrl = new URL(moduleUrl, "http://veryfront.local");\n const snapshotKeys = parsedUrl.searchParams.getAll("pins");\n const pathMatch = parsedUrl.pathname.match(\n /^\\/_vf_modules\\/_pins\\/([^/]+)(?:\\/|$)/\n );\n if (pathMatch) {\n try {\n snapshotKeys.push(decodeURIComponent(pathMatch[1]));\n } catch (_) {\n return false;\n }\n }\n if (snapshotKeys.length !== 1 || !/^on:[A-Za-z0-9._-]+$/.test(snapshotKeys[0])) return false;\n const response = await deps.fetchModule(moduleUrl, { cache: "no-store" });\n if (!await isDependencySnapshotConflictResponse(response)) return false;\n if (!allowDocumentReload) return true;\n if (deps.recoveryState[RECOVERY_STATE_KEY] === true) return true;\n deps.recoveryState[RECOVERY_STATE_KEY] = true;\n try {\n deps.reloadDocument();\n } catch (_) {\n delete deps.recoveryState[RECOVERY_STATE_KEY];\n return false;\n }\n return true;\n } catch (_) {\n return false;\n }\n }\n async function importSnapshotBoundModule(moduleUrl, allowDocumentReload = true) {\n try {\n return await deps.importModule(moduleUrl);\n } catch (error) {\n const snapshotConflict = await recoverFromSnapshotBoundModuleFailure(\n moduleUrl,\n allowDocumentReload\n );\n if (snapshotConflict && !allowDocumentReload) {\n const conflictError = new Error(\n "Dependency snapshot is unavailable during speculative module prefetch"\n );\n conflictError.name = "DependencySnapshotConflictError";\n conflictError.dependencySnapshotConflict = true;\n conflictError.cause = error;\n throw conflictError;\n }\n throw error;\n }\n }\n return { importSnapshotBoundModule, recoverFromSnapshotBoundModuleFailure };\n}\nfunction isDependencySnapshotConflict(error) {\n return Boolean(error?.dependencySnapshotConflict);\n}\n\n// src/utils/version-constant.ts\nvar VERSION = "0.1.1203";\n\n// src/html/hydration-script-builder/runtime/module-urls.ts\nfunction appendQueryParam(url, key, value) {\n return url + (url.includes("?") ? "&" : "?") + key + "=" + value;\n}\nfunction appendDependencyPinningVersion(url, moduleData) {\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n if (typeof pinKey !== "string" || !pinKey.startsWith("on:")) return url;\n const hashIndex = url.indexOf("#");\n const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";\n const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;\n const queryIndex = withoutHash.indexOf("?");\n const base = queryIndex >= 0 ? withoutHash.slice(0, queryIndex) : withoutHash;\n const params = new URLSearchParams(queryIndex >= 0 ? withoutHash.slice(queryIndex + 1) : "");\n const modulePrefix = "/_vf_modules/";\n const prefixIndex = base.indexOf(modulePrefix);\n const origin = prefixIndex >= 0 ? base.slice(0, prefixIndex) : "";\n if (prefixIndex >= 0 && (origin === "" || /^https?:\\/\\/[^/]+$/i.test(origin))) {\n const pathStart = prefixIndex + modulePrefix.length;\n let modulePath = base.slice(pathStart);\n if (modulePath.startsWith("_pins/")) {\n const existingKeyEnd = modulePath.indexOf("/", "_pins/".length);\n const encodedExistingKey = existingKeyEnd < 0 ? modulePath.slice("_pins/".length) : modulePath.slice("_pins/".length, existingKeyEnd);\n let existingKey;\n try {\n existingKey = decodeURIComponent(encodedExistingKey);\n } catch {\n existingKey = void 0;\n }\n if (existingKey && /^on:[A-Za-z0-9._-]+$/.test(existingKey)) {\n if (existingKeyEnd < 0) return url;\n modulePath = modulePath.slice(existingKeyEnd + 1);\n }\n }\n params.delete("pins");\n const query = params.toString();\n return base.slice(0, pathStart) + "_pins/" + encodeURIComponent(pinKey) + "/" + modulePath + (query ? "?" + query : "") + hash;\n }\n params.set("pins", pinKey);\n return base + "?" + params.toString() + hash;\n}\nfunction componentCacheKey(path, moduleData) {\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n return typeof pinKey === "string" && pinKey.startsWith("on:") ? path + "|vf_pins|" + pinKey : path;\n}\nfunction normalizeReleaseAssetModulePath(path) {\n return String(path || "").replace(/^\\/?_vf_modules\\//, "").replace(/^\\/+/, "").replace(/[?#].*$/, "");\n}\nfunction buildPinnedRscModuleUrl(path, moduleData) {\n let moduleUrl = "/_veryfront/rsc/module?rel=" + encodeURIComponent(path);\n const pinKey = moduleData && moduleData.dependencyPinningCacheKey;\n if (typeof pinKey === "string" && pinKey.startsWith("on:")) {\n moduleUrl += "&pins=" + encodeURIComponent(pinKey);\n }\n return moduleUrl;\n}\nfunction buildPageDataEndpoint(path, origin) {\n const targetUrl = new URL(path, origin);\n const normalizedPath = targetUrl.pathname === "/" ? "" : targetUrl.pathname.replace(/^\\//, "");\n const endpointUrl = new URL(\n "/_veryfront/page-data/" + normalizedPath + ".json",\n origin\n );\n endpointUrl.search = targetUrl.search;\n return endpointUrl.pathname + endpointUrl.search;\n}\nfunction pageDataCacheIdentity(path, documentDependencyPinningCacheKey2) {\n return documentDependencyPinningCacheKey2 ? documentDependencyPinningCacheKey2 + "|path:" + path : path;\n}\nfunction assertPageDataMatchesDocumentSnapshot(path, data, documentDependencyPinningCacheKey2) {\n if (!documentDependencyPinningCacheKey2) return data;\n if (data && data.dependencyPinningCacheKey === documentDependencyPinningCacheKey2) {\n return data;\n }\n const error = new Error("Page data dependency snapshot does not match the document");\n error.status = 409;\n error.dependencySnapshotMismatch = true;\n error.path = path;\n throw error;\n}\n\n// src/html/hydration-script-builder/runtime/component-loader.ts\nvar VERYFRONT_RUNTIME_VERSION = VERSION;\nfunction createComponentLoader(deps) {\n const { window, moduleServerUrl: moduleServerUrl2 } = deps;\n const { DEBUG, log, logError } = deps.logging;\n const componentCache = /* @__PURE__ */ new Map();\n const loadingPromises = /* @__PURE__ */ new Map();\n let releaseId = null;\n let releaseAssetModules = null;\n let studioEmbed = false;\n let hmrRefreshTimestamp = null;\n function clearComponentCache(path) {\n if (!path) {\n componentCache.clear();\n loadingPromises.clear();\n log("Cleared all component caches");\n return;\n }\n for (const key of componentCache.keys()) {\n if (key === path || key.startsWith(path + "|vf_pins|")) {\n componentCache.delete(key);\n }\n }\n for (const key of loadingPromises.keys()) {\n if (key === path || key.startsWith(path + "|vf_pins|")) {\n loadingPromises.delete(key);\n }\n }\n log("Cleared component cache for:", path);\n }\n function setReleaseId(value) {\n releaseId = typeof value === "string" && value ? value : null;\n window.__veryfrontReleaseId = releaseId;\n }\n function appendReleaseModuleVersion(url) {\n if (!releaseId || url.includes("vf_release=")) return url;\n let versionedUrl = appendQueryParam(url, "vf_release", encodeURIComponent(releaseId));\n versionedUrl = appendQueryParam(\n versionedUrl,\n "vf_runtime",\n encodeURIComponent(VERYFRONT_RUNTIME_VERSION)\n );\n return versionedUrl;\n }\n function setReleaseAssetModules(value) {\n releaseAssetModules = value && typeof value === "object" && !Array.isArray(value) ? value : null;\n window.__veryfrontReleaseAssetModules = releaseAssetModules;\n }\n function resolveReleaseAssetModuleUrl(path) {\n if (!releaseAssetModules || studioEmbed || hmrRefreshTimestamp) return null;\n const key = normalizeReleaseAssetModulePath(path);\n if (releaseAssetModules[key]) return releaseAssetModules[key];\n const withoutExt = key.replace(/\\.(tsx|ts|jsx|mdx|js|mjs)$/, "");\n const extensions = [".tsx", ".ts", ".jsx", ".mdx", ".js"];\n for (const ext of extensions) {\n const candidate = withoutExt + ext;\n if (releaseAssetModules[candidate]) return releaseAssetModules[candidate];\n }\n return null;\n }\n function pathToModuleUrl(path, embedInStudio, moduleData) {\n const releaseAssetUrl = resolveReleaseAssetModuleUrl(path);\n if (releaseAssetUrl) return releaseAssetUrl;\n const pattern = /(pages|components|app|lib|layouts|shared|features)\\/(.+)\\.(tsx|ts|jsx|mdx)$/;\n const match = path.match(new RegExp("/" + pattern.source)) || path.match(new RegExp("^" + pattern.source));\n let url;\n if (match) {\n url = moduleServerUrl2 + "/" + match[1] + "/" + match[2] + ".js";\n } else {\n const hasKnownExt = /\\.(tsx|ts|jsx|mdx|js|mjs)$/.test(path);\n url = moduleServerUrl2 + "/" + (hasKnownExt ? path.replace(/\\.(tsx|ts|jsx|mdx)$/, ".js") : path + ".js");\n }\n if (embedInStudio) url = appendQueryParam(url, "studio_embed", "true");\n if (hmrRefreshTimestamp) url = appendQueryParam(url, "t", hmrRefreshTimestamp);\n if (!embedInStudio && !hmrRefreshTimestamp) url = appendReleaseModuleVersion(url);\n url = appendDependencyPinningVersion(url, moduleData);\n return url;\n }\n function setStudioEmbed(value) {\n studioEmbed = value;\n window.__veryfrontStudioEmbed = value;\n }\n function setHMRRefreshTimestamp(timestamp) {\n hmrRefreshTimestamp = timestamp;\n window.__veryfrontHMRRefreshTimestamp = timestamp;\n }\n async function loadComponent(path, moduleData, options = {}) {\n if (!path) return null;\n const cacheKey = componentCacheKey(path, moduleData);\n if (componentCache.has(cacheKey)) {\n log("Component cached:", path);\n return componentCache.get(cacheKey);\n }\n const existingPromise = loadingPromises.get(cacheKey);\n if (existingPromise) return existingPromise;\n const loadPromise = (async () => {\n try {\n const moduleUrl = pathToModuleUrl(path, studioEmbed, moduleData);\n const start = DEBUG ? performance.now() : 0;\n log("Loading component:", moduleUrl);\n const module = await deps.snapshotModules.importSnapshotBoundModule(\n moduleUrl,\n options.allowDocumentReload !== false\n );\n const component = module.MDXLayout || module.MainLayout || module.default || module;\n if (DEBUG) {\n const duration = performance.now() - start;\n console.log(\n "[Veryfront Perf] %cimport:" + path.split("/").pop() + ": %c" + duration.toFixed(2) + "ms",\n "color: #888",\n duration > 50 ? "color: #f00; font-weight: bold" : "color: #0a0"\n );\n }\n componentCache.set(cacheKey, component);\n return component;\n } catch (error) {\n if (isDependencySnapshotConflict(error)) throw error;\n logError("Failed to load component:", path, error);\n return null;\n } finally {\n loadingPromises.delete(cacheKey);\n }\n })();\n loadingPromises.set(cacheKey, loadPromise);\n return loadPromise;\n }\n return {\n loadComponent,\n pathToModuleUrl,\n clearComponentCache,\n setStudioEmbed,\n setReleaseId,\n setReleaseAssetModules,\n setHMRRefreshTimestamp\n };\n}\n\n// src/html/hydration-script-builder/runtime/route-timing.ts\nvar MAX_ROUTE_TIMINGS = 100;\nvar MAX_SERVER_TIMING_LENGTH = 1024;\nfunction routeTimingNow() {\n return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();\n}\nfunction sanitizeServerTimingMetricName(name) {\n return String(name || "").trim().replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 128);\n}\nfunction sanitizeServerTimingHeader(value) {\n if (!value) return null;\n const metrics = [];\n const printable = String(value).replace(/[^\\x20-\\x7E]/g, " ").trim();\n if (!printable) return null;\n for (const item of printable.split(",")) {\n const segments = item.split(";").map((segment) => segment.trim()).filter(Boolean);\n const name = sanitizeServerTimingMetricName(segments[0]);\n if (!name) continue;\n for (const segment of segments.slice(1)) {\n const [key, rawValue = ""] = segment.split("=");\n if ((key ?? "").trim().toLowerCase() !== "dur") continue;\n const duration = Number(rawValue.trim().replace(/^"|"$/g, ""));\n if (!Number.isFinite(duration) || duration < 0) continue;\n metrics.push(name + ";dur=" + (Math.round(duration * 100) / 100).toFixed(2));\n break;\n }\n }\n const sanitized = metrics.join(", ");\n return sanitized ? sanitized.slice(0, MAX_SERVER_TIMING_LENGTH) : null;\n}\nfunction parseServerTimingMetrics(value) {\n const header = sanitizeServerTimingHeader(value);\n if (!header) return null;\n const metrics = {};\n for (const item of header.split(",")) {\n const segments = item.split(";").map((segment) => segment.trim()).filter(Boolean);\n const name = sanitizeServerTimingMetricName(segments[0]);\n if (!name) continue;\n for (const segment of segments.slice(1)) {\n const [key, rawValue = ""] = segment.split("=");\n if ((key ?? "").trim().toLowerCase() !== "dur") continue;\n const duration = Number(rawValue.trim().replace(/^"|"$/g, ""));\n if (Number.isFinite(duration) && duration >= 0) {\n metrics[name] = Math.round(duration * 100) / 100;\n }\n }\n }\n return Object.keys(metrics).length ? metrics : null;\n}\nfunction readResponseServerTiming(response) {\n try {\n return sanitizeServerTimingHeader(response.headers?.get("server-timing"));\n } catch (_) {\n return null;\n }\n}\nfunction roundRouteTimingValue(value) {\n return Math.round(value * 100) / 100;\n}\nfunction extractResourceTiming(entry) {\n const fields = [\n "startTime",\n "requestStart",\n "responseStart",\n "responseEnd",\n "duration",\n "transferSize",\n "encodedBodySize",\n "decodedBodySize"\n ];\n const timing = {};\n for (const field of fields) {\n const value = entry?.[field];\n if (typeof value === "number" && Number.isFinite(value) && value >= 0) {\n timing[field] = roundRouteTimingValue(value);\n }\n }\n return Object.keys(timing).length ? timing : null;\n}\nfunction createRouteTimingRecorder(window, logging2) {\n const { log } = logging2;\n function emitRouteTiming(phase, path, startedAt, detail = {}) {\n const entry = {\n phase,\n path,\n duration: Math.max(0, routeTimingNow() - startedAt),\n timestamp: Date.now(),\n ...detail\n };\n const timings = Array.isArray(window.__veryfrontRouteTimings) ? window.__veryfrontRouteTimings : [];\n timings.push(entry);\n if (timings.length > MAX_ROUTE_TIMINGS) {\n timings.splice(0, timings.length - MAX_ROUTE_TIMINGS);\n }\n window.__veryfrontRouteTimings = timings;\n try {\n window.dispatchEvent(new CustomEvent("veryfront:route-timing", { detail: entry }));\n } catch (_) {\n }\n log("Route timing:", entry);\n return entry;\n }\n function getPageDataResourceTiming(endpoint, fetchStartedAt) {\n try {\n if (typeof performance === "undefined" || typeof performance.getEntriesByName !== "function") {\n return null;\n }\n const href = new URL(endpoint, window.location.href).href;\n const entries = performance.getEntriesByName(href, "resource");\n if (!entries.length) return null;\n for (let index = entries.length - 1; index >= 0; index--) {\n const entry = entries[index];\n const responseEnd = entry?.responseEnd;\n if (typeof responseEnd === "number" && Number.isFinite(responseEnd) && responseEnd + 1 >= fetchStartedAt) {\n return extractResourceTiming(entry);\n }\n }\n return null;\n } catch (_) {\n return null;\n }\n }\n function buildPageDataTimingDetail(response, endpoint, fetchStartedAt, source) {\n const detail = { source, status: response.status };\n const serverTiming = readResponseServerTiming(response);\n if (serverTiming) {\n detail.serverTiming = serverTiming;\n const serverTimingMetrics = parseServerTimingMetrics(serverTiming);\n if (serverTimingMetrics) detail.serverTimingMetrics = serverTimingMetrics;\n }\n const resourceTiming = getPageDataResourceTiming(response.url || endpoint, fetchStartedAt);\n if (resourceTiming) detail.resourceTiming = resourceTiming;\n return detail;\n }\n return { emitRouteTiming, buildPageDataTimingDetail };\n}\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\nfunction handoffClientRouteMetadata(metadata, targetDocument = document) {\n const retainedTitle = targetDocument.title;\n retireClientHeadOwnership(targetDocument);\n updateRouteTitle(\n typeof metadata.title === "string" && metadata.title ? metadata.title : retainedTitle,\n targetDocument\n );\n updateRouteMetaTags(metadata, targetDocument);\n}\n\n// src/html/hydration-script-builder/runtime/router.ts\nvar FETCH_TIMEOUT_MS = 1e4;\nvar MAX_RETRIES = 2;\nvar MAX_CACHE_SIZE = 50;\nvar CACHE_TTL_MS = 5 * 60 * 1e3;\nvar BACKGROUND_REFRESH_INTERVAL_MS = 30 * 1e3;\nvar PREFETCH_DELAY_MS = 100;\nvar MAX_PREFETCH_PATHS = 100;\nvar IDLE_PREFETCH_DELAY_MS = 1200;\nvar IDLE_PREFETCH_MAX_LINKS = 4;\nvar VIEWPORT_PREFETCH_MAX_LINKS = 8;\nvar PAGE_DATA_PREFETCH_CONCURRENCY = 2;\nvar VIEWPORT_PREFETCH_ROOT_MARGIN = "200px";\nvar MAX_SCROLL_POSITIONS = 100;\nfunction createRouterRuntime(deps) {\n const { env: env2, logging: logging2, routeTiming: routeTiming2, componentLoader: componentLoader2, snapshotModules: snapshotModules2 } = deps;\n const { window, document: document2, React: React2, RouterProvider: RouterProvider2, PageContextProvider: PageContextProvider2 } = env2;\n const { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = env2;\n const { log, logError, logBackgroundFetchFailure, perfStart, perfEnd } = logging2;\n const { emitRouteTiming, buildPageDataTimingDetail } = routeTiming2;\n const { loadComponent } = componentLoader2;\n const documentPinKey = deps.documentDependencyPinningCacheKey;\n let hydrationResolve;\n let hydrationReject;\n const hydrationPromise = new Promise((resolve, reject) => {\n hydrationResolve = resolve;\n hydrationReject = reject;\n });\n let hydrationCompleted = false;\n let hydrationFailed = false;\n function signalHydrationComplete() {\n hydrationCompleted = true;\n hydrationResolve();\n log("Hydration complete signal received");\n }\n function signalHydrationFailed(error) {\n hydrationFailed = true;\n hydrationReject(error);\n logError("Hydration failed signal received:", error);\n }\n window.__veryfrontHydrationComplete = signalHydrationComplete;\n window.__veryfrontHydrationFailed = signalHydrationFailed;\n function pageDataCacheIdentity2(path) {\n return pageDataCacheIdentity(path, documentPinKey);\n }\n function navigateDocument(target) {\n const safeUrl = resolveDocumentNavigationUrl(target, window.location.origin);\n if (safeUrl) {\n window.location.href = safeUrl;\n return;\n }\n logError("Refusing an unsafe document navigation:", target);\n window.location.reload();\n }\n let clientBuildVersion = null;\n function checkVersionMismatch(newVersion) {\n if (!clientBuildVersion) {\n clientBuildVersion = newVersion;\n log("Build version initialized:", newVersion);\n return false;\n }\n if (newVersion.serverStart !== clientBuildVersion.serverStart) {\n log("Server restarted, reloading...", {\n old: clientBuildVersion.serverStart,\n new: newVersion.serverStart\n });\n return true;\n }\n if (newVersion.framework !== clientBuildVersion.framework) {\n log("Framework version changed, reloading...", {\n old: clientBuildVersion.framework,\n new: newVersion.framework\n });\n return true;\n }\n if (newVersion.projectUpdated && clientBuildVersion.projectUpdated && newVersion.projectUpdated !== clientBuildVersion.projectUpdated) {\n log("Project content updated, reloading...", {\n old: clientBuildVersion.projectUpdated,\n new: newVersion.projectUpdated\n });\n return true;\n }\n return false;\n }\n const pageDataCache = /* @__PURE__ */ new Map();\n const pendingPageDataFetches = /* @__PURE__ */ new Map();\n const backgroundRefreshTimestamps = /* @__PURE__ */ new Map();\n function getCachedPageData(path) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const entry = pageDataCache.get(cacheIdentity);\n if (!entry) return null;\n if (Date.now() - entry.timestamp < CACHE_TTL_MS) return entry.data;\n pageDataCache.delete(cacheIdentity);\n backgroundRefreshTimestamps.delete(cacheIdentity);\n return null;\n }\n function setCachedPageData(path, data) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n if (pageDataCache.size >= MAX_CACHE_SIZE) {\n const oldest = pageDataCache.keys().next().value;\n if (oldest) {\n pageDataCache.delete(oldest);\n backgroundRefreshTimestamps.delete(oldest);\n }\n }\n pageDataCache.set(cacheIdentity, { data, timestamp: Date.now() });\n }\n const scrollPositions = /* @__PURE__ */ new Map();\n function saveScrollPosition(path) {\n if (scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = scrollPositions.keys().next().value;\n if (oldest) scrollPositions.delete(oldest);\n }\n scrollPositions.set(path, window.scrollY);\n }\n function restoreScrollPosition(path) {\n const savedY = scrollPositions.get(path);\n if (savedY === void 0) return false;\n requestAnimationFrame(() => window.scrollTo(0, savedY));\n return true;\n }\n let progressBar = null;\n let progressTimeout = null;\n function showNavigationProgress() {\n if (!progressBar) {\n progressBar = document2.createElement("div");\n progressBar.id = "vf-nav-progress";\n progressBar.style.cssText = "position:fixed;top:0;left:0;height:3px;width:0;background:linear-gradient(90deg,#0066ff,#00aaff);z-index:99999;transition:width 0.3s ease-out,opacity 0.2s;opacity:1;";\n document2.body.prepend(progressBar);\n }\n progressBar.style.opacity = "1";\n progressBar.style.width = "30%";\n progressTimeout = setTimeout2(() => {\n if (progressBar?.style) progressBar.style.width = "70%";\n }, 300);\n document2.body.setAttribute("aria-busy", "true");\n }\n function hideNavigationProgress() {\n if (progressTimeout) {\n clearTimeout2(progressTimeout);\n progressTimeout = null;\n }\n if (progressBar) {\n progressBar.style.width = "100%";\n setTimeout2(() => {\n if (!progressBar) return;\n progressBar.style.opacity = "0";\n setTimeout2(() => {\n if (progressBar) progressBar.style.width = "0";\n }, 200);\n }, 150);\n }\n document2.body.removeAttribute("aria-busy");\n }\n let currentAbortController = null;\n function sleep(ms) {\n return new Promise((resolve) => setTimeout2(resolve, ms));\n }\n async function fetchWithRetry(url, options, maxRetries = MAX_RETRIES) {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const callerSignal = options.signal;\n const abortFromCaller = () => controller.abort();\n if (callerSignal?.aborted) controller.abort();\n callerSignal?.addEventListener("abort", abortFromCaller, { once: true });\n const timeout = setTimeout2(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const response = await env2.fetch(url, { ...options, signal: controller.signal });\n clearTimeout2(timeout);\n callerSignal?.removeEventListener("abort", abortFromCaller);\n if (response.ok) return response;\n if (response.status >= 500 && attempt < maxRetries) {\n log("Server error, retrying...", response.status);\n await sleep(Math.pow(2, attempt) * 500);\n continue;\n }\n return response;\n } catch (error) {\n clearTimeout2(timeout);\n callerSignal?.removeEventListener("abort", abortFromCaller);\n if (error.name === "AbortError" && callerSignal?.aborted) throw error;\n if (attempt === maxRetries) throw error;\n log("Fetch failed, retrying...", error.message);\n await sleep(Math.pow(2, attempt) * 500);\n }\n }\n throw new Error("Failed to fetch page data");\n }\n async function fetchPageDataFresh(path, signal, options = {}) {\n const {\n triggerReloadOnVersionMismatch = false,\n recordRouteTiming = false,\n timingSource = "network"\n } = options;\n const endpoint = buildPageDataEndpoint(path, window.location.origin);\n const startedAt = recordRouteTiming ? routeTimingNow() : 0;\n log("Fetching page data:", path);\n perfStart("fetch:" + path);\n const headers = options.prefetch ? { "X-Veryfront-Prefetch": "1" } : { "X-Veryfront-Navigation": "spa" };\n if (documentPinKey) {\n headers["X-Veryfront-Dependency-Pins"] = documentPinKey;\n }\n const response = await fetchWithRetry(endpoint, {\n headers,\n signal\n }, options.prefetch ? 0 : MAX_RETRIES);\n if (!response.ok) {\n perfEnd("fetch:" + path);\n if (recordRouteTiming) {\n emitRouteTiming(\n "page-data",\n path,\n startedAt,\n buildPageDataTimingDetail(response, endpoint, startedAt, timingSource)\n );\n }\n const error = new Error("Failed to fetch page data: " + response.status);\n error.status = response.status;\n throw error;\n }\n perfStart("parse:" + path);\n const data = assertPageDataMatchesDocumentSnapshot(\n path,\n await response.json(),\n documentPinKey\n );\n perfEnd("parse:" + path);\n perfEnd("fetch:" + path);\n if (recordRouteTiming) {\n emitRouteTiming(\n "page-data",\n path,\n startedAt,\n buildPageDataTimingDetail(response, endpoint, startedAt, timingSource)\n );\n }\n if (triggerReloadOnVersionMismatch) {\n const checkedData = handlePageDataVersionMismatch(path, data);\n if (checkedData !== data) return checkedData;\n }\n setCachedPageData(path, data);\n return data;\n }\n function handlePageDataVersionMismatch(path, data) {\n if (data.buildVersion && checkVersionMismatch(data.buildVersion)) {\n log("Version mismatch detected, performing full page reload to:", path);\n navigateDocument(path);\n return new Promise(() => {\n });\n }\n return data;\n }\n function startPageDataFetch(path, signal, options = {}) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const request = fetchPageDataFresh(path, signal, options).finally(() => {\n if (options.trackPending !== false && pendingPageDataFetches.get(cacheIdentity) === request) {\n pendingPageDataFetches.delete(cacheIdentity);\n }\n });\n if (options.trackPending !== false) {\n pendingPageDataFetches.set(cacheIdentity, request);\n }\n return request;\n }\n function fetchPageDataDeduped(path) {\n const pending = pendingPageDataFetches.get(pageDataCacheIdentity2(path));\n if (pending) return pending;\n return startPageDataFetch(path, null);\n }\n function refreshPageDataInBackground(path) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n const lastRefreshAt = backgroundRefreshTimestamps.get(cacheIdentity) || 0;\n const now = Date.now();\n if (now - lastRefreshAt < BACKGROUND_REFRESH_INTERVAL_MS) return;\n backgroundRefreshTimestamps.set(cacheIdentity, now);\n fetchPageDataDeduped(path).catch((error) => {\n logBackgroundFetchFailure("Stale page data refresh", path, error);\n });\n }\n async function fetchPageDataForNavigation(path, signal) {\n const startedAt = routeTimingNow();\n const cached = getCachedPageData(path);\n if (cached) {\n log("Using cached page data:", path);\n refreshPageDataInBackground(path);\n emitRouteTiming("page-data", path, startedAt, { source: "cache" });\n return cached;\n }\n const pending = pendingPageDataFetches.get(pageDataCacheIdentity2(path));\n if (pending) {\n log("Reusing pending page data fetch for navigation:", path);\n const data = await pending;\n emitRouteTiming("page-data", path, startedAt, { source: "deduped" });\n return handlePageDataVersionMismatch(path, data);\n }\n return startPageDataFetch(path, signal, {\n triggerReloadOnVersionMismatch: true,\n recordRouteTiming: true,\n timingSource: "network"\n });\n }\n function fetchPageDataForPrefetch(path, signal) {\n if (getCachedPageData(path)) return Promise.resolve();\n return startPageDataFetch(path, signal, { prefetch: true, trackPending: false }).then((data) => preloadModulesForPageData(data, path)).catch((error) => {\n if (!isAbortError(error)) {\n logBackgroundFetchFailure("Page data prefetch", path, error);\n }\n throw error;\n });\n }\n let currentPath = window.location.pathname;\n let isNavigating = false;\n async function navigateSPA(href, historyMode = "push", restoreScroll = false) {\n currentAbortController?.abort();\n if (isNavigating) return;\n isNavigating = true;\n const [navigationPath] = href.split("#");\n removeQueuedPrefetch(navigationPath || href);\n abortActiveSpeculativePrefetches();\n currentAbortController = new AbortController();\n const signal = currentAbortController.signal;\n const navigationStartedAt = routeTimingNow();\n showNavigationProgress();\n perfStart("nav:total:" + href);\n try {\n log("SPA navigating to:", href);\n saveScrollPosition(currentPath);\n const [path, hash] = href.split("#");\n const targetPath = path || currentPath;\n perfStart("nav:fetchData:" + href);\n const pageData = await fetchPageDataForNavigation(targetPath, signal);\n perfEnd("nav:fetchData:" + href);\n if (signal.aborted) return;\n if (pageData && pageData.redirect && typeof pageData.redirect.destination === "string") {\n const redirectUrl = resolveDocumentNavigationUrl(\n pageData.redirect.destination,\n window.location.origin\n );\n if (redirectUrl) {\n log("SPA navigation redirect -> " + redirectUrl);\n window.location.href = redirectUrl;\n return;\n }\n }\n if (historyMode === "push") {\n window.history.pushState({ pageData, scrollY: 0 }, "", href);\n } else if (historyMode === "replace") {\n window.history.replaceState({ pageData, scrollY: 0 }, "", href);\n }\n currentPath = targetPath;\n router.pathname = targetPath;\n router.query = Object.fromEntries(new URLSearchParams(window.location.search));\n router.params = flattenRouteParams(pageData.params);\n perfStart("nav:render:" + href);\n await renderPageFromData(pageData, targetPath);\n perfEnd("nav:render:" + href);\n if (restoreScroll) {\n restoreScrollPosition(targetPath);\n } else if (hash) {\n requestAnimationFrame(() => {\n const target = document2.getElementById(hash);\n if (target) {\n target.scrollIntoView({ behavior: "smooth" });\n return;\n }\n window.scrollTo(0, 0);\n });\n } else {\n window.scrollTo(0, 0);\n }\n hideNavigationProgress();\n perfEnd("nav:total:" + href);\n emitRouteTiming("total", targetPath, navigationStartedAt, {\n href,\n historyMode,\n restoreScroll\n });\n log("SPA navigation complete");\n } catch (error) {\n hideNavigationProgress();\n if (error.name === "AbortError") {\n log("Navigation aborted");\n return;\n }\n logError("SPA navigation failed:", error.message);\n if (error.status === 404) {\n logError("Page not found:", href);\n }\n navigateDocument(href);\n } finally {\n isNavigating = false;\n currentAbortController = null;\n processPageDataPrefetchQueue();\n }\n }\n async function loadPageDataComponent(pageData, path, options = {}) {\n if (!pageData.isolatedClientPage) return loadComponent(path, pageData, options);\n const moduleUrl = buildPinnedRscModuleUrl(path, pageData);\n const module = await snapshotModules2.importSnapshotBoundModule(\n moduleUrl,\n options.allowDocumentReload !== false\n );\n return module.MDXLayout || module.MainLayout || module.default || module;\n }\n async function renderPageFromData(pageData, targetPath) {\n if (pageData.requiresFullDocumentNavigation) {\n throw new Error("Server layout requires full document navigation");\n }\n if (window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(pageData.releaseId || null);\n }\n if (window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(pageData.releaseAssetModules || null);\n }\n perfStart("render:loadAll");\n const allPaths = getPageDataModulePaths(pageData);\n const modulesStartedAt = routeTimingNow();\n const components = await Promise.all(\n allPaths.map((path) => loadPageDataComponent(pageData, path))\n );\n emitRouteTiming("modules", targetPath, modulesStartedAt, { count: allPaths.length });\n perfEnd("render:loadAll");\n const [PageComponent, ...rest] = components;\n const ErrorComponent = pageData.errorPath ? rest.pop() : null;\n const AppComponent = pageData.appPath ? rest.pop() : null;\n const LayoutComponents = rest;\n if (!PageComponent) {\n throw new Error("Failed to load page component: " + pageData.pagePath);\n }\n handoffClientRouteMetadata(\n pageData.frontmatter ?? {},\n document2\n );\n if (pageData.css) {\n const existingStyle = document2.getElementById("veryfront-spa-css");\n if (existingStyle) {\n existingStyle.textContent = pageData.css;\n } else {\n const styleEl = document2.createElement("style");\n const nonce = getDocumentNonce(document2);\n if (nonce) styleEl.setAttribute("nonce", nonce);\n styleEl.id = "veryfront-spa-css";\n styleEl.textContent = pageData.css;\n document2.head.appendChild(styleEl);\n }\n log("Injected CSS for SPA navigation", { cssLength: pageData.css.length });\n } else if (pageData.cssAction === "clear") {\n const existingStyle = document2.getElementById("veryfront-spa-css");\n if (existingStyle) {\n existingStyle.remove();\n log("Cleared SPA CSS for release stylesheet navigation");\n }\n }\n const normalizedParams = flattenRouteParams(pageData.params);\n let tree = React2.createElement(PageComponent, {\n ...pageData.props,\n params: normalizedParams\n });\n if (pageData.layouts?.length) {\n for (let i = pageData.layouts.length - 1; i >= 0; i--) {\n const layout = pageData.layouts[i];\n const LayoutComponent = LayoutComponents[i];\n if (!LayoutComponent || !layout) continue;\n const layoutProps = pageData.layoutProps?.[layout.path] || {};\n tree = React2.createElement(LayoutComponent, { ...layoutProps, children: tree });\n }\n }\n if (AppComponent) {\n tree = React2.createElement(AppComponent, { children: tree });\n log("Wrapped with App component for SPA navigation");\n }\n if (ErrorComponent) {\n class AppRouterErrorBoundary extends React2.Component {\n constructor(props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n static getDerivedStateFromError(error) {\n return { hasError: true, error };\n }\n render() {\n if (this.state.hasError) {\n return React2.createElement(ErrorComponent, {\n error: this.state.error,\n reset: () => this.setState({ hasError: false, error: null })\n });\n }\n return this.props.children;\n }\n }\n tree = React2.createElement(AppRouterErrorBoundary, null, tree);\n }\n const headingsArray = pageData.headings || [];\n const pageContext = {\n slug: pageData.slug || "",\n path: pageData.pagePath || targetPath,\n params: normalizedParams,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n frontmatter: pageData.frontmatter || {},\n data: pageData.props || {},\n headings: headingsArray,\n mdxHeadings: headingsArray\n };\n tree = React2.createElement(PageContextProvider2, { pageContext, children: tree });\n tree = React2.createElement(RouterProvider2, { router, children: tree });\n const container = pageData.isolatedClientPage ? document2.getElementById("veryfront-page-island") : document2.getElementById("root");\n if (!hydrationCompleted && !hydrationFailed) {\n log("Waiting for hydration to complete before SPA render...");\n try {\n await Promise.race([\n hydrationPromise,\n new Promise(\n (_, reject) => setTimeout2(() => reject(new Error("Hydration timeout")), 1e4)\n )\n ]);\n } catch (waitError) {\n log("Hydration wait failed:", waitError.message);\n }\n }\n if (container?.__reactRoot) {\n perfStart("render:reactRender");\n container.__reactRoot.render(tree);\n perfEnd("render:reactRender");\n log("Page re-rendered via SPA");\n scheduleRoutePrefetchRefresh();\n return;\n }\n if (hydrationFailed) {\n throw new Error(\n "React root not found - hydration failed, falling back to full page navigation"\n );\n }\n throw new Error("React root not found");\n }\n let prefetchTimeout = null;\n let currentHoverLink = null;\n let routePrefetchRefreshPending = false;\n let viewportPrefetchObserver = null;\n const observedPrefetchLinks = /* @__PURE__ */ new WeakSet();\n const prefetchedPaths = /* @__PURE__ */ new Set();\n const inFlightPrefetches = /* @__PURE__ */ new Set();\n const queuedPrefetchPaths = /* @__PURE__ */ new Set();\n const pageDataPrefetchQueue = [];\n const activePageDataPrefetchControllers = /* @__PURE__ */ new Map();\n function cancelScheduledPrefetch() {\n if (prefetchTimeout) {\n clearTimeout2(prefetchTimeout);\n prefetchTimeout = null;\n }\n currentHoverLink = null;\n }\n function getPageDataModulePaths(pageData) {\n const layoutPaths = (pageData.layouts || []).map((l) => l.path).filter(Boolean);\n const allPaths = [pageData.pagePath, ...layoutPaths].filter(Boolean);\n if (pageData.appPath) allPaths.push(pageData.appPath);\n if (pageData.errorPath) allPaths.push(pageData.errorPath);\n return allPaths;\n }\n function getCurrentRouteHref() {\n return window.location.pathname + window.location.search;\n }\n function getInternalRouteHrefFromLink(link) {\n if (!link || link.target === "_blank" || link.hasAttribute("download") || link.getAttribute("data-prefetch") === "false") {\n return null;\n }\n const href = link.getAttribute("href");\n if (!href || href.startsWith("#") || href.startsWith("//") || !href.startsWith("/")) {\n return null;\n }\n try {\n const url = new URL(href, window.location.origin);\n if (url.origin !== window.location.origin) return null;\n const routeHref = url.pathname + url.search;\n return routeHref === getCurrentRouteHref() ? null : routeHref;\n } catch (_) {\n return null;\n }\n }\n function getEligiblePrefetchLinks(limit) {\n const links = [];\n const seenHrefs = /* @__PURE__ */ new Set();\n for (const link of document2.querySelectorAll("a[href]")) {\n const href = getInternalRouteHrefFromLink(link);\n if (!href || seenHrefs.has(href)) continue;\n seenHrefs.add(href);\n links.push({ link, href });\n if (links.length >= limit) break;\n }\n return links;\n }\n async function preloadModulesForPageData(pageData, path) {\n if (!pageData || pageData.requiresFullDocumentNavigation) return;\n if (pageData.releaseId && window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(pageData.releaseId);\n }\n if (pageData.releaseAssetModules && window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(pageData.releaseAssetModules);\n }\n const modulePaths = getPageDataModulePaths(pageData);\n if (modulePaths.length === 0) return;\n try {\n await Promise.all(\n modulePaths.map(\n (modulePath) => loadPageDataComponent(pageData, modulePath, { allowDocumentReload: false })\n )\n );\n } catch (error) {\n if (isDependencySnapshotConflict(error)) {\n const cacheIdentity = pageDataCacheIdentity2(path);\n pageDataCache.delete(cacheIdentity);\n backgroundRefreshTimestamps.delete(cacheIdentity);\n prefetchedPaths.delete(path);\n throw error;\n }\n logBackgroundFetchFailure("Module prefetch", path, error);\n }\n }\n function removeQueuedPrefetch(path) {\n queuedPrefetchPaths.delete(path);\n for (let i = pageDataPrefetchQueue.length - 1; i >= 0; i--) {\n if (pageDataPrefetchQueue[i] === path) pageDataPrefetchQueue.splice(i, 1);\n }\n }\n function abortActiveSpeculativePrefetches() {\n for (const controller of activePageDataPrefetchControllers.values()) {\n controller.abort();\n }\n }\n function processPageDataPrefetchQueue() {\n if (isNavigating) return;\n while (activePageDataPrefetchControllers.size < PAGE_DATA_PREFETCH_CONCURRENCY && pageDataPrefetchQueue.length > 0) {\n const href = pageDataPrefetchQueue.shift();\n queuedPrefetchPaths.delete(href);\n if (prefetchedPaths.has(href) || inFlightPrefetches.has(href) || getCachedPageData(href)) {\n continue;\n }\n if (prefetchedPaths.size >= MAX_PREFETCH_PATHS) {\n const oldest = prefetchedPaths.values().next().value;\n if (oldest) prefetchedPaths.delete(oldest);\n }\n const controller = new AbortController();\n prefetchedPaths.add(href);\n inFlightPrefetches.add(href);\n activePageDataPrefetchControllers.set(href, controller);\n fetchPageDataForPrefetch(href, controller.signal).catch((error) => {\n prefetchedPaths.delete(href);\n if (isDependencySnapshotConflict(error)) {\n logBackgroundFetchFailure("Module prefetch", href, error);\n }\n }).finally(() => {\n inFlightPrefetches.delete(href);\n activePageDataPrefetchControllers.delete(href);\n processPageDataPrefetchQueue();\n });\n }\n }\n function prefetchPage(href) {\n if (isNavigating) return;\n if (prefetchedPaths.has(href) || inFlightPrefetches.has(href) || queuedPrefetchPaths.has(href)) return;\n const cachedPageData = getCachedPageData(href);\n if (cachedPageData) {\n preloadModulesForPageData(cachedPageData, href).catch((error) => {\n logBackgroundFetchFailure("Module prefetch", href, error);\n });\n return;\n }\n queuedPrefetchPaths.add(href);\n pageDataPrefetchQueue.push(href);\n processPageDataPrefetchQueue();\n }\n function prefetchEligibleRouteLinks(limit) {\n for (const { href } of getEligiblePrefetchLinks(limit)) {\n prefetchPage(href);\n }\n }\n function ensureViewportPrefetchObserver() {\n if (viewportPrefetchObserver || typeof IntersectionObserver !== "function") {\n return viewportPrefetchObserver;\n }\n viewportPrefetchObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n viewportPrefetchObserver?.unobserve(entry.target);\n const href = getInternalRouteHrefFromLink(\n entry.target\n );\n if (href) prefetchPage(href);\n }\n }, { rootMargin: VIEWPORT_PREFETCH_ROOT_MARGIN });\n return viewportPrefetchObserver;\n }\n function observeViewportPrefetchLinks() {\n const observer = ensureViewportPrefetchObserver();\n if (!observer) return;\n for (const { link } of getEligiblePrefetchLinks(VIEWPORT_PREFETCH_MAX_LINKS)) {\n if (observedPrefetchLinks.has(link)) continue;\n observedPrefetchLinks.add(link);\n observer.observe(link);\n }\n }\n function runRoutePrefetchRefresh() {\n routePrefetchRefreshPending = false;\n prefetchEligibleRouteLinks(IDLE_PREFETCH_MAX_LINKS);\n observeViewportPrefetchLinks();\n }\n function scheduleRoutePrefetchRefresh() {\n if (routePrefetchRefreshPending) return;\n routePrefetchRefreshPending = true;\n setTimeout2(() => {\n if (typeof requestIdleCallback === "function") {\n requestIdleCallback(runRoutePrefetchRefresh, { timeout: IDLE_PREFETCH_DELAY_MS });\n return;\n }\n runRoutePrefetchRefresh();\n }, IDLE_PREFETCH_DELAY_MS);\n }\n const router = {\n domain: window.location.origin,\n path: window.location.pathname,\n push: (path) => {\n void navigateSPA(path, "push");\n },\n replace: (path) => {\n void navigateSPA(path, "replace");\n },\n back: () => {\n window.history.back();\n },\n forward: () => {\n window.history.forward();\n },\n prefetch: (path) => {\n prefetchPage(path);\n },\n pathname: window.location.pathname,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n // Seed route params from the hydration data (issue #2741). Catch-all\n // segments arrive as arrays and are joined so no path info is lost.\n params: flattenRouteParams(deps.initialHydrationData.params || {}),\n isPreview: false,\n isMounted: true,\n navigate: (path) => navigateSPA(path, "push"),\n reload: () => window.location.reload()\n };\n window.__veryfrontRouter = router;\n if (deps.navigationStoreUsesRegistryFallback) {\n log("Router runtime does not export getNavigationStore; using shared v1 registry fallback");\n }\n if (typeof deps.getNavigationStore === "function") {\n deps.getNavigationStore().setNavigator((href, options) => {\n const mode = options && options.history;\n const historyMode = mode === "replace" ? "replace" : mode === "none" ? "none" : "push";\n return navigateSPA(href, historyMode);\n });\n }\n window.addEventListener("popstate", async (e) => {\n const path = window.location.pathname;\n log("Popstate:", path);\n saveScrollPosition(currentPath);\n if (!e.state?.pageData) {\n await navigateSPA(path, "none", true);\n return;\n }\n showNavigationProgress();\n try {\n currentPath = path;\n router.pathname = path;\n router.query = Object.fromEntries(new URLSearchParams(window.location.search));\n router.params = flattenRouteParams(e.state.pageData.params);\n await renderPageFromData(e.state.pageData, path);\n restoreScrollPosition(path);\n hideNavigationProgress();\n } catch (error) {\n hideNavigationProgress();\n logError("Popstate render failed:", error.message);\n window.location.reload();\n }\n });\n document2.addEventListener("click", (e) => {\n const link = e.target?.closest("a[href]");\n if (!link) return;\n const href = link.getAttribute("href");\n if (!href) return;\n if (href.startsWith("#")) {\n const target = document2.getElementById(href.slice(1));\n if (!target) return;\n e.preventDefault();\n target.scrollIntoView({ behavior: "smooth" });\n window.history.pushState(null, "", href);\n return;\n }\n if (link.target === "_blank" || link.hasAttribute("download") || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || !href.startsWith("/") || href.startsWith("//")) {\n return;\n }\n e.preventDefault();\n cancelScheduledPrefetch();\n void navigateSPA(href, "push");\n });\n document2.addEventListener(\n "mouseenter",\n (e) => {\n if (!e.target || typeof e.target.closest !== "function") return;\n const link = e.target.closest("a[href]");\n if (!link) return;\n const href = getInternalRouteHrefFromLink(link);\n if (!href) return;\n if (currentHoverLink === link) return;\n if (prefetchTimeout) {\n clearTimeout2(prefetchTimeout);\n prefetchTimeout = null;\n }\n currentHoverLink = link;\n prefetchTimeout = setTimeout2(() => {\n prefetchPage(href);\n prefetchTimeout = null;\n }, PREFETCH_DELAY_MS);\n },\n true\n );\n document2.addEventListener(\n "mouseleave",\n (e) => {\n if (!e.target || typeof e.target.closest !== "function") return;\n const relatedTarget = e.relatedTarget;\n if (currentHoverLink && relatedTarget && currentHoverLink.contains(relatedTarget)) return;\n cancelScheduledPrefetch();\n },\n true\n );\n if (document2.readyState === "loading") {\n document2.addEventListener("DOMContentLoaded", scheduleRoutePrefetchRefresh, { once: true });\n } else {\n scheduleRoutePrefetchRefresh();\n }\n window.useRouter = () => {\n try {\n return env2.useRouterFromModule();\n } catch (_) {\n return window.__veryfrontRouter;\n }\n };\n return {\n router,\n navigateSPA,\n renderPageFromData,\n prefetchPage,\n signalHydrationComplete,\n signalHydrationFailed\n };\n}\n\n// src/html/hydration-script-builder/runtime/renderer.ts\nfunction isModuleNotFoundError(error) {\n if (!error) return false;\n if (error instanceof SyntaxError) return false;\n const message = String(error.message || error);\n return /(?:dynamically imported module|Importing a module script failed|Failed to load module script)/i.test(message);\n}\nfunction preferReachedModuleError(earlier, later) {\n if (!earlier) return later;\n if (!later) return earlier;\n if (isModuleNotFoundError(earlier) && !isModuleNotFoundError(later)) return later;\n return earlier;\n}\nasync function loadPageModuleWithIndexFallback(basePath, pageSlug, pageModuleError, importModule) {\n try {\n return await importModule(basePath + ".js");\n } catch (error) {\n const routeError = preferReachedModuleError(pageModuleError, error);\n if (pageSlug === "index" || pageSlug.endsWith("/index")) throw routeError;\n try {\n return await importModule(basePath + "/index.js");\n } catch (indexError) {\n throw preferReachedModuleError(routeError, indexError);\n }\n }\n}\nfunction isAppRouterPath(path, appRouterRoot) {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n return normalizedPath === appRouterRoot || normalizedPath.startsWith(appRouterRoot + "/");\n}\nfunction isRootAppLayoutPath(path, appRouterRoot) {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n const pathWithoutExtension = normalizedPath.replace(/\\.(?:tsx|jsx|ts|js)$/, "");\n return pathWithoutExtension === appRouterRoot + "/layout";\n}\nfunction unwrapAppRouterDocumentLayout(LayoutComponent, React2) {\n return function AppRouterDocumentLayout(props) {\n const element = LayoutComponent(props);\n const asElement = element;\n if (!React2.isValidElement(element) || asElement.type !== "html") {\n return element;\n }\n const body = React2.Children.toArray(asElement.props?.children).find(\n (child) => React2.isValidElement(child) && child.type === "body"\n );\n return body?.props?.children ?? props.children;\n };\n}\nfunction createHydrationRenderer(deps) {\n const { env: env2, logging: logging2, componentLoader: componentLoader2, snapshotModules: snapshotModules2, moduleServerUrl: moduleServerUrl2 } = deps;\n const { window, document: document2, React: React2, RouterProvider: RouterProvider2, PageContextProvider: PageContextProvider2 } = env2;\n const { DEBUG, log, logError } = logging2;\n const { loadComponent, pathToModuleUrl } = componentLoader2;\n const { importSnapshotBoundModule } = snapshotModules2;\n async function renderPage(pathname) {\n const resolvedPathname = (() => {\n const input = typeof pathname === "string" ? pathname : window.location.pathname;\n try {\n return new URL(input, window.location.origin).pathname || "/";\n } catch (_) {\n const [pathOnly] = String(input || "/").split(/[?#]/);\n return pathOnly || "/";\n }\n })();\n const dataScript = findServerHydrationDataElement(document2);\n if (!dataScript) {\n logError("Hydration data not found");\n return;\n }\n let data = {};\n try {\n data = JSON.parse(dataScript.textContent || "{}");\n } catch (parseError) {\n logError("Failed to parse hydration data:", parseError);\n return;\n }\n log("Hydration data:", data);\n if (data.studioEmbed && window.__veryfrontSetStudioEmbed) {\n window.__veryfrontSetStudioEmbed(true);\n }\n if (window.__veryfrontSetReleaseId) {\n window.__veryfrontSetReleaseId(data.releaseId || null);\n }\n if (data.releaseAssetModules && window.__veryfrontSetReleaseAssetModules) {\n window.__veryfrontSetReleaseAssetModules(data.releaseAssetModules);\n }\n try {\n let pageModule;\n const pagePath = typeof data.pagePath === "string" ? data.pagePath : "";\n const normalizedPagePath = pagePath.replace(/^\\/+/, "");\n const normalizedAppRouterRoot = typeof data.appRouterRoot === "string" && data.appRouterRoot.replace(/^\\/+|\\/+$/g, "") ? data.appRouterRoot.replace(/^\\/+|\\/+$/g, "") : "app";\n const hasReleaseAssetModules = data.releaseAssetModules && Object.keys(data.releaseAssetModules).length > 0;\n const shouldRenderRscClientPage = data.clientModuleStrategy === "rsc-module" && !hasReleaseAssetModules && isAppRouterPath(normalizedPagePath, normalizedAppRouterRoot);\n const isolatedClientPage = data.isolatedClientPage === true;\n const loadHydrationComponent = async (path, preferRscModule) => {\n const normalizedPath = typeof path === "string" ? path.replace(/^\\/+/, "") : "";\n if (preferRscModule && isAppRouterPath(normalizedPath, normalizedAppRouterRoot)) {\n const moduleUrl = buildPinnedRscModuleUrl(path, data);\n log("Loading App Router component from RSC module:", moduleUrl);\n const module = await importSnapshotBoundModule(moduleUrl);\n return module.default || module;\n }\n return loadComponent(path, data);\n };\n let pageModuleError = null;\n if (data.pagePath) {\n const moduleUrl = shouldRenderRscClientPage ? buildPinnedRscModuleUrl(data.pagePath, data) : pathToModuleUrl(data.pagePath, data.studioEmbed, data);\n log("Loading page from hydration data:", moduleUrl);\n try {\n pageModule = await importSnapshotBoundModule(moduleUrl);\n } catch (error) {\n pageModuleError = error;\n logError("Failed to load page from hydration data:", error);\n }\n }\n if (!pageModule) {\n const pageSlug = resolvedPathname === "/" ? "index" : resolvedPathname.slice(1);\n log("Falling back to Pages Router pattern:", pageSlug);\n const prefix = pageSlug.startsWith("@/") ? "" : "/pages";\n const basePath = moduleServerUrl2 + prefix + "/" + pageSlug;\n pageModule = await loadPageModuleWithIndexFallback(\n basePath,\n pageSlug,\n pageModuleError,\n (moduleUrl) => importSnapshotBoundModule(appendDependencyPinningVersion(moduleUrl, data))\n );\n }\n if (!pageModule) {\n logError("Page module failed to load");\n return;\n }\n const PageComponent = pageModule.default || pageModule;\n if (!PageComponent) {\n logError("Page component not found");\n return;\n }\n const normalizedParams = flattenRouteParams(data.params);\n const pageProps = { ...data.props || {}, params: normalizedParams };\n let tree = React2.createElement(PageComponent, pageProps);\n const layouts = data.layouts;\n if (layouts?.length) {\n for (let i = layouts.length - 1; i >= 0; i--) {\n const layout = layouts[i];\n if (!layout) continue;\n const LayoutComponent = await loadHydrationComponent(\n layout.path,\n shouldRenderRscClientPage\n );\n if (LayoutComponent) {\n const WrappedLayoutComponent = shouldRenderRscClientPage && isRootAppLayoutPath(layout.path, normalizedAppRouterRoot) ? unwrapAppRouterDocumentLayout(LayoutComponent, React2) : LayoutComponent;\n const layoutProps = data.layoutProps?.[layout.path] || {};\n tree = React2.createElement(\n WrappedLayoutComponent,\n { ...layoutProps, children: tree }\n );\n }\n }\n }\n if (data.appPath && !isolatedClientPage) {\n const AppComponent = await loadHydrationComponent(data.appPath, shouldRenderRscClientPage);\n if (AppComponent) {\n tree = React2.createElement(AppComponent, { children: tree });\n }\n }\n if (data.errorPath) {\n const ErrorComponent = await loadHydrationComponent(\n data.errorPath,\n shouldRenderRscClientPage\n );\n if (ErrorComponent) {\n class AppRouterErrorBoundary extends React2.Component {\n constructor(props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n static getDerivedStateFromError(error) {\n return { hasError: true, error };\n }\n render() {\n if (this.state.hasError) {\n return React2.createElement(ErrorComponent, {\n error: this.state.error,\n reset: () => this.setState({ hasError: false, error: null })\n });\n }\n return this.props.children;\n }\n }\n tree = React2.createElement(AppRouterErrorBoundary, null, tree);\n }\n }\n const headings = data.headings || [];\n const pageContext = {\n slug: data.slug || "",\n path: data.pagePath || resolvedPathname,\n params: normalizedParams,\n query: Object.fromEntries(new URLSearchParams(window.location.search)),\n frontmatter: data.frontmatter || {},\n data: data.props || {},\n headings,\n mdxHeadings: headings\n // Alias for backwards compatibility\n };\n tree = React2.createElement(PageContextProvider2, { pageContext, children: tree });\n tree = React2.createElement(RouterProvider2, { router: deps.router, children: tree });\n const container = isolatedClientPage ? document2.getElementById("veryfront-page-island") : document2.getElementById("root");\n if (!container) {\n if (isolatedClientPage) {\n throw new Error("Isolated client page root not found");\n }\n return;\n }\n if (container.__reactRoot) {\n container.__reactRoot.render(tree);\n log("Page re-rendered");\n return;\n }\n if (shouldRenderRscClientPage) {\n container.__reactRoot = env2.createRoot(container);\n container.__reactRoot.render(tree);\n log("Client-side React app rendered successfully");\n } else {\n const { hydrateRoot } = await import("react-dom/client");\n const options = {\n identifierPrefix: "vf",\n onRecoverableError: (error) => {\n if (data.dev && DEBUG) {\n log("Hydration mismatch (suppressed):", error.message);\n }\n }\n };\n container.__reactRoot = hydrateRoot(container, tree, options);\n log("Client-side React app hydrated successfully");\n }\n if (window.__veryfrontHydrationComplete) {\n window.__veryfrontHydrationComplete();\n }\n } catch (error) {\n logError("Client initialization error:", error);\n if (window.__veryfrontHydrationFailed) {\n window.__veryfrontHydrationFailed(error);\n }\n }\n }\n function start() {\n window.__veryfrontRenderPage = renderPage;\n void renderPage(window.location.pathname);\n const initialDataScript = findServerHydrationDataElement(document2);\n if (initialDataScript) {\n try {\n const pageData = JSON.parse(initialDataScript.textContent || "{}");\n if (pageData.pagePath) {\n window.history.replaceState({ pageData, scrollY: 0 }, "", window.location.href);\n log("Stored initial page data in history state");\n }\n } catch (_) {\n }\n }\n }\n return { renderPage, start };\n}\n\n// src/html/hydration-script-builder/runtime/navigation-store.ts\nvar NAVIGATION_STORE_REGISTRY_KEY = "veryfront.navigation.store.v1";\nfunction resolveNavigationStore(RouterRuntime2) {\n const usesRegistryFallback2 = typeof RouterRuntime2.getNavigationStore !== "function";\n if (!usesRegistryFallback2) {\n return {\n usesRegistryFallback: usesRegistryFallback2,\n getNavigationStore: RouterRuntime2.getNavigationStore\n };\n }\n return {\n usesRegistryFallback: usesRegistryFallback2,\n getNavigationStore: () => {\n const storeKey = Symbol.for(NAVIGATION_STORE_REGISTRY_KEY);\n const registry = globalThis;\n const existing = registry[storeKey];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? loc.pathname + loc.search + loc.hash : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n registry[storeKey] = store;\n return store;\n }\n };\n}\n\n// src/html/hydration-script-builder/runtime/main.ts\nvar runtimeWindow = globalThis;\nvar runtimeDocument = globalThis.document;\nvar env = {\n window: runtimeWindow,\n document: runtimeDocument,\n fetch: (url, init) => fetch(url, init),\n React,\n RouterProvider,\n PageContextProvider,\n createRoot: (container) => createRoot(container),\n importModule: (moduleUrl) => import(moduleUrl),\n useRouterFromModule,\n setTimeout: (handler, timeout) => setTimeout(handler, timeout),\n clearTimeout: (id) => clearTimeout(id)\n};\nvar logging = createLogging(runtimeWindow);\nvar initialHydrationData = readInitialHydrationData(runtimeDocument);\nvar documentDependencyPinningCacheKey = readDocumentDependencyPinningCacheKey(\n initialHydrationData\n);\nvar routeTiming = createRouteTimingRecorder(runtimeWindow, logging);\nvar snapshotModules = createSnapshotModuleImporter({\n importModule: env.importModule,\n fetchModule: env.fetch,\n reloadDocument: () => runtimeWindow.location.reload(),\n recoveryState: runtimeWindow\n});\nvar componentLoader = createComponentLoader({\n window: runtimeWindow,\n logging,\n moduleServerUrl: moduleServerUrl(runtimeWindow),\n snapshotModules\n});\nruntimeWindow.__veryfrontClearComponentCache = componentLoader.clearComponentCache;\nruntimeWindow.__veryfrontSetStudioEmbed = componentLoader.setStudioEmbed;\nruntimeWindow.__veryfrontSetReleaseId = componentLoader.setReleaseId;\nruntimeWindow.__veryfrontSetReleaseAssetModules = componentLoader.setReleaseAssetModules;\nruntimeWindow.__veryfrontSetHMRRefreshTimestamp = componentLoader.setHMRRefreshTimestamp;\nvar { usesRegistryFallback, getNavigationStore } = resolveNavigationStore(RouterRuntime);\nvar routerRuntime = createRouterRuntime({\n env,\n logging,\n routeTiming,\n componentLoader,\n snapshotModules,\n initialHydrationData,\n documentDependencyPinningCacheKey,\n getNavigationStore,\n navigationStoreUsesRegistryFallback: usesRegistryFallback\n});\ncreateHydrationRenderer({\n env,\n logging,\n componentLoader,\n snapshotModules,\n moduleServerUrl: moduleServerUrl(runtimeWindow),\n router: routerRuntime.router\n}).start();\n'; diff --git a/src/utils/version-constant.ts b/src/utils/version-constant.ts index f387b7e46b..53fcb44c46 100644 --- a/src/utils/version-constant.ts +++ b/src/utils/version-constant.ts @@ -1,4 +1,4 @@ // Keep in sync with deno.json version. // scripts/release.ts updates this constant during releases. /** Shared version value. */ -export const VERSION = "0.1.1202"; +export const VERSION = "0.1.1203"; From c67cafce1c8c873ccaa4c05ac02111afb4197983 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:05:42 +0200 Subject: [PATCH 3/7] test(build): also discover worker entrypoints by name Ports the guard from #3398, which finds worker entrypoints by filename rather than by call site. The two are complementary: the call-site scan misses an entrypoint that exists but is not yet spawned through a recognised `new Worker(...)`, and the name scan misses workers that do not follow the naming convention. Dropping the include now trips both. --- .../build/compile-binary-includes.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unit/build/compile-binary-includes.test.ts b/tests/unit/build/compile-binary-includes.test.ts index c6a5901b8b..3d55952b75 100644 --- a/tests/unit/build/compile-binary-includes.test.ts +++ b/tests/unit/build/compile-binary-includes.test.ts @@ -54,6 +54,45 @@ describe("compile-binary includes", () => { } }); + it("should include every worker entrypoint named as one", async () => { + // Complements the call-site scan below, which finds workers by how they are + // spawned and so misses an entrypoint that exists but is not yet wired to a + // `new Worker(...)` the scan recognises. This finds them by name instead, so + // the two together cover both a worker the code spawns and a worker the + // repo merely contains. + const projectRoot = new URL("../../../", import.meta.url); + const entrypointName = /-worker-entry\.ts$|(?:^|-)worker-script\.ts$/; + + const workerEntrypoints: string[] = []; + async function collect(dir: string): Promise { + for await (const entry of Deno.readDir(new URL(dir, projectRoot))) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory) { + if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; + await collect(path); + } else if (entry.name.endsWith(".ts") && entrypointName.test(entry.name)) { + workerEntrypoints.push(path); + } + } + } + await collect("src"); + + assertEquals( + workerEntrypoints.length > 0, + true, + "expected to discover at least one worker entrypoint under src", + ); + + const includeFlags = getIncludeFlags("full"); + for (const entrypoint of workerEntrypoints) { + assertEquals( + includeFlags.some((path) => entrypoint === path || entrypoint.startsWith(`${path}/`)), + true, + `${entrypoint} must be in compile-binary.ts DEFAULT_INCLUDES; deno compile cannot trace the computed worker URL, so the binary crashes when the worker is spawned`, + ); + } + }); + it("should include every worker entrypoint spawned from a sibling URL", async () => { // `deno compile` embeds a worker only when it can statically read the // specifier. Every worker here resolves one relative to `import.meta.url`, From f25942093dbfcff93eb788c8489559f9ec4b85a6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:14:51 +0200 Subject: [PATCH 4/7] test(build): drop the absolute output path from the compile args test AGENTS.md bans local absolute paths in tests, and this one never touches the filesystem: createCompileArgs only pushes the value into the argument list, and normalizeOutputPath runs in the CLI path rather than here. A relative placeholder asserts exactly the same thing. Pre-existing; surfaced by this PR only because the helper's signature changed. --- tests/unit/build/compile-binary-includes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/build/compile-binary-includes.test.ts b/tests/unit/build/compile-binary-includes.test.ts index 3d55952b75..a62c774a92 100644 --- a/tests/unit/build/compile-binary-includes.test.ts +++ b/tests/unit/build/compile-binary-includes.test.ts @@ -11,7 +11,7 @@ describe("compile-binary includes", () => { const args = createCompileArgs({ entrypoint: profile === "proxy" ? "cli/proxy-main.ts" : "cli/main.ts", extraIncludes: [], - output: "/tmp/test-veryfront", + output: "test-veryfront", profile, }); From 136796c3bee004bb31eabc740be508337930f532 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:19:48 +0200 Subject: [PATCH 5/7] fix(build): correct the proxy exclusion rationale and widen the worker constant Review found the proxy justification rests on a false premise. It said the frozen proxy lock 'has never carried those packages, so the proxy cannot have been spawning this worker'. It does carry them -- scripts/build/proxy-deno.lock pins @babel/generator, parser, traverse and types. The compile failure is a version skew (the graph wants @babel/types@7.29.8 plus two helpers against a pinned 7.29.0), not an absence, so no safety conclusion follows from it. That matters because cli/proxy-main.ts does pull the evaluator's runner into its graph, and the exclusion is enforced by a test. The comment and the test now state the build constraint they can actually support, say plainly that this is not evidence the proxy is safe, and point at the reachability question as open. Also folds the three other computed-sibling-URL workers into UNTRACEABLE_WORKER_INCLUDES. The constant named a class but held one of its four members, leaving the rest inline under ad-hoc comments and giving the next person ambiguous precedent. ext-react-ssr's worker-renderer-bundle is deliberately left out: it is the payload the worker loads, not an entrypoint. The resolved include lists are unchanged apart from the original one-line fix -- full 34 -> 35, proxy untouched. Both wiring tests asserted over the constant, so an empty constant made them vacuously green while the real guards failed. They now assert it is populated. Emptying it trips five guards instead of three. --- scripts/build/compile-binary.ts | 30 +++++++++++-------- .../build/compile-binary-includes.test.ts | 24 ++++++++++++--- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/scripts/build/compile-binary.ts b/scripts/build/compile-binary.ts index 64693813e8..da438e8ce4 100644 --- a/scripts/build/compile-binary.ts +++ b/scripts/build/compile-binary.ts @@ -24,6 +24,9 @@ const PROJECT_ROOT = fromFileUrl(new URL("../..", import.meta.url)); */ export const UNTRACEABLE_WORKER_INCLUDES = [ "src/config/declarative-evaluator-worker-entry.ts", + "extensions/ext-react-ssr/src/worker-renderer.ts", + "extensions/ext-document-kreuzberg/src/upload-extraction-worker.ts", + "extensions/ext-document-kreuzberg/src/native-progress-extraction-worker.ts", ]; export const DEFAULT_INCLUDES = [ @@ -55,27 +58,30 @@ export const DEFAULT_INCLUDES = [ "extensions/ext-parser-babel/src/index.ts", "extensions/ext-parser-babel/src/parser-only.ts", "extensions/ext-react-ssr/src/index.ts", - // Resolved through a computed sibling URL at runtime, so compile cannot - // discover either the worker entrypoint or its embedded renderer payload. - "extensions/ext-react-ssr/src/worker-renderer.ts", + // The renderer payload the worker loads. Not an entrypoint, so it is not in + // UNTRACEABLE_WORKER_INCLUDES, but compile cannot discover it either. "extensions/ext-react-ssr/src/worker-renderer-bundle.generated.ts", "extensions/ext-yaml/src/index.ts", "extensions/ext-sandbox-shell-tools/src/index.ts", - // Spawned via `new Worker(new URL(...))`, which deno compile does not trace. - "extensions/ext-document-kreuzberg/src/upload-extraction-worker.ts", - "extensions/ext-document-kreuzberg/src/native-progress-extraction-worker.ts", "src/rendering/rsc", "src/utils/clsx.ts", "dist/framework-src", ]; export const PROXY_INCLUDES = [ - // Deliberately omits UNTRACEABLE_WORKER_INCLUDES. `cli/proxy-main.ts` pulls - // the declarative evaluator's *runner* into its module graph, but embedding - // the worker entry drags the evaluator's babel dependency tree into - // scripts/build/proxy-deno.lock, which --frozen rejects. That lock has never - // carried those packages, so the proxy cannot have been spawning this worker. - // If it ever needs to, both this list and the proxy lock must change together. + // Deliberately omits UNTRACEABLE_WORKER_INCLUDES, on build grounds only. + // Adding them fails the compile outright: the worker entry's graph wants + // @babel/types@7.29.8 plus @babel/helper-string-parser and + // @babel/helper-validator-identifier, while proxy-deno.lock pins + // @babel/types@7.29.0, and --frozen refuses to update the lock. + // + // This is NOT evidence that the proxy is safe. The lock already carries a + // babel parse toolchain (parser, generator, traverse, types), so "the deps + // are absent, therefore the worker never runs here" does not follow -- + // the conflict is a version skew, not an absence. `cli/proxy-main.ts` does + // pull the evaluator's runner into its graph, so whether the proxy can reach + // a spawn at runtime is an open question, tracked separately. If it can, this + // list and the proxy lock have to be regenerated together. // // The proxy runtime is loaded after provider activation. Providers are // statically referenced by cli/proxy-main.ts so --include does not embed the diff --git a/tests/unit/build/compile-binary-includes.test.ts b/tests/unit/build/compile-binary-includes.test.ts index a62c774a92..2408a60635 100644 --- a/tests/unit/build/compile-binary-includes.test.ts +++ b/tests/unit/build/compile-binary-includes.test.ts @@ -27,6 +27,15 @@ describe("compile-binary includes", () => { } it("should embed the untraceable worker entrypoints in the full profile", () => { + // Wiring check on the `...UNTRACEABLE_WORKER_INCLUDES` spread, not the real + // protection -- iterating the constant makes an empty constant vacuously + // green. The two discovery tests below are what actually guard the class, + // so assert the constant is populated before trusting this. + assertEquals( + UNTRACEABLE_WORKER_INCLUDES.length > 0, + true, + "UNTRACEABLE_WORKER_INCLUDES is empty, which makes this test vacuous", + ); const includeFlags = getIncludeFlags("full"); for (const workerInclude of UNTRACEABLE_WORKER_INCLUDES) { assertEquals( @@ -40,10 +49,17 @@ describe("compile-binary includes", () => { }); it("should keep untraceable workers out of the frozen proxy profile", () => { - // Not an accident worth "fixing": embedding these drags the declarative - // evaluator's babel tree into scripts/build/proxy-deno.lock, which --frozen - // rejects, so the proxy binary fails to build. If the proxy ever needs to - // spawn one, the lock has to be regenerated in the same change. + // Pins a build constraint, not a safety claim. Embedding these fails the + // proxy compile: the worker entry's graph wants a newer @babel/types than + // proxy-deno.lock pins, and --frozen refuses to update it. The lock does + // carry a babel toolchain already, so this says nothing about whether the + // proxy can reach a worker spawn -- that question is tracked separately. + // Regenerate the lock in the same change if the answer turns out to be yes. + assertEquals( + UNTRACEABLE_WORKER_INCLUDES.length > 0, + true, + "UNTRACEABLE_WORKER_INCLUDES is empty, which makes this test vacuous", + ); const includeFlags = getIncludeFlags("proxy"); for (const workerInclude of UNTRACEABLE_WORKER_INCLUDES) { assertEquals( From 6c6062d0fdb59ea125f6255a1883fe3dac8b527f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:28:28 +0200 Subject: [PATCH 6/7] fix(build): fail the build when a declared worker is not embedded Every existing check answers 'did we ask for this?'. The binary that crash-looped production would have passed all of them, because nobody asked and no test noticed. This asserts the compiled artifact actually contains each declared worker's VFS entry, and fails the build otherwise. Two defects found by running it against real binaries rather than trusting it: - Decoding a 959MB binary in one call throws 'buffer exceeds maximum length', so the check crashed instead of checking. It now streams in 8MB chunks, decoding latin1 so bytes map to characters one-for-one, carrying each chunk's tail forward so a marker split across a boundary is still found. - Expectations were derived from the resolved include list, which made the check tautological: dropping a worker from the list also dropped it from what was verified, so the exact regression this exists to catch passed. They now come from UNTRACEABLE_WORKER_INCLUDES, with the proxy profile exempt for the build reason documented on PROXY_INCLUDES. Verified end to end against real binaries. Declared but not included: exit: 1 Error: Compiled binary is missing 1 worker entrypoint(s): src/config/declarative-evaluator-worker-entry.ts. Unmodified build: exit 0. The unit tests cover the matcher, including that the dist/framework-src data asset (...worker-entry.ts.src) does not count as an embedded worker -- its body symbols appear in a broken binary too, so only the exact VFS entry discriminates. --- scripts/build/compile-binary.ts | 97 +++++++++++++++++++ .../build/compile-binary-includes.test.ts | 31 ++++++ 2 files changed, 128 insertions(+) diff --git a/scripts/build/compile-binary.ts b/scripts/build/compile-binary.ts index da438e8ce4..9cbc2fc4c4 100644 --- a/scripts/build/compile-binary.ts +++ b/scripts/build/compile-binary.ts @@ -142,6 +142,101 @@ export function createCompileArgs(options: CompileBinaryOptions): string[] { return args; } +/** + * Names the worker entries a compiled binary is missing. + * + * Every include-list check answers "did we ask for this?", which the binary + * that crash-looped production would have passed -- it was asked for by nobody + * and no test noticed. This answers "did it actually ship?", which is the only + * question whose answer differs between a working and a broken binary. + * + * Matches the embedded VFS entry (`"n":""`) rather than worker body + * symbols. `dist/framework-src` ships a renamed copy of the same source as a + * data asset (`...worker-entry.ts.src`), so body symbols appear in a broken + * binary too and read as a pass. The trailing quote keeps `.ts` from matching + * `.ts.src`. + */ +export function findMissingEmbeddedWorkers( + binaryContent: string, + workerIncludes: readonly string[], +): string[] { + return workerIncludes.filter((include) => { + const fileName = include.slice(include.lastIndexOf("/") + 1); + return !binaryContent.includes(`"n":"${fileName}"`); + }); +} + +/** + * Streams the binary looking for each worker's VFS entry. + * + * Reads in chunks rather than decoding the file at once: these binaries embed + * hundreds of megabytes, and a single decode throws "buffer exceeds maximum + * length" well before it can check anything. Decodes as latin1 so bytes map to + * characters one-for-one and the ASCII markers survive arbitrary binary data, + * and carries the tail of each chunk forward so a marker split across a + * boundary is still found. + */ +async function findMissingEmbeddedWorkersInFile( + path: string, + workerIncludes: readonly string[], +): Promise { + const CHUNK_BYTES = 8 * 1024 * 1024; + const markerLength = (include: string) => include.length - include.lastIndexOf("/") + 6; + const carryLength = Math.max(...workerIncludes.map(markerLength)); + + const remaining = new Set(workerIncludes); + const decoder = new TextDecoder("latin1"); + const buffer = new Uint8Array(CHUNK_BYTES); + const file = await Deno.open(path, { read: true }); + + try { + let carry = ""; + while (remaining.size > 0) { + const bytesRead = await file.read(buffer); + if (bytesRead === null) break; + + const text = carry + decoder.decode(buffer.subarray(0, bytesRead)); + for (const include of [...remaining]) { + if (findMissingEmbeddedWorkers(text, [include]).length === 0) { + remaining.delete(include); + } + } + carry = text.slice(-carryLength); + } + } finally { + file.close(); + } + + return [...remaining]; +} + +async function assertWorkersEmbedded( + outputPath: string, + profile: CompileBinaryProfile, +): Promise { + // Expectations come from the declared constant, NOT from the resolved include + // list. Deriving them from the include list makes this tautological: dropping + // a worker from the list would also drop it from what is checked, so the one + // regression this exists to catch would pass. The proxy profile is the sole + // exemption, for the build reason documented on PROXY_INCLUDES. + const expected = profile === "proxy" ? [] : UNTRACEABLE_WORKER_INCLUDES; + if (expected.length === 0) { + return; + } + + const missing = await findMissingEmbeddedWorkersInFile( + normalizeOutputPath(outputPath), + expected, + ); + if (missing.length > 0) { + throw new Error( + `Compiled binary is missing ${missing.length} worker entrypoint(s): ${ + missing.join(", ") + }.\nThe binary would start, serve traffic, and crash the first time one is spawned.`, + ); + } +} + export async function compileBinary(options: CompileBinaryOptions): Promise { const result = await new Deno.Command("deno", { args: createCompileArgs(options), @@ -153,6 +248,8 @@ export async function compileBinary(options: CompileBinaryOptions): Promise { return includeFlags; } + it("should detect a worker entry missing from a compiled binary", () => { + // The include-list tests all answer "did we ask for this?". The binary that + // crash-looped production would pass every one of them. This answers "did it + // ship?", which is the only question whose answer differed. + const embedded = '{"File":{"n":"declarative-evaluator-worker-entry.ts","o":[1,2]}}'; + assertEquals( + findMissingEmbeddedWorkers(embedded, ["src/config/declarative-evaluator-worker-entry.ts"]), + [], + ); + assertEquals( + findMissingEmbeddedWorkers("", ["src/config/declarative-evaluator-worker-entry.ts"]), + ["src/config/declarative-evaluator-worker-entry.ts"], + ); + }); + + it("should not accept the framework-src data asset as an embedded worker", () => { + // dist/framework-src ships the same source renamed `.ts.src` so compile + // treats it as data, not a module. A broken binary therefore still contains + // the worker's body and its `.src` name. Only the exact VFS entry + // discriminates, so the trailing quote in the marker is load-bearing. + const brokenBinary = + '{"File":{"n":"declarative-evaluator-worker-entry.ts.src","o":[1,2]}} evaluateRequest'; + assertEquals( + findMissingEmbeddedWorkers(brokenBinary, [ + "src/config/declarative-evaluator-worker-entry.ts", + ]), + ["src/config/declarative-evaluator-worker-entry.ts"], + ); + }); + it("should embed the untraceable worker entrypoints in the full profile", () => { // Wiring check on the `...UNTRACEABLE_WORKER_INCLUDES` spread, not the real // protection -- iterating the constant makes an empty constant vacuously From 0dd76b5ca8f0e4a227a2d0c82a043c248ff7833c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 01:37:03 +0200 Subject: [PATCH 7/7] docs(build): point the proxy exclusion at its tracking issue The comment said the reachability question was tracked separately without saying where. It is veryfront-issue-inbox#382. --- scripts/build/compile-binary.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/compile-binary.ts b/scripts/build/compile-binary.ts index 9cbc2fc4c4..6f9dd0c7f6 100644 --- a/scripts/build/compile-binary.ts +++ b/scripts/build/compile-binary.ts @@ -80,8 +80,8 @@ export const PROXY_INCLUDES = [ // are absent, therefore the worker never runs here" does not follow -- // the conflict is a version skew, not an absence. `cli/proxy-main.ts` does // pull the evaluator's runner into its graph, so whether the proxy can reach - // a spawn at runtime is an open question, tracked separately. If it can, this - // list and the proxy lock have to be regenerated together. + // a spawn at runtime is an open question, tracked in veryfront-issue-inbox#382. + // If it can, this list and the proxy lock have to be regenerated together. // // The proxy runtime is loaded after provider activation. Providers are // statically referenced by cli/proxy-main.ts so --include does not embed the