@timber-js/app 0.2.0-alpha.151 → 0.2.0-alpha.152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"internal.js","names":[],"sources":["../../src/server/server-timing.ts","../../src/server/instrumentation.ts","../../src/server/pipeline-helpers.ts","../../src/server/proxy.ts","../../src/server/middleware-runner.ts","../../src/server/metadata-social.ts","../../src/server/metadata-platform.ts","../../src/server/metadata-render.ts","../../src/server/metadata.ts","../../src/client/error-reconstituter.tsx","../../src/server/safe-load.ts","../../src/server/status-code-resolver.ts","../../src/server/deny-boundary.ts","../../src/server/access-gate.tsx","../../src/server/param-coercion.ts","../../src/client/segment-update-context.ts","../../src/client/segment-outlet.tsx","../../src/server/route-element-builder.ts","../../src/server/version-skew.ts","../../src/server/pipeline-metadata.ts","../../src/server/pipeline-interception.ts","../../src/server/pipeline-outcome.ts","../../src/server/pipeline-phases.ts","../../src/server/pipeline.ts","../../src/server/build-manifest.ts","../../src/server/early-hints.ts","../../src/server/early-hints-sender.ts","../../src/server/tree-builder.ts","../../src/server/csrf.ts","../../src/server/body-limits.ts","../../src/server/route-handler.ts","../../src/server/render-timeout.ts"],"sourcesContent":["/**\n * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.\n *\n * Collects timing entries per request using ALS. Each pipeline phase\n * (proxy, middleware, render, SSR, access, fetch) records an entry.\n * Before response flush, entries are formatted into a Server-Timing header.\n *\n * Only active in dev mode — zero overhead in production.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing\n * Task: LOCAL-290\n */\n\nimport { timingAls } from './als-registry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface TimingEntry {\n /** Metric name (alphanumeric + hyphens, no spaces). */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Human-readable description (shown in DevTools). */\n desc?: string;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Run a callback with a per-request timing collector.\n * Must be called at the top of the request pipeline (wraps the full request).\n */\nexport function runWithTimingCollector<T>(fn: () => T): T {\n return timingAls.run({ entries: [] }, fn);\n}\n\n/**\n * Record a timing entry for the current request.\n * No-ops if called outside a timing collector (e.g. in production).\n */\nexport function recordTiming(entry: TimingEntry): void {\n const store = timingAls.getStore();\n if (!store) return;\n store.entries.push(entry);\n}\n\n/**\n * Run a function and automatically record its duration as a timing entry.\n * Returns the function's result. No-ops the recording if outside a collector.\n */\nexport async function withTiming<T>(\n name: string,\n desc: string | undefined,\n fn: () => T | Promise<T>\n): Promise<T> {\n const store = timingAls.getStore();\n if (!store) return fn();\n\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const dur = Math.round(performance.now() - start);\n store.entries.push({ name, dur, desc });\n }\n}\n\n/**\n * Get the Server-Timing header value for the current request.\n * Returns null if no entries exist or outside a collector.\n *\n * Format: `name;dur=123;desc=\"description\", name2;dur=456`\n * See RFC 6797 / Server-Timing spec for format details.\n */\nexport function getServerTimingHeader(): string | null {\n const store = timingAls.getStore();\n if (!store || store.entries.length === 0) return null;\n\n // Deduplicate names — if a name appears multiple times, suffix with index\n const nameCounts = new Map<string, number>();\n const entries = store.entries.map((entry) => {\n const count = nameCounts.get(entry.name) ?? 0;\n nameCounts.set(entry.name, count + 1);\n const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;\n return { ...entry, name: uniqueName };\n });\n\n const parts = entries.map((entry) => {\n let part = `${entry.name};dur=${entry.dur}`;\n if (entry.desc) {\n // Escape quotes in desc per Server-Timing spec\n const safeDesc = entry.desc.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n part += `;desc=\"${safeDesc}\"`;\n }\n return part;\n });\n\n // Respect header size limits — browsers typically handle up to 8KB headers.\n // Truncate if the header exceeds 4KB to leave room for other headers.\n const MAX_HEADER_SIZE = 4096;\n let result = '';\n for (let i = 0; i < parts.length; i++) {\n const candidate = result ? `${result}, ${parts[i]}` : parts[i]!;\n if (candidate.length > MAX_HEADER_SIZE) break;\n result = candidate;\n }\n\n return result || null;\n}\n\n/**\n * Sanitize a URL for use in Server-Timing desc.\n * Strips query params and truncates long paths to avoid information leakage.\n */\nexport function sanitizeUrlForTiming(url: string): string {\n try {\n const parsed = new URL(url);\n const origin = parsed.host;\n let path = parsed.pathname;\n // Truncate long paths\n if (path.length > 50) {\n path = path.slice(0, 47) + '...';\n }\n return `${origin}${path}`;\n } catch {\n // Not a valid URL — truncate raw string\n if (url.length > 60) {\n return url.slice(0, 57) + '...';\n }\n return url;\n }\n}\n","/**\n * Instrumentation — loads and runs the user's instrumentation.ts file.\n *\n * instrumentation.ts is a file convention at the project root that exports:\n * - register() — called once at server startup, before the first request\n * - onRequestError() — called for every unhandled server error\n * - logger — any object with info/warn/error/debug methods\n *\n * See design/17-logging.md §\"instrumentation.ts — The Entry Point\"\n */\n\nimport { setLogger, type TimberLogger } from './logger.js';\n\n// ─── Instrumentation Types ────────────────────────────────────────────────\n\nexport type InstrumentationOnRequestError = (\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n) => void | Promise<void>;\n\nexport interface InstrumentationRequestInfo {\n /** HTTP method: 'GET', 'POST', etc. */\n method: string;\n /** Request path: '/dashboard/projects/123' */\n path: string;\n /** Request headers as a plain object. */\n headers: Record<string, string>;\n}\n\nexport interface InstrumentationErrorContext {\n /** Which pipeline phase the error occurred in. */\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route';\n /** The route pattern: '/dashboard/projects/[id]' */\n routePath: string;\n /** Type of route that was matched. */\n routeType: 'page' | 'route' | 'action';\n /** Always set — OTEL trace ID or UUID fallback. */\n traceId: string;\n}\n\n// ─── Instrumentation Module Shape ─────────────────────────────────────────\n\ninterface InstrumentationModule {\n register?: () => void | Promise<void>;\n onRequestError?: InstrumentationOnRequestError;\n logger?: TimberLogger;\n}\n\n// ─── State ────────────────────────────────────────────────────────────────\n//\n// Intentional per-app singletons (not per-request). Instrumentation loads\n// once at server startup and persists for the lifetime of the process/isolate.\n// These must NOT be migrated to ALS — they are correctly scoped to the app.\n\nlet _initialized = false;\nlet _onRequestError: InstrumentationOnRequestError | null = null;\n\n/**\n * Load and initialize the user's instrumentation.ts module.\n *\n * - Awaits register() before returning (server blocks on this).\n * - Picks up the logger export and wires it into the framework logger.\n * - Stores onRequestError for later invocation.\n *\n * @param loader - Function that dynamically imports the user's instrumentation module.\n * Returns null if no instrumentation.ts exists.\n */\nexport async function loadInstrumentation(\n loader: () => Promise<InstrumentationModule | null>\n): Promise<void> {\n if (_initialized) return;\n _initialized = true;\n\n let mod: InstrumentationModule | null;\n try {\n mod = await loader();\n } catch (error) {\n console.error('[timber] Failed to load instrumentation.ts:', error);\n return;\n }\n\n if (!mod) return;\n\n // Wire up the logger export\n if (mod.logger && typeof mod.logger.info === 'function') {\n setLogger(mod.logger);\n }\n\n // Store onRequestError for later\n if (typeof mod.onRequestError === 'function') {\n _onRequestError = mod.onRequestError;\n }\n\n // Await register() — server does not accept requests until this resolves\n if (typeof mod.register === 'function') {\n try {\n await mod.register();\n } catch (error) {\n console.error('[timber] instrumentation.ts register() threw:', error);\n throw error;\n }\n }\n}\n\n/**\n * Call the user's onRequestError hook. Catches and logs any errors thrown\n * by the hook itself — it must not affect the response.\n */\nexport async function callOnRequestError(\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n): Promise<void> {\n if (!_onRequestError) return;\n try {\n await _onRequestError(error, request, context);\n } catch (hookError) {\n console.error('[timber] onRequestError hook threw:', hookError);\n }\n}\n\n/**\n * Check if onRequestError is registered.\n */\nexport function hasOnRequestError(): boolean {\n return _onRequestError !== null;\n}\n\n/**\n * Reset instrumentation state. Test-only.\n */\nexport function resetInstrumentation(): void {\n _initialized = false;\n _onRequestError = null;\n}\n","/**\n * Pipeline helpers — small utility functions used by `pipeline.ts` and\n * `pipeline-phases.ts`. Lifted out of `pipeline.ts` to keep that file\n * focused on the request handler entry point.\n *\n * Each helper is intentionally a free function with no closure capture, so\n * it can be unit-tested in isolation.\n *\n * See design/07-routing.md §\"Request Lifecycle\".\n */\n\nimport type { ProxyExport } from './proxy.js';\nimport { getSetCookieHeaders } from './cookie-context.js';\nimport { callOnRequestError } from './instrumentation.js';\nimport { getTraceId } from './tracing.js';\nimport { RedirectSignal } from './primitives.js';\nimport type { ProxyConfig } from './pipeline.js';\n\n// ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────\n\n/**\n * Only __proto__ needs stripping — it has a language-level setter that\n * changes the prototype chain of spread copies. constructor and prototype\n * are harmless own properties on null-prototype objects.\n */\nconst DANGEROUS_KEYS = new Set(['__proto__']);\n\n/**\n * Deep-walk a value returned by a segment param codec, producing a\n * sanitized copy where every plain object is null-prototype and\n * dangerous keys (__proto__, constructor, prototype) are stripped at\n * every depth.\n *\n * Non-plain objects (Date, Map, class instances, etc.) are returned\n * as-is — they cannot be poisoned by `{...x}` spread and may carry\n * author-intended prototype methods.\n *\n * Arrays are walked element-wise.\n *\n * Performance: URL params are bounded by URL length (~8 KB). Realistic\n * trees are <1 KB. The recursive walk is sub-microsecond.\n *\n * See TIM-655, TIM-855, TIM-873, design/13-security.md\n */\nexport function sanitizeParamValue(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n\n if (Array.isArray(value)) {\n return value.map(sanitizeParamValue);\n }\n\n // Only walk plain objects — anything with a custom prototype (Date, Map,\n // class instances) is left untouched.\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n\n const out: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(value as Record<string, unknown>)) {\n if (!DANGEROUS_KEYS.has(key)) {\n out[key] = sanitizeParamValue((value as Record<string, unknown>)[key]);\n }\n }\n return out;\n}\n\n// ─── Proxy Resolver ────────────────────────────────────────────────────────\n\n/**\n * Resolver closure produced once at pipeline construction. The lazy variant\n * still calls `loader()` per-request (HMR relies on re-importing), but the\n * choice of which branch to take is made once, not on every request.\n */\nexport type ProxyResolver = () => ProxyExport | Promise<ProxyExport>;\n\n/**\n * Build a proxy resolver closure from the declared source. Called exactly\n * once at `createPipeline` setup time, so the hot path sees only the branch\n * that corresponds to this pipeline's configured variant.\n *\n * Returns `null` when the app has no proxy.ts — the hot path short-circuits\n * around `runProxyPhase` entirely in that case.\n *\n * Accepts the sugar form (a bare `ProxyExport` — function or function array)\n * and normalises it to the static variant. Functions and arrays are\n * structurally distinct from the tagged `{ kind: 'lazy', loader }` object,\n * so discrimination is unambiguous.\n */\nexport function makeProxyResolver(\n proxy: ProxyConfig | ProxyExport | undefined\n): ProxyResolver | null {\n if (proxy === undefined) return null;\n // Sugar: a bare ProxyExport (function or function array) — treat as static.\n if (typeof proxy === 'function' || Array.isArray(proxy)) {\n const exp = proxy;\n return () => exp;\n }\n if (proxy.kind === 'static') {\n const exp = proxy.export;\n return () => exp;\n }\n const loader = proxy.loader;\n return async () => (await loader()).default;\n}\n\n// ─── Cookie / Header Helpers ───────────────────────────────────────────────\n\n/**\n * Apply all Set-Cookie headers from the cookie jar to a Headers object.\n * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.\n */\nexport function applyCookieJar(headers: Headers): void {\n for (const value of getSetCookieHeaders()) {\n headers.append('Set-Cookie', value);\n }\n}\n\n/**\n * Merge framework-managed response headers onto a terminal response without\n * overwriting headers the terminal response already set itself.\n */\nexport function mergeMissingHeaders(target: Headers, source: Headers): void {\n const existingKeys = new Set([...target.keys()].map((key) => key.toLowerCase()));\n for (const [key, value] of source.entries()) {\n if (!existingKeys.has(key.toLowerCase())) {\n target.append(key, value);\n }\n }\n}\n\n// ─── Mutable Response Cloning ──────────────────────────────────────────────\n\n/**\n * Clone a Response into a fresh one whose header bag is guaranteed mutable.\n *\n * `Response.redirect()` and some platform-level passthrough responses (notably\n * on Cloudflare Workers) return objects with frozen header bags. Calling\n * `.set()` or `.append()` on them throws `TypeError: immutable`, which the\n * pipeline can hit when it appends Set-Cookie or Server-Timing entries.\n *\n * The pipeline calls this at the producer sites where user-controlled\n * responses enter the framework — `outcomeToResponse` for all phase outcomes,\n * and `handleRequest` for metadata-route and auto-sitemap user handlers — so\n * downstream code can write headers without runtime feature-detection.\n *\n * The clone is unconditional. This is a deliberate trade: we avoid a\n * try/catch + thrown `TypeError` on every request (the previous probe-based\n * approach paid that cost on the hot path) and accept one cheap Response\n * rewrap at the framework boundary instead.\n */\nexport function cloneWithMutableHeaders(response: Response): Response {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n}\n\n// ─── Redirect Builder ──────────────────────────────────────────────────────\n\n/**\n * Build a redirect Response from a RedirectSignal.\n *\n * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect\n * so the client router can perform a soft SPA redirect. A raw 302 would be\n * turned into an opaque redirect by fetch({redirect:'manual'}), crashing\n * createFromFetch. See design/19-client-navigation.md.\n */\nexport function buildRedirectResponse(\n signal: RedirectSignal,\n req: Request,\n headers: Headers\n): Response {\n const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRsc) {\n headers.set('X-Timber-Redirect', signal.location);\n return new Response(null, { status: 204, headers });\n }\n headers.set('Location', signal.location);\n return new Response(null, { status: signal.status, headers });\n}\n\n// ─── Instrumentation ───────────────────────────────────────────────────────\n\n/**\n * Fire the user's onRequestError hook with request context.\n * Extracts request info from the Request object and calls the instrumentation hook.\n */\nexport async function fireOnRequestError(\n error: unknown,\n req: Request,\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'\n): Promise<void> {\n const url = new URL(req.url);\n const headersObj: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headersObj[k] = v;\n });\n\n await callOnRequestError(\n error,\n { method: req.method, path: url.pathname, headers: headersObj },\n { phase, routePath: url.pathname, routeType: 'page', traceId: getTraceId() }\n );\n}\n","/**\n * Proxy runner — executes app/proxy.ts before route matching.\n *\n * Supports two forms:\n * - Function: (req, next) => Promise<Response>\n * - Array: middleware functions composed left-to-right\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\"\n */\n\n/** Signature for a single proxy middleware function. */\nexport type ProxyFn = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** The proxy.ts default export — either a function or an array of functions. */\nexport type ProxyExport = ProxyFn | ProxyFn[];\n\n/**\n * Run the proxy pipeline.\n *\n * @param proxyExport - The default export from proxy.ts (function or array)\n * @param req - The incoming request\n * @param next - The continuation that proceeds to route matching and rendering\n * @returns The final response\n */\nexport async function runProxy(\n proxyExport: ProxyExport,\n req: Request,\n next: () => Promise<Response>\n): Promise<Response> {\n const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];\n\n // Compose left-to-right: first item's next() calls the second, etc.\n // The last item's next() calls the original `next` (route matching + render).\n let i = fns.length;\n let composed = next;\n while (i--) {\n const fn = fns[i]!;\n const downstream = composed;\n composed = () => Promise.resolve(fn(req, downstream));\n }\n\n return composed();\n}\n","/**\n * Middleware runner — executes a route's middleware.ts chain before rendering.\n *\n * All middleware.ts files in the segment chain run, root to leaf (top-down).\n * The first middleware that returns a Response short-circuits the chain.\n * There is no next() — each middleware is independent.\n *\n * See design/07-routing.md §\"middleware.ts\"\n */\n\nimport type { MiddlewareContext } from './types.js';\n\n/** Signature of a middleware.ts default export. */\nexport type MiddlewareFn = (ctx: MiddlewareContext) => Response | void | Promise<Response | void>;\n\n/**\n * Run a route's middleware function.\n *\n * @param middlewareFn - The default export from the route's middleware.ts\n * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)\n * @returns A Response if middleware short-circuited, or undefined to continue\n */\nexport async function runMiddleware(\n middlewareFn: MiddlewareFn,\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n const result = await middlewareFn(ctx);\n if (result instanceof Response) {\n return result;\n }\n return undefined;\n}\n\n/**\n * Run all middleware functions in the segment chain, root to leaf.\n *\n * Execution is top-down: root middleware runs first, leaf middleware runs last.\n * All middleware share the same MiddlewareContext — a parent that sets\n * ctx.requestHeaders makes it visible to child middleware and downstream components.\n *\n * Short-circuits on the first middleware that returns a Response.\n * Remaining middleware in the chain do not execute.\n *\n * @param chain - Middleware functions ordered root-to-leaf\n * @param ctx - Shared middleware context\n * @returns A Response if any middleware short-circuited, or undefined to continue\n */\nexport async function runMiddlewareChain(\n chain: MiddlewareFn[],\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n for (const fn of chain) {\n const result = await fn(ctx);\n if (result instanceof Response) {\n return result;\n }\n }\n return undefined;\n}\n\n// ─── Per-Request Middleware Bypass ─────────────────────────────────────────\n\n/**\n * Per-request marker for synthetic re-render requests that should NOT\n * re-execute `middleware.ts`. The action-dispatch wrapper runs middleware\n * once on the inbound action POST; when validation fails on the no-JS\n * path, it builds a synthetic GET that flows through the normal pipeline\n * to render the page with `getFormFlash()` data. Without this marker, the\n * pipeline would run middleware a second time on that synthetic GET.\n *\n * The set is keyed by the synthetic Request object itself, so the entry\n * lives exactly as long as the request and is garbage-collected with it.\n * Cannot be set or detected by user code — there is no header, no URL\n * parameter, nothing on the wire that an attacker could spoof.\n *\n * See TIM-871.\n *\n * @internal — framework use only.\n */\nconst middlewareBypassRequests = new WeakSet<Request>();\n\n/**\n * Mark a request so the pipeline skips its middleware phase.\n *\n * Used by `wrap-action-dispatch.ts` for the no-JS form-rerender path.\n *\n * @internal\n */\nexport function markRequestBypassMiddleware(req: Request): void {\n middlewareBypassRequests.add(req);\n}\n\n/**\n * Check whether a request was marked to bypass middleware.\n *\n * Called by `handleRequest` in pipeline-phases.ts before invoking the\n * middleware phase. Returns false for any request not explicitly marked.\n *\n * @internal\n */\nexport function shouldBypassMiddleware(req: Request): boolean {\n return middlewareBypassRequests.has(req);\n}\n","/**\n * Social metadata rendering — Open Graph and Twitter Card meta tags.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render Open Graph metadata into head element descriptors.\n *\n * Handles og:title, og:description, og:image (with dimensions/alt),\n * og:video, og:audio, og:article:author, and other OG properties.\n */\nexport function renderOpenGraph(\n og: NonNullable<Metadata['openGraph']>,\n elements: HeadElement[]\n): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['og:title', og.title],\n ['og:description', og.description],\n ['og:url', og.url],\n ['og:site_name', og.siteName],\n ['og:locale', og.locale],\n ['og:type', og.type],\n ['og:article:published_time', og.publishedTime],\n ['og:article:modified_time', og.modifiedTime],\n ];\n\n for (const [property, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { property, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (og.images) {\n if (typeof og.images === 'string') {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: og.images } });\n } else {\n const imgList = Array.isArray(og.images) ? og.images : [og.images];\n for (const img of imgList) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: img.url } });\n if (img.width) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:width', content: String(img.width) },\n });\n }\n if (img.height) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:height', content: String(img.height) },\n });\n }\n if (img.alt) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image:alt', content: img.alt } });\n }\n }\n }\n }\n\n // Videos\n if (og.videos) {\n for (const video of og.videos) {\n elements.push({ tag: 'meta', attrs: { property: 'og:video', content: video.url } });\n }\n }\n\n // Audio\n if (og.audio) {\n for (const audio of og.audio) {\n elements.push({ tag: 'meta', attrs: { property: 'og:audio', content: audio.url } });\n }\n }\n\n // Authors\n if (og.authors) {\n for (const author of og.authors) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:article:author', content: author },\n });\n }\n }\n}\n\n/**\n * Render Twitter Card metadata into head element descriptors.\n *\n * Handles twitter:card, twitter:site, twitter:title, twitter:image,\n * twitter:player, and twitter:app (per-platform name/id/url).\n */\nexport function renderTwitter(tw: NonNullable<Metadata['twitter']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['twitter:card', tw.card],\n ['twitter:site', tw.site],\n ['twitter:site:id', tw.siteId],\n ['twitter:title', tw.title],\n ['twitter:description', tw.description],\n ['twitter:creator', tw.creator],\n ['twitter:creator:id', tw.creatorId],\n ];\n\n for (const [name, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (tw.images) {\n if (typeof tw.images === 'string') {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: tw.images } });\n } else {\n const imgList = Array.isArray(tw.images) ? tw.images : [tw.images];\n for (const img of imgList) {\n const url = typeof img === 'string' ? img : img.url;\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: url } });\n }\n }\n }\n\n // Player card fields\n if (tw.players) {\n for (const player of tw.players) {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:player', content: player.playerUrl } });\n if (player.width) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:width', content: String(player.width) },\n });\n }\n if (player.height) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:height', content: String(player.height) },\n });\n }\n if (player.streamUrl) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:stream', content: player.streamUrl },\n });\n }\n }\n }\n\n // App card fields — 3 platforms × 3 attributes (name, id, url)\n if (tw.app) {\n const platforms: Array<[keyof NonNullable<typeof tw.app.id>, string]> = [\n ['iPhone', 'iphone'],\n ['iPad', 'ipad'],\n ['googlePlay', 'googleplay'],\n ];\n\n // App name is shared across platforms but the spec uses per-platform names.\n // Emit for each platform that has an ID.\n if (tw.app.name) {\n for (const [key, tag] of platforms) {\n if (tw.app.id?.[key]) {\n elements.push({\n tag: 'meta',\n attrs: { name: `twitter:app:name:${tag}`, content: tw.app.name },\n });\n }\n }\n }\n\n for (const [key, tag] of platforms) {\n const id = tw.app.id?.[key];\n if (id) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:id:${tag}`, content: id } });\n }\n }\n\n for (const [key, tag] of platforms) {\n const url = tw.app.url?.[key];\n if (url) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:url:${tag}`, content: url } });\n }\n }\n }\n}\n","/**\n * Platform-specific metadata rendering — icons, Apple Web App, App Links, iTunes.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render icon link elements (favicon, shortcut, apple-touch-icon, custom).\n */\nexport function renderIcons(icons: NonNullable<Metadata['icons']>, elements: HeadElement[]): void {\n // Icon\n if (icons.icon) {\n if (typeof icons.icon === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'icon', href: icons.icon } });\n } else if (Array.isArray(icons.icon)) {\n for (const icon of icons.icon) {\n const attrs: Record<string, string> = { rel: 'icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Shortcut\n if (icons.shortcut) {\n const urls = Array.isArray(icons.shortcut) ? icons.shortcut : [icons.shortcut];\n for (const url of urls) {\n elements.push({ tag: 'link', attrs: { rel: 'shortcut icon', href: url } });\n }\n }\n\n // Apple\n if (icons.apple) {\n if (typeof icons.apple === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'apple-touch-icon', href: icons.apple } });\n } else if (Array.isArray(icons.apple)) {\n for (const icon of icons.apple) {\n const attrs: Record<string, string> = { rel: 'apple-touch-icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Other\n if (icons.other) {\n for (const icon of icons.other) {\n const attrs: Record<string, string> = { rel: icon.rel, href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render alternate link elements (canonical, hreflang, media, types).\n */\nexport function renderAlternates(\n alternates: NonNullable<Metadata['alternates']>,\n elements: HeadElement[]\n): void {\n if (alternates.canonical) {\n elements.push({ tag: 'link', attrs: { rel: 'canonical', href: alternates.canonical } });\n }\n\n if (alternates.languages) {\n for (const [lang, href] of Object.entries(alternates.languages)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', hreflang: lang, href },\n });\n }\n }\n\n if (alternates.media) {\n for (const [media, href] of Object.entries(alternates.media)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', media, href },\n });\n }\n }\n\n if (alternates.types) {\n for (const [type, href] of Object.entries(alternates.types)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', type, href },\n });\n }\n }\n}\n\n/**\n * Render site verification meta tags (Google, Yahoo, Yandex, custom).\n */\nexport function renderVerification(\n verification: NonNullable<Metadata['verification']>,\n elements: HeadElement[]\n): void {\n const verificationProps: Array<[string, string | undefined]> = [\n ['google-site-verification', verification.google],\n ['y_key', verification.yahoo],\n ['yandex-verification', verification.yandex],\n ];\n\n for (const [name, content] of verificationProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n if (verification.other) {\n for (const [name, value] of Object.entries(verification.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n}\n\n/**\n * Render Apple Web App meta tags and startup image links.\n */\nexport function renderAppleWebApp(\n appleWebApp: NonNullable<Metadata['appleWebApp']>,\n elements: HeadElement[]\n): void {\n if (appleWebApp.capable) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-capable', content: 'yes' },\n });\n }\n if (appleWebApp.title) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-title', content: appleWebApp.title },\n });\n }\n if (appleWebApp.statusBarStyle) {\n elements.push({\n tag: 'meta',\n attrs: {\n name: 'apple-mobile-web-app-status-bar-style',\n content: appleWebApp.statusBarStyle,\n },\n });\n }\n if (appleWebApp.startupImage) {\n const images = Array.isArray(appleWebApp.startupImage)\n ? appleWebApp.startupImage\n : [{ url: appleWebApp.startupImage }];\n for (const img of images) {\n const url = typeof img === 'string' ? img : img.url;\n const attrs: Record<string, string> = { rel: 'apple-touch-startup-image', href: url };\n if (typeof img === 'object' && img.media) {\n attrs.media = img.media;\n }\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render App Links (al:*) meta tags for deep linking across platforms.\n */\nexport function renderAppLinks(\n appLinks: NonNullable<Metadata['appLinks']>,\n elements: HeadElement[]\n): void {\n const platformEntries: Array<[string, Array<Record<string, unknown>> | undefined]> = [\n ['ios', appLinks.ios],\n ['android', appLinks.android],\n ['windows', appLinks.windows],\n ['windows_phone', appLinks.windowsPhone],\n ['windows_universal', appLinks.windowsUniversal],\n ];\n\n for (const [platform, entries] of platformEntries) {\n if (!entries) continue;\n for (const entry of entries) {\n for (const [key, value] of Object.entries(entry)) {\n if (value !== undefined && value !== null) {\n elements.push({\n tag: 'meta',\n attrs: { property: `al:${platform}:${key}`, content: String(value) },\n });\n }\n }\n }\n }\n\n if (appLinks.web) {\n if (appLinks.web.url) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'al:web:url', content: appLinks.web.url },\n });\n }\n if (appLinks.web.shouldFallback !== undefined) {\n elements.push({\n tag: 'meta',\n attrs: {\n property: 'al:web:should_fallback',\n content: appLinks.web.shouldFallback ? 'true' : 'false',\n },\n });\n }\n }\n}\n\n/**\n * Render Apple iTunes smart banner meta tag.\n */\nexport function renderItunes(\n itunes: NonNullable<Metadata['itunes']>,\n elements: HeadElement[]\n): void {\n const parts = [`app-id=${itunes.appId}`];\n if (itunes.affiliateData) parts.push(`affiliate-data=${itunes.affiliateData}`);\n if (itunes.appArgument) parts.push(`app-argument=${itunes.appArgument}`);\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-itunes-app', content: parts.join(', ') },\n });\n}\n","/**\n * Metadata rendering — converts resolved Metadata into HeadElement descriptors.\n *\n * Extracted from metadata.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\nimport { renderOpenGraph, renderTwitter } from './metadata-social.js';\nimport {\n renderIcons,\n renderAlternates,\n renderVerification,\n renderAppleWebApp,\n renderAppLinks,\n renderItunes,\n} from './metadata-platform.js';\n\n// ─── Render to Elements ──────────────────────────────────────────────────────\n\n/**\n * Convert resolved metadata into an array of head element descriptors.\n *\n * Each descriptor has a `tag` ('title', 'meta', 'link') and either\n * `content` (for <title>) or `attrs` (for <meta>/<link>).\n *\n * The framework's MetadataResolver component consumes these descriptors\n * and renders them into the <head>.\n */\nexport function renderMetadataToElements(metadata: Metadata): HeadElement[] {\n const elements: HeadElement[] = [];\n\n // Title\n if (typeof metadata.title === 'string') {\n elements.push({ tag: 'title', content: metadata.title });\n }\n\n // Simple string meta tags\n const simpleMetaProps: Array<[string, string | undefined]> = [\n ['description', metadata.description],\n ['generator', metadata.generator],\n ['application-name', metadata.applicationName],\n ['referrer', metadata.referrer],\n ['category', metadata.category],\n ['creator', metadata.creator],\n ['publisher', metadata.publisher],\n ];\n\n for (const [name, content] of simpleMetaProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Keywords (array or string)\n if (metadata.keywords) {\n const content = Array.isArray(metadata.keywords)\n ? metadata.keywords.join(', ')\n : metadata.keywords;\n elements.push({ tag: 'meta', attrs: { name: 'keywords', content } });\n }\n\n // Robots\n if (metadata.robots) {\n const content =\n typeof metadata.robots === 'string' ? metadata.robots : renderRobotsObject(metadata.robots);\n elements.push({ tag: 'meta', attrs: { name: 'robots', content } });\n\n // googleBot as separate tag\n if (typeof metadata.robots === 'object' && metadata.robots.googleBot) {\n const gbContent =\n typeof metadata.robots.googleBot === 'string'\n ? metadata.robots.googleBot\n : renderRobotsObject(metadata.robots.googleBot);\n elements.push({ tag: 'meta', attrs: { name: 'googlebot', content: gbContent } });\n }\n }\n\n // Open Graph\n if (metadata.openGraph) {\n renderOpenGraph(metadata.openGraph, elements);\n }\n\n // Twitter\n if (metadata.twitter) {\n renderTwitter(metadata.twitter, elements);\n }\n\n // Icons\n if (metadata.icons) {\n renderIcons(metadata.icons, elements);\n }\n\n // Manifest\n if (metadata.manifest) {\n elements.push({ tag: 'link', attrs: { rel: 'manifest', href: metadata.manifest } });\n }\n\n // Alternates\n if (metadata.alternates) {\n renderAlternates(metadata.alternates, elements);\n }\n\n // Verification\n if (metadata.verification) {\n renderVerification(metadata.verification, elements);\n }\n\n // Format detection\n if (metadata.formatDetection) {\n const parts: string[] = [];\n if (metadata.formatDetection.telephone === false) parts.push('telephone=no');\n if (metadata.formatDetection.email === false) parts.push('email=no');\n if (metadata.formatDetection.address === false) parts.push('address=no');\n if (parts.length > 0) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'format-detection', content: parts.join(', ') },\n });\n }\n }\n\n // Authors\n if (metadata.authors) {\n const authorList = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];\n for (const author of authorList) {\n if (author.name) {\n elements.push({ tag: 'meta', attrs: { name: 'author', content: author.name } });\n }\n if (author.url) {\n elements.push({ tag: 'link', attrs: { rel: 'author', href: author.url } });\n }\n }\n }\n\n // Apple Web App\n if (metadata.appleWebApp) {\n renderAppleWebApp(metadata.appleWebApp, elements);\n }\n\n // App Links (al:*)\n if (metadata.appLinks) {\n renderAppLinks(metadata.appLinks, elements);\n }\n\n // iTunes\n if (metadata.itunes) {\n renderItunes(metadata.itunes, elements);\n }\n\n // Other (custom meta tags)\n if (metadata.other) {\n for (const [name, value] of Object.entries(metadata.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n return elements;\n}\n\n// ─── Rendering Helpers ───────────────────────────────────────────────────────\n\nfunction renderRobotsObject(robots: Record<string, unknown>): string {\n const parts: string[] = [];\n if (robots.index === true) parts.push('index');\n if (robots.index === false) parts.push('noindex');\n if (robots.follow === true) parts.push('follow');\n if (robots.follow === false) parts.push('nofollow');\n return parts.join(', ');\n}\n","/**\n * Metadata resolution for timber.js.\n *\n * Resolves metadata from a segment chain (layouts + page), applies title\n * templates, shallow-merges entries, and produces head element descriptors.\n *\n * Resolution happens inside the render pass — React.cache is active,\n * metadata is outside Suspense, and the flush point guarantees completeness.\n *\n * Rendering (Metadata → HeadElement[]) is in metadata-render.ts.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\n\n// Re-export renderMetadataToElements from the rendering module so existing\n// consumers (route-element-builder, tests) can keep importing from here.\nexport { renderMetadataToElements } from './metadata-render.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A single metadata entry from a layout or page module. */\nexport interface SegmentMetadataEntry {\n /** The resolved metadata object (from static or async `metadata` export). */\n metadata: Metadata;\n /** Whether this entry is from the page (leaf) module. */\n isPage: boolean;\n}\n\n/** Options for resolveMetadata. */\nexport interface ResolveMetadataOptions {\n /**\n * When true, the page's metadata is discarded (simulating a render error)\n * and `<meta name=\"robots\" content=\"noindex\">` is injected.\n */\n errorState?: boolean;\n}\n\n/** A rendered head element descriptor. */\nexport interface HeadElement {\n tag: 'title' | 'meta' | 'link';\n content?: string;\n attrs?: Record<string, string>;\n}\n\n// ─── Title Resolution ────────────────────────────────────────────────────────\n\n/**\n * Resolve a title value with an optional template.\n *\n * - string → apply template if present\n * - { absolute: '...' } → use as-is, skip template\n * - { default: '...' } → use as fallback (no template applied)\n * - undefined → undefined\n */\nexport function resolveTitle(\n title: Metadata['title'],\n template: string | undefined\n): string | undefined {\n if (title === undefined || title === null) {\n return undefined;\n }\n\n if (typeof title === 'string') {\n return template ? template.replace('%s', title) : title;\n }\n\n // Object form\n if (title.absolute !== undefined) {\n return title.absolute;\n }\n\n if (title.default !== undefined) {\n return title.default;\n }\n\n return undefined;\n}\n\n// ─── Metadata Resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve metadata from a segment chain.\n *\n * Processes entries from root layout to page (in segment order).\n * The merge algorithm:\n * 1. Shallow-merge all keys except title (later wins)\n * 2. Track the most recent title template\n * 3. Resolve the final title using the template\n *\n * In error state, the page entry is dropped and noindex is injected.\n *\n * See design/16-metadata.md §\"Merge Algorithm\"\n */\nexport function resolveMetadata(\n entries: SegmentMetadataEntry[],\n options: ResolveMetadataOptions = {}\n): Metadata {\n const { errorState = false } = options;\n\n const merged: Metadata = {};\n let titleTemplate: string | undefined;\n let lastDefault: string | undefined;\n let rawTitle: Metadata['title'];\n\n for (const { metadata, isPage } of entries) {\n // In error state, skip the page's metadata entirely\n if (errorState && isPage) {\n continue;\n }\n\n // Track title template\n if (metadata.title !== undefined && typeof metadata.title === 'object') {\n if (metadata.title.template !== undefined) {\n titleTemplate = metadata.title.template;\n }\n if (metadata.title.default !== undefined) {\n lastDefault = metadata.title.default;\n }\n }\n\n // Shallow-merge all keys except title\n for (const key of Object.keys(metadata) as Array<keyof Metadata>) {\n if (key === 'title') continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (merged as any)[key] = metadata[key];\n }\n\n // Track raw title (will be resolved after the loop)\n if (metadata.title !== undefined) {\n rawTitle = metadata.title;\n }\n }\n\n // In error state, we lost page title — use the most recent default\n if (errorState) {\n rawTitle = lastDefault !== undefined ? { default: lastDefault } : rawTitle;\n // Don't apply template in error state\n titleTemplate = undefined;\n }\n\n // Resolve the final title\n const resolvedTitle = resolveTitle(rawTitle, titleTemplate);\n if (resolvedTitle !== undefined) {\n merged.title = resolvedTitle;\n }\n\n // Error state: inject noindex, overriding any user robots\n if (errorState) {\n merged.robots = 'noindex';\n }\n\n return merged;\n}\n\n// ─── URL Resolution ──────────────────────────────────────────────────────────\n\n/**\n * Check if a string is an absolute URL.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//');\n}\n\n/**\n * Resolve a relative URL against a base URL.\n */\nfunction resolveUrl(url: string, base: URL): string {\n if (isAbsoluteUrl(url)) return url;\n return new URL(url, base).toString();\n}\n\n/**\n * Resolve relative URLs in metadata fields against metadataBase.\n *\n * Returns a new metadata object with URLs resolved. Absolute URLs are not modified.\n * If metadataBase is not set, returns the metadata unchanged.\n */\nexport function resolveMetadataUrls(metadata: Metadata): Metadata {\n const base = metadata.metadataBase;\n if (!base) return metadata;\n\n const result = { ...metadata };\n\n // Resolve openGraph images\n if (result.openGraph) {\n result.openGraph = { ...result.openGraph };\n if (typeof result.openGraph.images === 'string') {\n result.openGraph.images = resolveUrl(result.openGraph.images, base);\n } else if (Array.isArray(result.openGraph.images)) {\n result.openGraph.images = result.openGraph.images.map((img) => ({\n ...img,\n url: resolveUrl(img.url, base),\n }));\n } else if (result.openGraph.images) {\n // Single object: { url, width?, height?, alt? }\n result.openGraph.images = {\n ...result.openGraph.images,\n url: resolveUrl(result.openGraph.images.url, base),\n };\n }\n if (result.openGraph.url && !isAbsoluteUrl(result.openGraph.url)) {\n result.openGraph.url = resolveUrl(result.openGraph.url, base);\n }\n }\n\n // Resolve twitter images\n if (result.twitter) {\n result.twitter = { ...result.twitter };\n if (typeof result.twitter.images === 'string') {\n result.twitter.images = resolveUrl(result.twitter.images, base);\n } else if (Array.isArray(result.twitter.images)) {\n // Resolve each image URL, preserving the union type structure\n const resolved = result.twitter.images.map((img) =>\n typeof img === 'string' ? resolveUrl(img, base) : { ...img, url: resolveUrl(img.url, base) }\n );\n // If all entries are strings, assign as string[]; otherwise as object[]\n const allStrings = resolved.every((r) => typeof r === 'string');\n result.twitter.images = allStrings\n ? (resolved as string[])\n : (resolved as Array<{ url: string; alt?: string; width?: number; height?: number }>);\n } else if (result.twitter.images) {\n // Single object: { url, alt?, width?, height? }\n result.twitter.images = {\n ...result.twitter.images,\n url: resolveUrl(result.twitter.images.url, base),\n };\n }\n }\n\n // Resolve alternates\n if (result.alternates) {\n result.alternates = { ...result.alternates };\n if (result.alternates.canonical && !isAbsoluteUrl(result.alternates.canonical)) {\n result.alternates.canonical = resolveUrl(result.alternates.canonical, base);\n }\n if (result.alternates.languages) {\n const langs: Record<string, string> = {};\n for (const [lang, url] of Object.entries(result.alternates.languages)) {\n langs[lang] = isAbsoluteUrl(url) ? url : resolveUrl(url, base);\n }\n result.alternates.languages = langs;\n }\n }\n\n // Resolve icon URLs\n if (result.icons) {\n result.icons = { ...result.icons };\n if (typeof result.icons.icon === 'string') {\n result.icons.icon = resolveUrl(result.icons.icon, base);\n } else if (Array.isArray(result.icons.icon)) {\n result.icons.icon = result.icons.icon.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n if (typeof result.icons.apple === 'string') {\n result.icons.apple = resolveUrl(result.icons.apple, base);\n } else if (Array.isArray(result.icons.apple)) {\n result.icons.apple = result.icons.apple.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n }\n\n return result;\n}\n","'use client';\n\n/**\n * Reconstitutes a SerializableError into a real Error instance before\n * passing to the user's error component.\n *\n * TSX error pages are 'use client' components that receive { error: Error, digest, reset }.\n * Error objects are not RSC-serializable (React Flight throws \"Only plain objects\n * can be passed to Client Components\"). This wrapper receives the error as a plain\n * SerializableError object, reconstitutes a real Error instance, and passes it\n * to the user's error component — ensuring error instanceof Error works correctly.\n *\n * See design/spike-TIM-565-unify-error-pages.md §\"Edge Case B\"\n * See design/10-error-handling.md §\"RSC → SSR for Error Pages via SerializableError\"\n */\n\nimport { createElement, type ReactNode, type ComponentType } from 'react';\n\n/**\n * Plain-object representation of an Error that can cross the RSC → client boundary.\n * Stack is only included in dev mode (gated by isDevMode() on the server).\n */\nexport interface SerializableError {\n message: string;\n name: string;\n stack?: string;\n}\n\n/**\n * Props for the ErrorReconstituter wrapper component.\n * All props are RSC-serializable:\n * - error: plain object (SerializableError)\n * - digest: plain JSON or null\n * - reset: undefined (only meaningful on client after boundary catch)\n * - component: client module reference (RSC Flight serializes as opaque ref)\n * - status / dangerouslyPassData: set only when error.tsx serves a deny()\n * as the last entry in the 4xx fallback chain — forwarded so dual-shape\n * error.tsx implementations can branch on the deny status (TIM-1081)\n */\ninterface ErrorReconstituterProps {\n error: SerializableError;\n digest: { code: string; data: unknown } | null;\n reset: undefined;\n component: ComponentType<{\n error: Error;\n digest: { code: string; data: unknown } | null;\n reset: (() => void) | undefined;\n status?: number;\n dangerouslyPassData?: unknown;\n }>;\n status?: number;\n dangerouslyPassData?: unknown;\n}\n\n/**\n * Reconstitute a SerializableError into a real Error instance and render\n * the user's error component with the proper props.\n */\nexport function ErrorReconstituter({\n error: serialized,\n digest,\n reset,\n component,\n status,\n dangerouslyPassData,\n}: ErrorReconstituterProps): ReactNode {\n // Reconstitute a real Error so instanceof checks work in user code\n const error = Object.assign(new Error(serialized.message), {\n name: serialized.name,\n ...(serialized.stack != null ? { stack: serialized.stack } : {}),\n });\n\n return createElement(component, {\n error,\n digest,\n reset,\n ...(status != null ? { status, dangerouslyPassData } : {}),\n });\n}\n","/**\n * loadModule — enriched error context for route manifest .load() failures.\n *\n * Wraps the lazy `load()` functions from the route manifest with a\n * try/catch that re-throws with the file path and original cause.\n *\n * Callers that need fallthrough behavior (error renderers) can use\n * `.catch(() => null)` or try/catch — the decision stays at the call site.\n *\n * See design/spike-TIM-551-dynamic-import-audit.md §\"Proposed Wrapping Strategy\"\n */\n\n/** A manifest file reference with a lazy import function and file path. */\nexport interface ManifestLoader {\n load: () => Promise<unknown>;\n filePath: string;\n}\n\n/**\n * Custom error class for module load failures.\n *\n * Preserves the original error as `cause` while providing a\n * human-readable message with the file path.\n */\nexport class ModuleLoadError extends Error {\n /** The file path that failed to load. */\n readonly filePath: string;\n\n constructor(filePath: string, cause: unknown) {\n const originalMessage = cause instanceof Error ? cause.message : String(cause);\n super(`[timber] Failed to load module ${filePath}\\n ${originalMessage}`, { cause });\n this.name = 'ModuleLoadError';\n this.filePath = filePath;\n }\n}\n\n/**\n * Load a route manifest module with enriched error context.\n *\n * On success: returns the module object (same as `loader.load()`).\n * On failure: throws `ModuleLoadError` with file path and original cause.\n *\n * For error rendering paths that need fallthrough instead of throwing,\n * callers should catch at the call site:\n *\n * ```ts\n * // Throwing (default) — route-element-builder, api-handler, etc.\n * const mod = await loadModule(segment.page);\n *\n * // Fallthrough — error-renderer, error-boundary-wrapper\n * const mod = await loadModule(segment.error).catch(() => null);\n * ```\n */\nexport async function loadModule<T = Record<string, unknown>>(loader: ManifestLoader): Promise<T> {\n try {\n return (await loader.load()) as T;\n } catch (error) {\n throw new ModuleLoadError(loader.filePath, error);\n }\n}\n","/**\n * Status-code file resolver for timber.js error/denial rendering.\n *\n * Given an HTTP status code and a matched segment chain, resolves the\n * correct file to render by walking the fallback chain described in\n * design/10-error-handling.md §\"Status-Code Files\".\n *\n * **Generic over `TFile`** (TIM-848). Walks `SegmentNode<TFile>` trees\n * regardless of whether `TFile` is the build-time `RouteFile` or the\n * runtime `ManifestFile`. Before TIM-848 there were two near-identical\n * resolvers — one for the Map-based scanner output and one for the\n * object-based runtime manifest. Now there is one.\n *\n * Supports two format families:\n * - 'component' (default): .tsx/.jsx/.mdx status files → React rendering pipeline\n * - 'json': .json status files → raw JSON response, no React\n *\n * Fallback chains operate within the same format family (no cross-format fallback).\n *\n * **Component chain (4xx):**\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n * Pass 4 — framework default (returns null)\n *\n * **JSON chain (4xx and 5xx):**\n * Pass 1 — json status files (leaf → root): {status}.json → {category}.json\n * Pass 2 — framework default JSON (returns null, caller provides bare JSON)\n *\n * **5xx component:**\n * Per-segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n * Then framework default (returns null)\n */\n\nimport type { SegmentNode } from '../routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** How the status-code file was matched. */\nexport type StatusFileKind =\n | 'exact' // e.g. 403.tsx matched status 403\n | 'category' // e.g. 4xx.tsx matched status 403\n | 'legacy' // e.g. not-found.tsx matched status 404\n | 'error'; // error.tsx as last resort\n\n/** Response format family for status-code resolution. */\nexport type StatusFileFormat = 'component' | 'json';\n\n/** Result of resolving a status-code file for a segment chain. */\nexport interface StatusFileResolution<TFile> {\n /** The matched route file. */\n file: TFile;\n /** The HTTP status code (always the original status, not the file's code). */\n status: number;\n /** How the file was matched. */\n kind: StatusFileKind;\n /** Index into the segments array where the file was found. */\n segmentIndex: number;\n}\n\n/** How a slot denial file was matched. */\nexport type SlotDeniedKind = 'denied' | 'default';\n\n/** Result of resolving a slot denied file. */\nexport interface SlotDeniedResolution<TFile> {\n /** The matched route file (denied.tsx or default.tsx). */\n file: TFile;\n /** Slot name without @ prefix. */\n slotName: string;\n /** How the file was matched. */\n kind: SlotDeniedKind;\n}\n\n// ─── Legacy Compat Mapping ───────────────────────────────────────────────────\n\n/**\n * Maps legacy file convention names to their corresponding HTTP status codes.\n * Only used in the 4xx component fallback chain. Exported so the in-tree\n * deny chain (deny-boundary.ts) uses the same mapping — see TIM-1081.\n */\nexport const LEGACY_FILE_TO_STATUS: Record<string, number> = {\n 'not-found': 404,\n 'forbidden': 403,\n 'unauthorized': 401,\n};\n\n/** Reverse index: status code → legacy file name. Built once at module load. */\nconst STATUS_TO_LEGACY_FILE: Record<number, string> = Object.fromEntries(\n Object.entries(LEGACY_FILE_TO_STATUS).map(([name, status]) => [status, name])\n);\n\n// ─── Lookup Helpers ──────────────────────────────────────────────────────\n\n/**\n * Look up `{statusStr}` then `{categoryKey}` (e.g. \"4xx\" / \"5xx\") in a\n * status-file group on a single segment. Shared by all three fallback\n * chains — the only structural difference between component 4xx,\n * component 5xx, and JSON resolution is *which* group is searched and\n * how the per-segment loop is layered around it.\n */\nfunction lookupInGroup<TFile>(\n group: Record<string, TFile> | undefined,\n statusStr: string,\n categoryKey: string,\n segmentIndex: number,\n status: number\n): StatusFileResolution<TFile> | null {\n if (!group) return null;\n const exact = group[statusStr];\n if (exact) return { file: exact, status, kind: 'exact', segmentIndex };\n const category = group[categoryKey];\n if (category) return { file: category, status, kind: 'category', segmentIndex };\n return null;\n}\n\n/**\n * Look up the legacy convention file (`not-found.tsx` / `forbidden.tsx` /\n * `unauthorized.tsx`) for `status` on a single segment. Returns null if\n * `status` has no legacy mapping or the file isn't present.\n */\nfunction lookupLegacy<TFile>(\n group: Record<string, TFile> | undefined,\n status: number,\n segmentIndex: number\n): StatusFileResolution<TFile> | null {\n if (!group) return null;\n const name = STATUS_TO_LEGACY_FILE[status];\n if (!name) return null;\n const file = group[name];\n return file ? { file, status, kind: 'legacy', segmentIndex } : null;\n}\n\n// ─── Resolver ────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the status-code file to render for a given HTTP status code.\n *\n * Walks the segment chain from leaf to root following the fallback chain\n * defined in design/10-error-handling.md. Returns null if no file is found\n * (caller should render the framework default).\n *\n * @param status - The HTTP status code (4xx or 5xx).\n * @param segments - The matched segment chain from root (index 0) to leaf (last).\n * @param format - The response format family ('component' or 'json'). Defaults to 'component'.\n */\nexport function resolveStatusFile<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>,\n format: StatusFileFormat = 'component'\n): StatusFileResolution<TFile> | null {\n if (status < 400 || status > 599) return null;\n if (format === 'json') return resolveJson(status, segments);\n if (status <= 499) return resolve4xx(status, segments);\n return resolve5xx(status, segments);\n}\n\n/**\n * 4xx component fallback chain — three separate full passes leaf→root.\n *\n * The passes must be separate (not interleaved per-segment) so that a\n * root-level `404.tsx` beats a leaf-level `error.tsx`. The 5xx chain\n * inverts this and is per-segment: a leaf's `error.tsx` beats a root's\n * `5xx.tsx`. This asymmetry is the only reason these two functions exist\n * separately.\n *\n * Pass 1 — {status}.tsx → 4xx.tsx (statusFiles)\n * Pass 2 — not-found / forbidden / unauthorized (legacyStatusFiles)\n * Pass 3 — error.tsx (error)\n */\nfunction resolve4xx<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupInGroup(segments[i].statusFiles, statusStr, '4xx', i, status);\n if (r) return r;\n }\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupLegacy(segments[i].legacyStatusFiles, status, i);\n if (r) return r;\n }\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const errorFile = segments[i].error;\n if (errorFile) {\n return { file: errorFile, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx component fallback chain — single pass, per-segment leaf→root.\n *\n * At each segment: {status}.tsx → 5xx.tsx → error.tsx. A leaf's\n * `error.tsx` therefore beats a root's `5xx.tsx`, which is the\n * intentional inverse of the 4xx chain.\n */\nfunction resolve5xx<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n const r = lookupInGroup(segment.statusFiles, statusStr, '5xx', i, status);\n if (r) return r;\n if (segment.error) {\n return { file: segment.error, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * JSON fallback chain (for both 4xx and 5xx) — single pass leaf→root.\n *\n * At each segment: {status}.json → {category}.json. No legacy compat,\n * no error.tsx — the JSON chain terminates at the category catch-all\n * and the caller falls back to a bare-JSON framework default.\n */\nfunction resolveJson<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n const categoryKey = status >= 500 ? '5xx' : '4xx';\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupInGroup(segments[i].jsonStatusFiles, statusStr, categoryKey, i, status);\n if (r) return r;\n }\n\n return null;\n}\n\n// ─── Slot Denied Resolver ────────────────────────────────────────────────────\n\n/**\n * Resolve the denial file for a parallel route slot.\n *\n * Slot denial is graceful degradation — no HTTP status on the wire.\n * Fallback chain: denied.tsx → default.tsx → null.\n *\n * @param slotNode - The segment node for the slot (segmentType === 'slot').\n */\nexport function resolveSlotDenied<TFile>(\n slotNode: SegmentNode<TFile>\n): SlotDeniedResolution<TFile> | null {\n const slotName = slotNode.segmentName.replace(/^@/, '');\n\n if (slotNode.denied) {\n return { file: slotNode.denied, slotName, kind: 'denied' };\n }\n\n if (slotNode.default) {\n return { file: slotNode.default, slotName, kind: 'default' };\n }\n\n return null;\n}\n","/**\n * Deny boundary subsystem — the in-tree DenySignal flow.\n *\n * Three things live together here because they form a single flow:\n *\n * 1. **Chain construction** (`buildDenyPageChain`) — walks the matched\n * segment chain at element-tree build time and produces a list of\n * `DenyPageEntry` records ordered by specificity (specific status →\n * category catch-all → `error.tsx`).\n *\n * 2. **Runtime matching** (`renderMatchingDenyPage`) — picks the first\n * chain entry whose status filter matches the thrown DenySignal and\n * returns a React element for the matching component. Used by\n * `AccessGate` and `PageDenyBoundary` when they catch a deny.\n *\n * 3. **The page boundary itself** (`PageDenyBoundary`) — the async server\n * component that wraps a server-component page, calls it, and catches\n * `DenySignal` so the deny page renders in-tree (no throw reaches\n * React Flight, single render pass).\n *\n * Plus the ALS helpers (`setDenyStatus` / `getDenyStatus`) the boundary\n * uses to thread the matched status code back to the pipeline so the\n * HTTP status reflects the deny.\n *\n * Folded into one module from the former `deny-page-resolver.ts` and\n * `page-deny-boundary.tsx` (TIM-853) — the names were misleading and the\n * three pieces only made sense together.\n *\n * See design/04-authorization.md, design/10-error-handling.md, TIM-666.\n */\n\nimport { createElement } from 'react';\n\nimport { ErrorReconstituter } from '../client/error-reconstituter.js';\nimport type { SerializableError } from '../client/error-reconstituter.js';\nimport { requestContextAls } from './als-registry.js';\nimport { DenySignal } from './primitives.js';\nimport { loadModule } from './safe-load.js';\nimport { LEGACY_FILE_TO_STATUS } from './status-code-resolver.js';\nimport { withSpan } from './tracing.js';\nimport { isMdxFilePath } from './utils/mdx-file.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/** A single entry in the deny page fallback chain. */\nexport interface DenyPageEntry {\n /** Status code filter: specific (403), category (400 = any 4xx), or null (catch-all). */\n status: number | null;\n /** The component to render (server or client — both work). */\n component: (...args: unknown[]) => unknown;\n /**\n * How the entry matched: a status-code file (404.tsx/4xx.tsx), a legacy\n * compat file (not-found.tsx/forbidden.tsx/unauthorized.tsx), or the\n * error.tsx catch-all. error.tsx entries render with their documented\n * { error, digest, reset } contract, not bare deny props. See TIM-1081.\n */\n kind: 'status' | 'legacy' | 'error';\n /** MDX files are server components — rendered with plain props, never ErrorReconstituter. */\n isMdx: boolean;\n}\n\n// ─── Chain Construction ──────────────────────────────────────────────────\n\n/**\n * Build the deny page fallback chain from the segment chain.\n *\n * Walks segments from `startIndex` outward (toward root) and collects\n * status-code file components in fallback order:\n * 1. Specific status files (403.tsx, 404.tsx) — exact match\n * 2. Category catch-alls (4xx.tsx) — matches any 4xx\n * 3. Legacy compat files (not-found.tsx → 404, forbidden.tsx → 403,\n * unauthorized.tsx → 401) — exact match\n * 4. error.tsx — catches everything\n *\n * Each segment is checked in this order. The chain is ordered so the\n * FIRST match wins at catch time. This mirrors resolveStatusFile's 4xx\n * component chain (status-code-resolver.ts) — the two walks must stay in\n * sync or in-tree and re-render deny paths resolve different files.\n */\nexport async function buildDenyPageChain(\n segments: ManifestSegmentNode[],\n startIndex: number\n): Promise<DenyPageEntry[]> {\n const chain: DenyPageEntry[] = [];\n\n // Pass 1: Status files (specific + category) across ALL segments.\n // These have higher priority than error.tsx — a root 4xx.tsx should\n // match before a leaf error.tsx. Walking inner → outer ensures the\n // nearest match wins within each priority tier.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.statusFiles) continue;\n\n // Specific status files (403.tsx, 404.tsx, etc.)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n chain.push({\n status,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'status',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n }\n\n // Category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n const categoryStatus = key === '4xx' ? 400 : 500;\n chain.push({\n status: categoryStatus,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'status',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n }\n\n // Pass 2: legacy compat files (not-found.tsx → 404, forbidden.tsx → 403,\n // unauthorized.tsx → 401). Lower priority than status files (a root\n // 4xx.tsx beats a leaf not-found.tsx) but higher than error.tsx — the\n // same ordering as resolveStatusFile's resolve4xx. See TIM-1081.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.legacyStatusFiles) continue;\n for (const [name, status] of Object.entries(LEGACY_FILE_TO_STATUS)) {\n const file = segment.legacyStatusFiles[name];\n if (!file) continue;\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n chain.push({\n status,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'legacy',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n\n // Pass 3: error.tsx files — lowest priority catch-all.\n // Only added AFTER all status and legacy files so they never shadow a\n // more specific file from an ancestor segment.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (segment.error) {\n const mod = await loadModule(segment.error).catch(() => null);\n if (mod?.default) {\n chain.push({\n status: null,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'error',\n isMdx: isMdxFilePath(segment.error.filePath),\n });\n }\n }\n }\n\n return chain;\n}\n\n// ─── Runtime Matcher ──────────────────────────────────────────────────────\n\n/**\n * Find the first deny page in the chain that matches the given status code.\n * Returns a React element for the matching component, or null if no match.\n */\nexport function renderMatchingDenyPage(\n chain: DenyPageEntry[],\n status: number,\n data: unknown\n): React.ReactElement | null {\n for (const entry of chain) {\n if (entry.status === status) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === 400 && status >= 400 && status <= 499) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === 500 && status >= 500 && status <= 599) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === null) {\n return renderDenyEntry(entry, status, data);\n }\n }\n return null;\n}\n\n/**\n * Build the element for a matched deny chain entry with the props contract\n * the file's convention documents:\n *\n * - Status-code and legacy files: { status, dangerouslyPassData }\n * - error.tsx (TSX): { error, digest, reset } via ErrorReconstituter, plus\n * { status, dangerouslyPassData } for dual-shape implementations. Without\n * the Error prop, any error.tsx written per the docs (reading\n * error.message) crashes — turning a clean deny(404) into a 500. TIM-1081.\n * - error.mdx: plain { status } — server component, static content.\n */\nexport function renderDenyEntry(\n entry: DenyPageEntry,\n status: number,\n data: unknown\n): React.ReactElement {\n const h = createElement as (...args: unknown[]) => React.ReactElement;\n\n if (entry.kind !== 'error') {\n return h(entry.component, { status, dangerouslyPassData: data });\n }\n\n // MDX error pages receive plain props — no Error serialization possible.\n if (entry.isMdx) {\n return h(entry.component, { status });\n }\n\n // The message is derived solely from the status code (already on the\n // wire) — safe to cross the RSC→client boundary in production. No stack,\n // no user data. See design/13-security.md §\"Errors don't leak\".\n const serializableDeny: SerializableError = {\n message: `Access denied with status ${status}`,\n name: 'DenySignal',\n };\n return h(ErrorReconstituter, {\n error: serializableDeny,\n digest: null,\n reset: undefined,\n component: entry.component,\n status,\n dangerouslyPassData: data,\n });\n}\n\n// ─── Page Boundary ────────────────────────────────────────────────────────\n\n/**\n * Async server component that wraps a page call with DenySignal catching.\n *\n * Calls the page component as an async function (the same thing React\n * Flight does internally), awaits it, and catches DenySignal. On catch,\n * renders the matching deny page in-tree. On success, returns the page's\n * rendered output normally.\n *\n * Client component pages ('use client') are NOT wrapped — they can't call\n * deny() (server-only API) and must go through createElement normally.\n *\n * No error reaches React Flight — the Flight stream is clean, SSR succeeds,\n * and the entire request uses a single renderToReadableStream call.\n */\nexport async function PageDenyBoundary({\n Page,\n route,\n denyPages,\n}: {\n /** The page server component function. */\n Page: (...args: unknown[]) => unknown;\n /** Route path for OTEL tracing. */\n route: string;\n /** Deny page fallback chain from the segment chain. */\n denyPages: DenyPageEntry[];\n}): Promise<React.ReactElement> {\n try {\n // Call the page as an async function — same as React Flight does.\n // Wrap in OTEL span for tracing (replaces the TracedPage wrapper).\n const result = await withSpan('timber.page', { 'timber.route': route }, () => Page({}));\n return result as React.ReactElement;\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n // Non-deny errors (RedirectSignal, runtime errors) propagate normally.\n throw error;\n }\n}\n\n// ─── ALS Helpers ──────────────────────────────────────────────────────────\n\n/**\n * Set the deny status in the request context ALS.\n * Called from AccessGate / PageDenyBoundary when a DenySignal is caught.\n * The pipeline reads this after render to set the HTTP status code.\n */\nexport function setDenyStatus(status: number): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.denyStatus = status;\n }\n}\n\n/**\n * Read the deny status from the request context ALS.\n * Returns undefined if no deny was caught during render.\n */\nexport function getDenyStatus(): number | undefined {\n return requestContextAls.getStore()?.denyStatus;\n}\n","/**\n * AccessGate and SlotAccessGate — framework-injected async server components.\n *\n * AccessGate wraps each segment's layout in the element tree. It calls the\n * segment's access.ts before the layout renders. If access.ts calls deny()\n * or redirect(), the signal propagates as a render-phase throw — caught by\n * the flush controller to produce the correct HTTP status code.\n *\n * SlotAccessGate wraps parallel slot content. On denial, it renders the\n * graceful degradation chain: denied.tsx → default.tsx → null. Slot denial\n * does not affect the HTTP status code.\n *\n * See design/04-authorization.md and design/02-rendering-pipeline.md §\"AccessGate\"\n */\n\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport type { AccessGateProps, SlotAccessGateProps } from './tree-builder.js';\nimport { withSpan, setSpanAttribute } from './tracing.js';\nimport { isDebug } from './debug.js';\nimport type { DenyPageEntry } from './deny-boundary.js';\nimport { renderMatchingDenyPage, setDenyStatus } from './deny-boundary.js';\nimport type { ReactNode } from 'react';\n\n// ─── AccessGate ─────────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for segments.\n *\n * When a pre-computed `verdict` prop is provided (from the pre-render pass\n * in route-element-builder.ts), AccessGate replays it synchronously — no\n * async, no re-execution of access.ts, immune to Suspense timing. The OTEL\n * span was already emitted during the pre-render pass.\n *\n * When no verdict is provided (backward compat with tree-builder.ts),\n * AccessGate calls accessFn directly with OTEL instrumentation.\n *\n * access.ts is a pure gate — return values are discarded. The layout below\n * gets the same data by calling the same cached functions (React.cache dedup).\n */\nexport function AccessGate(props: AccessGateProps): ReactNode | Promise<ReactNode> {\n const { accessFn, segmentName, verdict, denyPages, children } = props;\n\n // Fast path: replay pre-computed verdict from the pre-render pass.\n if (verdict !== undefined) {\n if (verdict === 'pass') {\n return children;\n }\n // Render deny page in-tree when possible (same as the fallback path).\n if (verdict instanceof DenySignal && denyPages) {\n const denyElement = renderMatchingDenyPage(denyPages, verdict.status, verdict.data);\n if (denyElement) {\n setDenyStatus(verdict.status);\n return denyElement;\n }\n }\n throw verdict;\n }\n\n // Primary path: call accessFn directly during render.\n // If denyPages is provided, catch DenySignal and render the deny page\n // in-tree — no throw reaches React Flight, no second render pass.\n return accessGateFallback(accessFn, segmentName, denyPages, children);\n}\n\n/**\n * Async fallback for AccessGate when no pre-computed verdict is available.\n * Calls accessFn with OTEL instrumentation.\n */\nasync function accessGateFallback(\n accessFn: AccessGateProps['accessFn'],\n segmentName: AccessGateProps['segmentName'],\n denyPages: DenyPageEntry[] | undefined,\n children: ReactNode\n): Promise<ReactNode> {\n try {\n await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {\n try {\n await accessFn();\n await setSpanAttribute('timber.result', 'pass');\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n await setSpanAttribute('timber.result', 'deny');\n await setSpanAttribute('timber.deny_status', error.status);\n if (error.sourceFile) {\n await setSpanAttribute('timber.deny_file', error.sourceFile);\n }\n } else if (error instanceof RedirectSignal) {\n await setSpanAttribute('timber.result', 'redirect');\n }\n throw error;\n }\n });\n } catch (error: unknown) {\n // Catch DenySignal and render the deny page in-tree.\n // No throw reaches React Flight — clean stream, single render pass.\n // RedirectSignal and other errors propagate normally.\n if (error instanceof DenySignal && denyPages) {\n const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n throw error;\n }\n\n return children;\n}\n\n// ─── SlotAccessGate ─────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for parallel slots.\n *\n * On denial, graceful degradation: denied.tsx → default.tsx → null.\n * The HTTP status code is unaffected — slot denial is a UI concern, not\n * a protocol concern. The parent layout and sibling slots still render.\n *\n * DeniedComponent is passed instead of a pre-built element so that\n * DenySignal.data can be forwarded as the dangerouslyPassData prop\n * and the slot name can be passed as the slot prop. See TIM-488.\n *\n * redirect() in slot access.ts is a dev-mode error — redirecting from a\n * slot doesn't make architectural sense.\n */\nexport async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactNode> {\n const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;\n\n try {\n await accessFn();\n } catch (error: unknown) {\n // DenySignal → graceful degradation (denied.tsx → default.tsx → null)\n // Build the denied element dynamically so DenySignal.data is forwarded.\n if (error instanceof DenySignal) {\n return (\n buildDeniedFallback(DeniedComponent, slotName, error.data, createElement) ??\n defaultFallback ??\n null\n );\n }\n\n // RedirectSignal in slot access → dev-mode error.\n // Slot access should use deny(), not redirect(). Redirecting from a\n // slot would redirect the entire page, which breaks the contract that\n // slot failure is graceful degradation.\n if (error instanceof RedirectSignal) {\n if (isDebug()) {\n console.error(\n '[timber] redirect() is not allowed in slot access.ts. ' +\n 'Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. ' +\n \"If you need to redirect, move the logic to the parent segment's access.ts.\"\n );\n }\n // In production, treat as a deny — render fallback rather than crash.\n return (\n buildDeniedFallback(DeniedComponent, slotName, undefined, createElement) ??\n defaultFallback ??\n null\n );\n }\n\n // Unhandled error — re-throw so error boundaries can catch it.\n // Dev-mode warning: slot access should use deny(), not throw.\n if (isDebug()) {\n console.warn(\n '[timber] Unhandled error in slot access.ts. ' +\n 'Use deny() for access control, not unhandled throws.',\n error\n );\n }\n throw error;\n }\n\n // Access passed — render slot content.\n return children;\n}\n\n/**\n * Build the denied fallback element dynamically with DenySignal data.\n * Returns null if no DeniedComponent is available.\n */\nfunction buildDeniedFallback(\n DeniedComponent: SlotAccessGateProps['DeniedComponent'],\n slotName: string,\n data: unknown,\n createElement: SlotAccessGateProps['createElement']\n): ReactNode | null {\n if (!DeniedComponent) return null;\n return createElement(DeniedComponent, {\n slot: slotName,\n dangerouslyPassData: data,\n });\n}\n","/**\n * Segment param coercion — runs the matched route's `params.ts` codecs\n * over the raw matcher output before middleware and rendering.\n *\n * Lifted out of `pipeline-phases.ts` (TIM-853) so the coercer can be\n * imported directly by other entry points (the action-dispatch wrapper,\n * the revalidation renderer in `rsc-entry/index.ts`) without pulling\n * the entire pipeline phase module along with it.\n *\n * The function throws `ParamCoercionError` from `route-element-builder.ts`\n * on any codec failure; the pipeline catches that and dispatches to the\n * 404 page. See design/07-routing.md §\"Where Coercion Runs\".\n */\n\nimport type { Codec } from '../codec.js';\nimport { toBracketKey } from '../params/resolve-schema.js';\nimport type { RouteMatch } from './pipeline.js';\nimport { sanitizeParamValue } from './pipeline-helpers.js';\nimport { loadModule } from './safe-load.js';\nimport { ParamCoercionError } from './route-element-builder.js';\nimport { isDebug } from './debug.js';\n\n// ---------------------------------------------------------------------------\n// Module-level global codec store (TIM-931)\n// ---------------------------------------------------------------------------\n\n/** Global schema codecs, set once at startup from virtual:timber-schema. */\nlet _globalCodecs: Record<string, Codec<unknown>> | null = null;\n\n/**\n * Register the global schema codecs. Called once from the RSC entry\n * at server startup when app/schema.ts provides segment param codecs.\n * @internal\n */\nexport function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | null): void {\n _globalCodecs = codecs;\n}\n\n/**\n * Coerce raw slot params through global schema codecs.\n *\n * Each slot segment that has a paramName is looked up in the global codec\n * map by its bracket key (e.g. '[...year]' for catch-all). If a codec\n * exists, the raw value is parsed through it. If no codec exists or no\n * global codecs are registered, the raw value passes through unchanged.\n *\n * Unlike main route coercion, this does NOT throw ParamCoercionError on\n * failure — slots degrade gracefully. A failed coercion logs a warning\n * in dev mode and keeps the raw value.\n *\n * @internal — framework use only\n */\nexport function coerceSlotParams(\n slotChain: Array<{ segmentType: string; paramName?: string }>,\n rawParams: Record<string, string | string[]>\n): Record<string, string | string[]> {\n const globalCodecs = _globalCodecs;\n if (!globalCodecs) return rawParams;\n\n const result: Record<string, string | string[]> = Object.create(null);\n for (const key of Object.keys(rawParams)) {\n result[key] = rawParams[key];\n }\n\n for (const segment of slotChain) {\n if (!segment.paramName) continue;\n const bracketKey = toBracketKey(segment.segmentType, segment.paramName);\n const codec = globalCodecs[bracketKey];\n if (!codec) continue;\n\n const key = segment.paramName;\n if (!(key in result)) continue;\n\n try {\n result[key] = sanitizeParamValue(codec.parse(result[key] as string | string[])) as\n | string\n | string[];\n } catch (err) {\n // Slot param coercion failures are non-fatal — keep the raw value.\n // The main route already validated the URL; the slot just interprets\n // the same parts differently.\n if (isDebug()) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\n `[timber] Slot param coercion failed for \"${key}\" (codec: ${bracketKey})\\n` +\n ` Error: ${message}\\n` +\n ` Raw value: ${JSON.stringify(result[key])}\\n` +\n ` Keeping raw value.`\n );\n }\n }\n }\n\n return result as Record<string, string | string[]>;\n}\n\n/**\n * Run segment param coercion on the matched route's segments.\n *\n * When `globalCodecs` is provided (from app/schema.ts), uses the global\n * codec map keyed by bare param name. Otherwise falls back to loading\n * per-segment params.ts modules.\n *\n * Throws ParamCoercionError if any codec fails (→ 404).\n *\n * This runs BEFORE middleware, so ctx.segmentParams is already typed.\n * See design/07-routing.md §\"Where Coercion Runs\"\n * See design/41-global-params.md §\"Pipeline Integration\"\n */\nexport async function coerceSegmentParams(match: RouteMatch): Promise<void> {\n const globalCodecs = _globalCodecs;\n // Unconditionally install a null-prototype target so the invariant\n // \"match.segmentParams is null-prototype\" holds from the first line,\n // regardless of whether any segment has a codec.\n const mergeTarget: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(match.segmentParams)) {\n if (key !== '__proto__') {\n mergeTarget[key] = match.segmentParams[key as keyof typeof match.segmentParams];\n }\n }\n match.segmentParams = mergeTarget as RouteMatch['segmentParams'];\n\n // TIM-931/TIM-936: When a global schema is available, use it for coercion\n // instead of per-segment params.ts files. The global codec map is keyed\n // by bracket name (e.g. '[id]', '[...slug]') to avoid collisions between\n // distinct segment types that share a param name.\n if (globalCodecs) {\n for (const segment of match.segments) {\n if (!segment.paramName) continue;\n const bracketKey = toBracketKey(segment.segmentType, segment.paramName);\n const codec = globalCodecs[bracketKey];\n if (!codec) continue; // no codec in schema → keep raw string\n\n const key = segment.paramName;\n try {\n mergeTarget[key] = sanitizeParamValue(codec.parse(mergeTarget[key] as string | string[]));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isDebug()) {\n console.warn(\n `[timber] Global schema codec rejected value for param \"${key}\"\\n` +\n ` Error: ${message}\\n` +\n ` Raw value: ${JSON.stringify(mergeTarget[key])}\\n` +\n ` Hint: check the codec in app/schema.ts for key '${bracketKey}'.`\n );\n }\n throw new ParamCoercionError(message);\n }\n }\n return;\n }\n\n // Legacy path: per-segment params.ts coercion\n for (const segment of match.segments) {\n // Only process segments that have a params.ts convention file\n if (!segment.params) continue;\n\n let mod: Record<string, unknown>;\n try {\n mod = await loadModule(segment.params);\n } catch (err) {\n const message = `Failed to load params module for segment \"${segment.segmentName}\": ${err instanceof Error ? err.message : String(err)}`;\n if (isDebug()) {\n console.warn(\n `[timber] Param coercion error: ${message}\\n` +\n ` Segment: ${segment.segmentName}\\n` +\n ` Params file: ${segment.params}`\n );\n }\n throw new ParamCoercionError(message);\n }\n\n const segmentParamsDef = mod.segmentParams as\n | { parse(raw: Record<string, string | string[]>): Record<string, unknown> }\n | undefined;\n\n if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;\n\n try {\n const coerced = segmentParamsDef.parse(match.segmentParams);\n\n // Deep-sanitize codec output: every nested plain object becomes\n // null-prototype with dangerous keys stripped at every depth.\n // See TIM-873, design/13-security.md\n for (const key of Object.keys(coerced as Record<string, unknown>)) {\n if (key !== '__proto__') {\n mergeTarget[key] = sanitizeParamValue((coerced as Record<string, unknown>)[key]);\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isDebug()) {\n const rawKeys = Object.keys(match.segmentParams).join(', ');\n console.warn(\n `[timber] Param codec rejected values for segment \"${segment.segmentName}\"\\n` +\n ` Error: ${message}\\n` +\n ` Available raw params: { ${rawKeys} }\\n` +\n ` Params file: ${segment.params}\\n` +\n ` Hint: this usually means a codec threw for the raw URL value.\\n` +\n ` Check that the regex/schema accepts the actual path segment string.`\n );\n }\n throw new ParamCoercionError(message);\n }\n }\n}\n","/**\n * SegmentUpdateContext — React context for partial navigation updates.\n *\n * During partial navigation (server skips unchanged sync layouts), the\n * router builds a Map of segment path → ReactNode updates and passes it\n * as the context value. Mounted SegmentOutlet components read from this\n * context to decide whether to render the update or their cached content.\n *\n * SINGLETON GUARANTEE: Uses globalThis + Symbol.for — same pattern as\n * NavigationContext. The RSC client bundler can duplicate this module\n * across chunks (browser-entry graph + client-reference graph). With\n * ESM output, each chunk gets its own module scope — a bare createContext\n * at module level would create separate instances per chunk. globalThis\n * guarantees a single instance regardless of duplication.\n *\n * See design/19-client-navigation.md §\"Singleton Guarantee via globalThis\"\n */\n\n'use client';\n\nimport React, { type ReactNode } from 'react';\n\nexport const EMPTY_SEGMENT_UPDATES = new Map<string, ReactNode>();\n\nconst CTX_KEY = Symbol.for('__timber_segment_update_ctx');\n\nfunction getOrCreateContext(): React.Context<Map<string, ReactNode>> {\n const existing = (globalThis as Record<symbol, unknown>)[CTX_KEY] as\n | React.Context<Map<string, ReactNode>>\n | undefined;\n if (existing !== undefined) return existing;\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<Map<string, ReactNode>>(EMPTY_SEGMENT_UPDATES);\n (globalThis as Record<symbol, unknown>)[CTX_KEY] = ctx;\n return ctx;\n }\n // RSC environment — createContext not available. Return a dummy that\n // won't be used (outlets only render on the client).\n return undefined as unknown as React.Context<Map<string, ReactNode>>;\n}\n\nexport const SegmentUpdateContext = getOrCreateContext();\n","/**\n * SegmentOutlet — client component boundary at each layout segment.\n *\n * Each layout in the segment tree is wrapped with a SegmentOutlet that:\n * 1. Knows its segment path (prop from the server)\n * 2. Reads from SegmentUpdateContext for partial navigation updates\n * 3. Caches its rendered content in a ref across navigations\n *\n * On full navigation: receives new children via props, caches and renders them.\n * On partial navigation (this segment skipped): the context map has no entry\n * for this path, so the outlet returns cached content — preserving layout state.\n * On partial navigation (this segment updated): the context map has content\n * for this path, so the outlet renders the update.\n *\n * Uses React context instead of useSyncExternalStore to stay compatible\n * with concurrent rendering (transitions). All outlets re-render when the\n * context value changes, but each bails out quickly if its segment has\n * no update — same approach as Next.js LayoutRouter.\n *\n * Security: performance optimization only. The server always runs all\n * access.ts files regardless of segment skipping.\n * See design/13-security.md §\"State tree manipulation\".\n */\n\n'use client';\n\nimport { useContext, useRef, type ReactNode } from 'react';\nimport { SegmentUpdateContext } from './segment-update-context.js';\n\nexport interface SegmentOutletProps {\n /**\n * Unique identifier for this segment. For normal segments this is the\n * urlPath (e.g., \"/\", \"/dashboard\"). For route groups this includes the\n * group name (e.g., \"/(marketing)\") to distinguish siblings that share\n * the same urlPath. Must match the segmentId used in state-tree-diff.ts.\n */\n segmentPath: string;\n\n /** The segment's React subtree (layout + inner content). */\n children: ReactNode;\n}\n\n/**\n * Client component boundary at each layout segment in the element tree.\n *\n * On full navigation (context map is empty): renders children, caches in ref.\n * On partial navigation: checks the context map for an update at segmentPath.\n * - Update found → renders update, caches in ref.\n * - No update → returns cached content (layout state preserved).\n *\n * React preserves component instances across reactRoot.render() calls\n * when the same type appears at the same tree position. The ref persists\n * across navigations because SegmentOutlet is reconciled, not remounted.\n */\nexport function SegmentOutlet({ segmentPath, children }: SegmentOutletProps) {\n const updates = useContext(SegmentUpdateContext);\n const contentRef = useRef<ReactNode>(null);\n\n const update = updates.get(segmentPath);\n\n if (update !== undefined) {\n contentRef.current = update;\n return update;\n }\n\n if (contentRef.current === null) {\n contentRef.current = children;\n return children;\n }\n\n if (children !== contentRef.current) {\n contentRef.current = children;\n return children;\n }\n\n return contentRef.current;\n}\n","/**\n * Route Element Builder — constructs a React element tree from a matched route.\n *\n * Extracted from rsc-entry.ts to enable reuse by the revalidation renderer\n * (which needs the element tree without RSC serialization) and to keep\n * rsc-entry.ts under the 500-line limit.\n *\n * This module handles:\n * 1. Running access.ts checks eagerly to gate metadata resolution (TIM-1027)\n * 2. Loading page/layout components from the segment chain\n * 3. Resolving metadata (skipped for denied segments to prevent side effects)\n * 4. Building the React element tree (page → error boundaries → access gates → layouts)\n * 5. Resolving parallel slots\n *\n * See design/02-rendering-pipeline.md, design/04-authorization.md\n */\n\nimport { createElement } from 'react';\nimport { randomUUID } from 'node:crypto';\n\nimport { withSpan } from './tracing.js';\nimport type { RouteMatch } from './pipeline.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\nimport { resolveMetadata, renderMetadataToElements } from './metadata.js';\nimport type { Metadata } from './types.js';\nimport { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata-routes.js';\n\n// In dev mode, use a per-startup nonce for metadata route cache busting\n// instead of per-file content hashes (avoids rehashing on every request).\nlet _devNonce: string | undefined;\nfunction getDevNonce(): string {\n _devNonce ??= randomUUID().slice(0, 8);\n return _devNonce;\n}\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport { AccessGate } from './access-gate.js';\nimport {\n PageDenyBoundary,\n buildDenyPageChain,\n renderMatchingDenyPage,\n setDenyStatus,\n} from './deny-boundary.js';\nimport type { DenyPageEntry } from './deny-boundary.js';\nimport { resolveSlotElement } from './slot-resolver.js';\nimport { SegmentProvider } from '../client/segment-context.js';\nimport { SegmentOutlet } from '../client/segment-outlet.js';\n\nimport { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';\nimport type { InterceptionContext } from './pipeline.js';\nimport { shouldSkipSegment } from './state-tree-diff.js';\nimport { loadModule } from './safe-load.js';\n\n/**\n * Replace the outermost SegmentOutlet in a partial payload with its\n * SegmentProvider child. Walks through AccessGate wrappers (which sit\n * outside SegmentOutlet) to find the outlet.\n */\nfunction replaceOutermostSegmentOutlet(\n element: React.ReactElement,\n replacement: React.ReactElement\n): React.ReactElement {\n if (element.type === SegmentOutlet) {\n return replacement;\n }\n // AccessGate wraps outside SegmentOutlet — recurse through it.\n const props = element.props as Record<string, unknown>;\n if (element.type === AccessGate && props.children) {\n const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);\n /* eslint-disable react/no-children-prop -- createElement API */\n return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {\n ...props,\n children: inner,\n });\n /* eslint-enable react/no-children-prop */\n }\n return element;\n}\n\n// ─── Client Reference Detection ──────────────────────────────────────────\n\n/**\n * Symbol used by React Flight to mark client references.\n * Client references are proxy objects created by @vitejs/plugin-rsc for\n * 'use client' modules in the RSC environment. They must be passed to\n * createElement() — calling them as functions throws:\n * \"Unexpectedly client reference export 'default' is called on server\"\n */\nconst CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');\n\n/**\n * Detect whether a component is a React client reference.\n * Client references have $$typeof set to Symbol.for('react.client.reference')\n * by registerClientReference() in the React Flight server runtime.\n *\n * Used to skip OTEL tracing wrappers that would call the component as a\n * function. Client components must go through createElement only — they are\n * serialized as references in the RSC Flight stream, not executed on the server.\n */\nexport function isClientReference(component: unknown): boolean {\n return (\n component != null &&\n typeof component === 'function' &&\n (component as unknown as Record<string, unknown>).$$typeof === CLIENT_REFERENCE_TAG\n );\n}\n\n// ─── Param Coercion Error ─────────────────────────────────────────────────\n\n/**\n * Thrown when a defineSegmentParams codec's parse() fails.\n * The pipeline catches this and responds with 404.\n */\nexport class ParamCoercionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ParamCoercionError';\n }\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/** Head element for client-side metadata updates. */\nexport interface HeadElement {\n tag: string;\n content?: string;\n attrs?: Record<string, string | null>;\n}\n\n/** Layout entry with component and segment. */\nexport interface LayoutComponentEntry {\n component: (...args: unknown[]) => unknown;\n segment: ManifestSegmentNode;\n}\n\n/** Result of building a route element tree. */\nexport interface RouteElementResult {\n /** The React element tree (page wrapped in layouts, access gates, error boundaries). */\n element: React.ReactElement;\n /** Resolved head elements for metadata. */\n headElements: HeadElement[];\n /** Layout components loaded along the segment chain. */\n layoutComponents: LayoutComponentEntry[];\n /** Segments from the route match. */\n segments: ManifestSegmentNode[];\n /** Max deferSuspenseFor hold window across all segments. */\n deferSuspenseFor: number;\n /**\n * Segment paths that were skipped because the client already has them cached.\n * Ordered outermost to innermost. Empty when no segments were skipped.\n * The client uses this to merge the partial payload with cached segments.\n * See design/19-client-navigation.md §\"X-Timber-State-Tree Header\"\n */\n skippedSegments: string[];\n}\n\n// ─── Module Processing Helpers ─────────────────────────────────────────────\n\n/**\n * Reject the legacy `generateMetadata` export with a helpful migration message.\n * Throws if the module exports `generateMetadata` instead of `metadata`.\n */\nfunction rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {\n if ('generateMetadata' in mod) {\n throw new Error(\n `${filePath}: \"generateMetadata\" is not a valid export. ` +\n `Export an async function named \"metadata\" instead.\\n\\n` +\n ` // Before\\n` +\n ` export async function generateMetadata({ params }) { ... }\\n\\n` +\n ` // After\\n` +\n ` export async function metadata() { ... }`\n );\n }\n}\n\n/**\n * Extract and resolve metadata from a module (layout or page).\n * Handles both static metadata objects and async metadata functions.\n * Returns the resolved Metadata, or null if none exported.\n *\n * Metadata functions no longer receive { params } — they access params\n * via getSegmentParams() from ALS, same as page/layout components.\n */\nasync function extractMetadata(\n mod: Record<string, unknown>,\n segment: ManifestSegmentNode\n): Promise<Metadata | null> {\n if (typeof mod.metadata === 'function') {\n type MetadataFn = () => Promise<Metadata>;\n return (\n (await withSpan(\n 'timber.metadata',\n { 'timber.segment': segment.segmentName ?? segment.urlPath },\n () => (mod.metadata as MetadataFn)()\n )) ?? null\n );\n }\n if (mod.metadata) {\n return mod.metadata as Metadata;\n }\n return null;\n}\n\n/**\n * Extract `deferSuspenseFor` from a module and return the maximum\n * of the current value and the module's value.\n */\nfunction extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {\n if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {\n return mod.deferSuspenseFor;\n }\n return current;\n}\n\n// ─── Builder ──────────────────────────────────────────────────────────────\n\n/**\n * Build a React element tree from a matched route.\n *\n * Runs access checks eagerly to gate metadata resolution, then loads\n * modules, resolves metadata (skipping denied segments), and constructs\n * the element tree. DenySignal and RedirectSignal from access are stored\n * as AccessGate verdicts for synchronous replay during render.\n *\n * Does NOT serialize to RSC Flight — the caller decides whether to render\n * to a stream or use the element directly (e.g., for action revalidation).\n *\n * For passing segments, AccessGate re-runs access during render so that\n * React.cache is populated for layout dedup (TIM-662). For denied\n * segments, the pre-computed verdict is replayed — no re-execution,\n * no metadata side effects, no head leak (TIM-1027).\n */\nexport async function buildRouteElement(\n req: Request,\n match: RouteMatch,\n interception?: InterceptionContext,\n clientStateTree?: Set<string> | null,\n metadataRouteHashes?: Record<string, string>\n): Promise<RouteElementResult> {\n const segments = match.segments;\n\n // ── Access pre-pass ──────────────────────────────────────────────────\n // Run access checks eagerly to determine which segments are denied.\n // This gates metadata() resolution: denied segments' metadata functions\n // are never called, preventing unauthorized side effects (DB queries)\n // and metadata content leaking into the <head> of denied responses.\n //\n // Verdicts are passed to AccessGate for synchronous replay (DenySignal\n // renders the deny page in-tree; RedirectSignal propagates). For PASSING\n // segments, no verdict is passed — AccessGate re-runs access during\n // renderToReadableStream to populate React.cache for layout dedup.\n //\n // See TIM-1027, design/04-authorization.md, design/13-security.md.\n const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();\n let firstDeniedIndex = Infinity;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n if (!segment.access || i >= firstDeniedIndex) continue;\n\n try {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => Promise<void>) | undefined;\n if (accessFn) {\n await withSpan(\n 'timber.access.pre',\n { 'timber.segment': segment.segmentName ?? 'unknown' },\n () => accessFn()\n );\n }\n } catch (error: unknown) {\n if (error instanceof DenySignal || error instanceof RedirectSignal) {\n accessVerdicts.set(i, error);\n firstDeniedIndex = i;\n } else {\n throw error;\n }\n }\n }\n\n // ── Module loading + metadata resolution ─────────────────────────────\n const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];\n const layoutComponents: LayoutComponentEntry[] = [];\n let PageComponent: ((...args: unknown[]) => unknown) | null = null;\n let deferSuspenseFor = 0;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const isLeaf = i === segments.length - 1;\n const isDenied = i >= firstDeniedIndex;\n\n // Load layout\n if (segment.layout) {\n const mod = await loadModule(segment.layout);\n if (mod.default) {\n layoutComponents.push({\n component: mod.default as (...args: unknown[]) => unknown,\n segment,\n });\n }\n\n // Param coercion is handled in the pipeline (Stage 2c) before\n // middleware and rendering. See coerceSegmentParams() in pipeline.ts.\n\n rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);\n if (!isDenied) {\n const layoutMetadata = await extractMetadata(mod, segment);\n if (layoutMetadata) {\n metadataEntries.push({ metadata: layoutMetadata, isPage: false });\n }\n }\n deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);\n }\n\n // Load page (leaf segment only)\n if (isLeaf && segment.page) {\n const mod = await loadModule(segment.page);\n\n // Param coercion is handled in the pipeline (Stage 2c) before\n // middleware and rendering. See coerceSegmentParams() in pipeline.ts.\n\n if (mod.default) {\n PageComponent = mod.default as (...args: unknown[]) => unknown;\n }\n rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);\n if (!isDenied) {\n const pageMetadata = await extractMetadata(mod, segment);\n if (pageMetadata) {\n metadataEntries.push({ metadata: pageMetadata, isPage: true });\n }\n }\n deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);\n }\n }\n\n if (!PageComponent) {\n const segmentInfo = segments\n .map(\n (s, i) =>\n ` [${i}] ${s.segmentName} (page: ${s.page ? 'yes' : 'no'}, layout: ${s.layout ? 'yes' : 'no'}, children: ${s.children?.length ?? 0})${i === segments.length - 1 ? ' ← leaf' : ''}`\n )\n .join('\\n');\n throw new Error(\n `No page component found for route: ${new URL(req.url).pathname}\\nMatched segments:\\n${segmentInfo}`\n );\n }\n\n // Build deny page fallback chains for each segment position.\n // When AccessGate or PageDenyBoundary catches a DenySignal, they render\n // the matching deny page in-tree instead of throwing into React Flight.\n // The chain walks from the current segment outward to root, collecting\n // status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order.\n // See TIM-666.\n const denyPageChains = new Map<number, DenyPageEntry[]>();\n for (let i = 0; i < segments.length; i++) {\n const chain = await buildDenyPageChain(segments, i);\n if (chain.length > 0) {\n denyPageChains.set(i, chain);\n }\n }\n\n // Resolve metadata\n const resolvedMetadata = resolveMetadata(metadataEntries);\n const headElements = renderMetadataToElements(resolvedMetadata);\n\n // Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).\n // opengraph-image emits both og:image and twitter:image (no separate twitter-image convention).\n // Skip OG auto-linking when the user already declared images in metadata.\n // See design/16-metadata.md §\"Auto-Linking\"\n const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);\n const requestUrl = new URL(req.url);\n const requestPathname = requestUrl.pathname;\n // In dev mode, use the request origin so OG URLs resolve to localhost.\n // In production, use metadataBase (the canonical domain).\n const ogBase =\n process.env.NODE_ENV !== 'production'\n ? new URL(requestUrl.origin)\n : resolvedMetadata.metadataBase;\n\n for (let si = 0; si < segments.length; si++) {\n const segment = segments[si];\n if (!segment.metadataRoutes) continue;\n // Skip auto-linking for denied segments — same gate as metadata().\n if (si >= firstDeniedIndex) continue;\n for (const baseName of Object.keys(segment.metadataRoutes)) {\n const convention = METADATA_ROUTE_CONVENTIONS[baseName];\n if (!convention) continue;\n // Non-nestable routes only auto-link from root\n if (!convention.nestable && segment.urlPath !== '/') continue;\n // Skip auto-linking if user already declared the image in metadata\n if (convention.type === 'opengraph-image' && hasUserOgImage) continue;\n // Build the href using the actual request path (not the pattern with [param]).\n // For nestable routes, the metadata route sits under the same resolved path\n // as the page. For root-only routes, use '/'.\n const resolvedPrefix = convention.nestable\n ? requestPathname === '/'\n ? ''\n : requestPathname\n : '';\n let href = `${resolvedPrefix}/${convention.servePath}`;\n // Append cache-bust query param for image metadata routes\n if (convention.type === 'opengraph-image') {\n const metaFile = segment.metadataRoutes[baseName];\n const fileHash = metaFile?.filePath ? metadataRouteHashes?.[metaFile.filePath] : undefined;\n const cacheBust = fileHash ?? getDevNonce();\n href = `${href}?${cacheBust}`;\n }\n // Resolve to absolute URL for og:image/twitter:image (social crawlers need full URLs)\n if (ogBase && convention.type === 'opengraph-image') {\n href = new URL(href, ogBase).toString();\n }\n for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {\n if (autoLink.tag === 'link') {\n const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };\n if (autoLink.type) attrs.type = autoLink.type;\n headElements.push({ tag: 'link', attrs });\n } else {\n const attrs: Record<string, string> = { content: autoLink.content };\n if (autoLink.property) attrs.property = autoLink.property;\n if (autoLink.name) attrs.name = autoLink.name;\n headElements.push({ tag: 'meta', attrs });\n }\n }\n }\n }\n\n // Build element tree: page wrapped in layouts (innermost to outermost)\n const h = createElement as (...args: unknown[]) => React.ReactElement;\n\n // Build the page element.\n // Client references ('use client' pages) must NOT be called as functions —\n // they are proxy objects that throw when invoked. They must go through\n // createElement only, which serializes them as client references in the\n // RSC Flight stream. OTEL tracing is skipped for client components.\n // See TIM-627 for the original bug.\n // Build the page element.\n // Server component pages are wrapped in PageDenyBoundary which calls\n // them as async functions and catches DenySignal — rendering the deny\n // page in-tree instead of throwing into React Flight. This eliminates\n // the second render pass for deny pages. See TIM-666.\n //\n // Client reference pages ('use client') can't call deny() (server-only),\n // so they go through createElement normally — no wrapper needed.\n const leafIndex = segments.length - 1;\n const leafDenyPages = denyPageChains.get(leafIndex);\n let element: React.ReactElement;\n if (isClientReference(PageComponent)) {\n element = h(PageComponent, {});\n } else if (leafDenyPages && leafDenyPages.length > 0) {\n // Server component page WITH deny page chain — wrap in PageDenyBoundary\n element = h(PageDenyBoundary, {\n Page: PageComponent,\n route: match.segments[leafIndex]?.urlPath ?? '/',\n denyPages: leafDenyPages,\n });\n } else {\n // Server component page WITHOUT deny page chain — trace only\n const TracedPage = async (props: Record<string, unknown>) => {\n return withSpan(\n 'timber.page',\n { 'timber.route': match.segments[leafIndex]?.urlPath ?? '/' },\n () => (PageComponent as (props: Record<string, unknown>) => unknown)(props)\n );\n };\n element = h(TracedPage, {});\n }\n\n // Build a lookup of layout components by segment for O(1) access.\n const layoutBySegment = new Map(\n layoutComponents.map(({ component, segment }) => [segment, component])\n );\n\n // Track which segments were skipped for the X-Timber-Skipped-Segments header.\n // The client uses this to merge the partial payload with its cached segments.\n const skippedSegments: string[] = [];\n\n // Wrap from innermost (leaf) to outermost (root), processing every\n // segment in the chain. Each segment may contribute:\n // 1. Error boundaries (status files + error.tsx)\n // 2. Layout component — wraps children + parallel slots\n // 3. SegmentProvider — records position for useSelectedLayoutSegment\n //\n // When clientStateTree is provided (from X-Timber-State-Tree header on\n // client navigation), sync layouts the client already has are skipped.\n // Access.ts was pre-checked eagerly above for metadata gating (TIM-1027).\n // See design/19-client-navigation.md §\"X-Timber-State-Tree Header\"\n //\n // hasRenderedLayoutBelow tracks whether a non-skipped layout has been\n // seen below the current segment. A segment can ONLY be skipped if\n // there is a rendered layout below it — the client merger can only\n // replace inner SegmentProviders (client component boundaries), not\n // page content embedded in a layout's server-rendered output.\n // Without this guard, skipping the innermost layout causes the merger\n // to drop the layout entirely and replace it with just the page.\n let hasRenderedLayoutBelow = false;\n let outermostSegmentProvider: React.ReactElement | null = null;\n // Track whether any rendered inner layout also exists in the client's\n // state tree. This prevents cross-section skipping: e.g., navigating\n // from /(group-a) to /dashboard shouldn't skip root \"/\" because the\n // client has no mounted outlet at \"/dashboard\".\n let innerRenderedInClientTree = false;\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n const isLeaf = i === segments.length - 1;\n const layoutComponent = layoutBySegment.get(segment);\n\n // Check if this segment's layout can be skipped for partial rendering.\n // Skipped segments: no layout wrapping, no error boundaries, no slots,\n // no AccessGate in element tree (access already ran pre-render).\n //\n // Additional constraints beyond shouldSkipSegment:\n // - Must have a rendered layout below (so the merger can find an\n // inner SegmentProvider to splice the new content into)\n // - Route groups are never skipped because sibling groups share the\n // same urlPath (e.g., /(marketing) and /(app) both have \"/\"),\n // which would cause the wrong cached layout to be reused\n // - At least one inner rendered layout must exist in the client's\n // state tree, ensuring the client has a mounted outlet to receive\n // the partial payload\n const skip =\n shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&\n hasRenderedLayoutBelow &&\n segment.segmentType !== 'group' &&\n innerRenderedInClientTree;\n\n if (skip) {\n // Skip this segment's layout/error boundaries — the client uses its cached version.\n // Metadata was already resolved above (head elements are correct).\n // Record for X-Timber-Skipped-Segments header (outermost first, so prepend).\n skippedSegments.unshift(segment.urlPath);\n\n // SECURITY: Even though the layout is skipped, AccessGate MUST still\n // wrap the element tree. access.ts runs on every navigation regardless\n // of cached layouts or state tree content.\n // See design/13-security.md §\"Auth always runs\" (test #11).\n if (segment.access) {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => unknown) | undefined;\n if (accessFn) {\n // Pass verdict for denied/redirected segments so AccessGate replays\n // without re-execution. Passing segments omit verdict so AccessGate\n // re-runs access during render for React.cache population.\n const verdict = accessVerdicts.get(i);\n element = h(AccessGate, {\n accessFn,\n segmentName: segment.segmentName,\n denyPages: denyPageChains.get(i),\n ...(verdict ? { verdict } : {}),\n children: element,\n });\n }\n }\n\n continue;\n }\n\n // This segment is rendered — mark that future (outer) segments have\n // a rendered layout below them and can safely be skipped.\n if (layoutComponent) {\n hasRenderedLayoutBelow = true;\n // Use segmentId for the client state tree check. Route groups share\n // their parent's urlPath (both \"/\"), but their segmentId includes\n // the group name (e.g., \"/(group-a)\"), so the check correctly\n // fails when the client has never visited that group.\n const outletKey =\n segment.segmentType === 'group'\n ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`\n : segment.urlPath;\n if (clientStateTree?.has(outletKey)) {\n innerRenderedInClientTree = true;\n }\n }\n\n // Wrap with error boundaries from this segment (inside layout).\n // Keep ALL error boundaries (including 4xx) — they're the safety net for\n // DenySignal from nested server components that escape AccessGate/PageDenyBoundary\n // try/catch. Status-code files must be 'use client' TSX or MDX to serialize\n // as error boundary fallbacks. See TIM-666.\n element = await wrapSegmentWithErrorBoundaries(segment, element, h);\n\n // Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.\n // When AccessGate denies, the layout never renders. The deny page appears\n // at the AccessGate level, wrapped by PARENT layouts only.\n // This prevents leaking layout UI (sidebars, nav) on denied pages.\n // See design/04-authorization.md §\"Access Failure\".\n if (layoutComponent) {\n // Resolve parallel slots for this layout.\n // Compute the parent tree path so slots can build their full\n // segment path for per-slot param storage in ALS.\n const parentTreePath =\n '/' +\n segments\n .slice(0, i + 1)\n .map((s) => s.segmentName)\n .filter(Boolean)\n .join('/');\n const slotProps: Record<string, unknown> = {};\n const slotEntries = Object.entries(segment.slots ?? {});\n for (const [slotName, slotNode] of slotEntries) {\n slotProps[slotName] = await resolveSlotElement(\n slotNode as ManifestSegmentNode,\n match,\n h,\n interception,\n parentTreePath\n );\n }\n\n const segmentPath = segment.urlPath.split('/');\n const parallelRouteKeys = Object.keys(segment.slots ?? {});\n\n // For route groups, urlPath is shared with the parent (both \"/\"),\n // so include the group name to distinguish them. Used for both OTEL\n // span labels and client-side element caching (segmentId).\n const segmentId =\n segment.segmentType === 'group'\n ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`\n : segment.urlPath;\n\n // Build the layout element.\n // Same client reference guard as pages — client layouts must not be\n // called as functions. OTEL tracing is skipped for client components.\n let layoutElement: React.ReactElement;\n if (isClientReference(layoutComponent)) {\n layoutElement = h(layoutComponent, {\n ...slotProps,\n children: element,\n });\n } else {\n // Server component layout — wrap with OTEL tracing AND DenySignal\n // catching. If the layout calls deny(), the signal is caught here\n // and the matching deny page renders in-tree (same pattern as\n // AccessGate and PageDenyBoundary). Without this, DenySignal\n // escapes to React Flight onError and triggers the re-render\n // fallback path. See TIM-668, design/04-authorization.md.\n const layoutComponentRef = layoutComponent;\n const layoutDenyPages = denyPageChains.get(i);\n const TracedLayout = async (props: Record<string, unknown>) => {\n try {\n return await withSpan('timber.layout', { 'timber.segment': segmentId }, () =>\n (layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)\n );\n } catch (error: unknown) {\n if (error instanceof DenySignal && layoutDenyPages) {\n const denyElement = renderMatchingDenyPage(layoutDenyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n // Non-deny errors (RedirectSignal, runtime errors) propagate normally.\n throw error;\n }\n };\n layoutElement = h(TracedLayout, {\n ...slotProps,\n children: element,\n });\n }\n\n const segmentProviderElement = h(SegmentProvider, {\n segments: segmentPath,\n segmentId,\n parallelRouteKeys,\n children: layoutElement,\n });\n\n element = h(SegmentOutlet, {\n segmentPath: segmentId,\n children: segmentProviderElement,\n });\n\n // Track the SegmentProvider for the outermost rendered layout.\n // On partial navigation (segments skipped above), the client already\n // has a SegmentOutlet mounted at this position. Sending another one\n // in the payload causes double-wrapping and infinite context recursion.\n // We'll strip the outermost SegmentOutlet after the loop.\n outermostSegmentProvider = segmentProviderElement;\n }\n\n // Wrap in AccessGate OUTSIDE the layout.\n // If access denies, the deny page renders here — the layout above\n // never executes. Parent layouts (from outer iterations) form the shell.\n // See TIM-662, TIM-666, design/04-authorization.md §\"Access Failure\".\n if (segment.access) {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => unknown) | undefined;\n if (accessFn) {\n // Pass verdict for denied/redirected segments so AccessGate replays\n // without re-execution. Passing segments omit verdict so AccessGate\n // re-runs access during render for React.cache population.\n const verdict = accessVerdicts.get(i);\n element = h(AccessGate, {\n accessFn,\n segmentName: segment.segmentName,\n denyPages: denyPageChains.get(i),\n ...(verdict ? { verdict } : {}),\n children: element,\n });\n }\n }\n }\n\n // On partial navigation (some segments skipped), the client already has a\n // SegmentOutlet mounted at the outermost non-skipped segment's position.\n // Sending another SegmentOutlet in the payload causes double-wrapping —\n // the inner outlet reads the same context update and recurses infinitely.\n // Replace the outermost SegmentOutlet with just its SegmentProvider child.\n if (skippedSegments.length > 0 && outermostSegmentProvider) {\n element = replaceOutermostSegmentOutlet(element, outermostSegmentProvider);\n }\n\n return {\n element,\n headElements: headElements as HeadElement[],\n layoutComponents,\n segments,\n deferSuspenseFor,\n skippedSegments,\n };\n}\n","/**\n * Version Skew Detection — graceful recovery when stale clients hit new deployments.\n *\n * When a new version of the app is deployed, clients with open tabs still have\n * the old JavaScript bundle. Without version skew handling, these stale clients\n * will experience:\n *\n * 1. Server action calls that crash (action IDs are content-hashed)\n * 2. Chunk load failures (old filenames gone from CDN)\n * 3. RSC payload mismatches (component references differ between builds)\n *\n * This module implements deployment ID comparison:\n * - A per-build deployment ID is generated at build time (see build-manifest.ts)\n * - The client sends it via `X-Timber-Deployment-Id` header on every RSC/action request\n * - The server compares it against the current build's ID\n * - On mismatch: signal the client to reload (not crash)\n *\n * The deployment ID is always-on in production. Dev mode skips the check\n * (HMR handles code updates without full reloads).\n *\n * See design/25-production-deployments.md, TIM-446\n */\n\n// ─── Constants ───────────────────────────────────────────────────\n\n/** Header sent by the client with every RSC/action request. */\nexport const DEPLOYMENT_ID_HEADER = 'X-Timber-Deployment-Id';\n\n/** Response header that signals the client to do a full page reload. */\nexport const RELOAD_HEADER = 'X-Timber-Reload';\n\n// ─── Deployment ID ───────────────────────────────────────────────\n\n/**\n * The current build's deployment ID. Set at startup from the manifest init\n * module (globalThis.__TIMBER_DEPLOYMENT_ID__). Null in dev mode.\n */\nlet currentDeploymentId: string | null = null;\n\n/**\n * Set the current deployment ID. Called once at server startup from the\n * manifest init module. In dev mode this is never called (deployment ID\n * checks are skipped).\n */\nexport function setDeploymentId(id: string): void {\n currentDeploymentId = id;\n}\n\n/**\n * Get the current deployment ID. Returns null in dev mode.\n */\nexport function getDeploymentId(): string | null {\n return currentDeploymentId;\n}\n\n// ─── Skew Detection ──────────────────────────────────────────────\n\n/** Result of a version skew check. */\nexport interface SkewCheckResult {\n /** Whether the client's deployment ID matches the server's. */\n ok: boolean;\n /** The client's deployment ID (null if header not sent — e.g., initial page load). */\n clientId: string | null;\n}\n\n/**\n * Check if a request's deployment ID matches the current build.\n *\n * Returns `{ ok: true }` when:\n * - Dev mode (no deployment ID set — HMR handles updates)\n * - No deployment ID header (initial page load, non-RSC request)\n * - Deployment IDs match\n *\n * Returns `{ ok: false }` when:\n * - Client sends a deployment ID that differs from the current build\n */\nexport function checkVersionSkew(req: Request): SkewCheckResult {\n // Dev mode — no deployment ID checks (HMR handles updates)\n if (!currentDeploymentId) {\n return { ok: true, clientId: null };\n }\n\n const clientId = req.headers.get(DEPLOYMENT_ID_HEADER);\n\n // No header — initial page load or non-RSC request. Always OK.\n if (!clientId) {\n return { ok: true, clientId: null };\n }\n\n // Compare deployment IDs\n if (clientId === currentDeploymentId) {\n return { ok: true, clientId };\n }\n\n return { ok: false, clientId };\n}\n\n/**\n * Apply version skew reload headers to a response.\n * Sets X-Timber-Reload: 1 to signal the client to do a full page reload.\n */\nexport function applyReloadHeaders(headers: Headers): void {\n headers.set(RELOAD_HEADER, '1');\n}\n","/**\n * Metadata route helpers for the request pipeline.\n *\n * Handles serving static metadata files and serializing sitemap responses.\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\n\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Content types that are text-based and should include charset=utf-8.\n * Binary formats (images) should not include charset.\n */\nconst TEXT_CONTENT_TYPES = new Set([\n 'application/xml',\n 'text/plain',\n 'application/json',\n 'application/manifest+json',\n 'image/svg+xml',\n]);\n\n/**\n * Serve a static metadata file by reading it from disk.\n *\n * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)\n * are served as-is with the appropriate Content-Type header.\n * Text files include charset=utf-8; binary files do not.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\nexport async function serveStaticMetadataFile(\n metaMatch: import('./route-matcher.js').MetadataRouteMatch\n): Promise<Response> {\n const { contentType, file } = metaMatch;\n const isText = TEXT_CONTENT_TYPES.has(contentType);\n\n const body = await readFile(file.filePath);\n\n const headers: Record<string, string> = {\n 'Content-Type': isText ? `${contentType}; charset=utf-8` : contentType,\n 'Content-Length': String(body.byteLength),\n };\n\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serialize a sitemap array to XML.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemap(\n entries: Array<{\n url: string;\n lastModified?: string | Date;\n changeFrequency?: string;\n priority?: number;\n }>\n): string {\n const urls = entries\n .map((e) => {\n let xml = ` <url>\\n <loc>${escapeXml(e.url)}</loc>`;\n if (e.lastModified) {\n const date = e.lastModified instanceof Date ? e.lastModified.toISOString() : e.lastModified;\n xml += `\\n <lastmod>${escapeXml(date)}</lastmod>`;\n }\n if (e.changeFrequency) {\n xml += `\\n <changefreq>${escapeXml(e.changeFrequency)}</changefreq>`;\n }\n if (e.priority !== undefined) {\n xml += `\\n <priority>${e.priority}</priority>`;\n }\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`;\n}\n\n/**\n * Serialize a sitemap index (list of sub-sitemap URLs) to XML.\n * Used for pagination when the total URL count exceeds 50,000.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemapIndex(sitemapUrls: string[]): string {\n const sitemaps = sitemapUrls\n .map((url) => ` <sitemap>\\n <loc>${escapeXml(url)}</loc>\\n </sitemap>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${sitemaps}\\n</sitemapindex>`;\n}\n\n/** Escape special XML characters. */\nexport function escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * Interception route matching for the request pipeline.\n *\n * Matches target URLs against interception rewrites to support the\n * modal route pattern (soft navigation intercepts).\n *\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n\nimport { classifyUrlSegment } from '../routing/segment-classify.js';\n\n/** Result of a successful interception match. */\nexport interface InterceptionMatchResult {\n /** The pathname to re-match (the source/intercepting route's parent). */\n sourcePathname: string;\n}\n\n/**\n * Check if a pathname starts with a prefix on a segment boundary.\n *\n * Prevents /feed from matching /feed-private — the prefix must be\n * followed by '/' or be an exact match.\n */\nfunction hasSegmentPrefix(pathname: string, prefix: string): boolean {\n if (prefix === '/') return true;\n if (!pathname.startsWith(prefix)) return false;\n return pathname.length === prefix.length || pathname[prefix.length] === '/';\n}\n\n/**\n * Check if an intercepting route applies for this soft navigation.\n *\n * Matches the target pathname against interception rewrites, constrained\n * by the source URL (X-Timber-URL header — where the user navigates FROM).\n *\n * Returns the source pathname to re-match if interception applies, or null.\n */\nexport function findInterceptionMatch(\n targetPathname: string,\n sourceUrl: string,\n rewrites: import('../routing/interception.js').InterceptionRewrite[]\n): InterceptionMatchResult | null {\n for (const rewrite of rewrites) {\n // Check if the source URL starts with the intercepting prefix,\n // enforcing segment boundary to prevent /feed matching /feed-private.\n if (!hasSegmentPrefix(sourceUrl, rewrite.interceptingPrefix)) continue;\n\n // Check if the target URL matches the intercepted pattern.\n // Dynamic segments in the pattern match any single URL segment.\n if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) {\n return { sourcePathname: rewrite.interceptingPrefix };\n }\n }\n return null;\n}\n\n/**\n * Check if a pathname matches a URL pattern with dynamic segments.\n *\n * Supports [param] (single segment) and [...param] (one or more segments).\n * Static segments must match exactly.\n */\nexport function pathnameMatchesPattern(pathname: string, pattern: string): boolean {\n const pathParts = pathname === '/' ? [] : pathname.slice(1).split('/');\n const patternParts = pattern === '/' ? [] : pattern.slice(1).split('/');\n\n let pi = 0;\n for (let i = 0; i < patternParts.length; i++) {\n const seg = classifyUrlSegment(patternParts[i]);\n\n switch (seg.kind) {\n case 'catch-all':\n return pi < pathParts.length;\n case 'optional-catch-all':\n return true;\n case 'dynamic':\n if (pi >= pathParts.length) return false;\n pi++;\n continue;\n case 'static':\n if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;\n pi++;\n continue;\n }\n }\n\n return pi === pathParts.length;\n}\n","/**\n * Pipeline outcome translator — converts a `PhaseOutcome` (the value\n * each phase function returns) into a final `Response`.\n *\n * Lifted out of `pipeline-phases.ts` (TIM-853) so the per-phase try /\n * catch logic and the terminal Response-building logic each live in\n * their own file. The phases produce values; this module is the single\n * source of truth for how those values become wire responses.\n *\n * See design/07-routing.md §\"Request Lifecycle\".\n */\n\nimport {\n applyCookieJar,\n buildRedirectResponse,\n cloneWithMutableHeaders,\n fireOnRequestError,\n mergeMissingHeaders,\n} from './pipeline-helpers.js';\nimport {\n logProxyError,\n logMiddlewareError,\n logMiddlewareShortCircuit,\n logRenderError,\n} from './logger.js';\nimport { markResponseFlushed } from './request-context.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { isDebug } from './debug.js';\nimport type { PipelineConfig, RouteMatch } from './pipeline.js';\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction rscErrorResponse(isRsc: boolean, status: number, headers?: Headers): Response {\n if (!isRsc) return new Response(null, { status });\n const h = headers ?? new Headers();\n h.set('X-Timber-Error', '1');\n h.set('content-type', 'application/json; charset=utf-8');\n return new Response(JSON.stringify({ error: true, status }), { status, headers: h });\n}\n\n// ─── Phase Outcome ─────────────────────────────────────────────────────────\n\nexport type PhaseName = 'proxy' | 'middleware' | 'render';\n\nexport type PhaseOutcome =\n | { kind: 'response'; phase: PhaseName; response: Response }\n | { kind: 'redirect'; phase: PhaseName; signal: RedirectSignal }\n | { kind: 'deny'; phase: PhaseName; signal: DenySignal }\n | { kind: 'error'; phase: PhaseName; error: unknown };\n\nexport interface OutcomeContext {\n req: Request;\n method: string;\n path: string;\n responseHeaders?: Headers;\n match?: RouteMatch;\n}\n\n// ─── Translator ────────────────────────────────────────────────────────────\n\n/**\n * Terminal outcome handler — converts a `PhaseOutcome` into a final\n * `Response`, applying cookies, building redirects, rendering deny pages\n * and fallback error pages, and firing instrumentation hooks.\n *\n * This is the single source of truth for how phase outputs become wire\n * responses; the per-phase try/catch blocks now produce values, not\n * Responses, so the conversion logic lives in exactly one place.\n */\nexport async function outcomeToResponse(\n config: PipelineConfig,\n outcome: PhaseOutcome,\n ctx: OutcomeContext\n): Promise<Response> {\n switch (outcome.kind) {\n case 'response': {\n // Clone unconditionally so downstream code (cookie/header merge,\n // Server-Timing in createPipeline) can write headers without paying\n // for a try/catch immutability probe per request. User middleware,\n // proxy, and route code may all return `Response.redirect()` or\n // platform-level responses with frozen header bags. See TIM-866.\n const finalResponse = cloneWithMutableHeaders(outcome.response);\n\n if (outcome.phase === 'proxy') return finalResponse;\n\n if (outcome.phase === 'middleware' && ctx.responseHeaders) {\n applyCookieJar(finalResponse.headers);\n mergeMissingHeaders(finalResponse.headers, ctx.responseHeaders);\n logMiddlewareShortCircuit({\n method: ctx.method,\n path: ctx.path,\n status: finalResponse.status,\n });\n }\n\n if (outcome.phase === 'render') {\n markResponseFlushed();\n }\n\n return finalResponse;\n }\n\n case 'redirect': {\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n return buildRedirectResponse(outcome.signal, ctx.req, headers);\n }\n\n case 'deny': {\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n if (config.renderDenyFallback) {\n try {\n // Clone user-supplied deny-page responses so downstream\n // Server-Timing writes are safe against frozen header bags\n // (e.g. user returned Response.redirect from the hook).\n return cloneWithMutableHeaders(\n await config.renderDenyFallback(outcome.signal, ctx.req, headers, ctx.match)\n );\n } catch (denyRenderError) {\n // Deny page rendering failed — log before falling through to bare response.\n // Without this, a crashing deny page produces a blank response with zero\n // server-side signal. See TIM-876.\n logRenderError({ method: ctx.method, path: ctx.path, error: denyRenderError });\n await fireOnRequestError(denyRenderError, ctx.req, 'render');\n if (config.onPipelineError && denyRenderError instanceof Error)\n config.onPipelineError(denyRenderError, 'render');\n }\n }\n if (isDebug()) {\n console.warn(\n `[timber] DenySignal(${outcome.signal.status}) from ${outcome.phase} phase — ` +\n `no renderDenyFallback configured, returning bare ${outcome.signal.status} response\\n` +\n ` Request: ${ctx.method} ${ctx.path}\\n` +\n ` Add a not-found.tsx or error.tsx to render a custom deny page.`\n );\n }\n return new Response(null, { status: outcome.signal.status, headers });\n }\n\n case 'error': {\n // RSC payload requests (client navigation) expect Flight data, not HTML.\n // Signal the error via X-Timber-Error so the client hard-navigates\n // to the server-rendered error page instead of feeding HTML to the\n // Flight decoder (which crashes with \"enqueueModel is not a function\").\n const isRsc = (ctx.req.headers.get('Accept') ?? '').includes('text/x-component');\n\n if (outcome.phase === 'proxy') {\n logProxyError({ error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'proxy');\n if (config.onPipelineError && outcome.error instanceof Error)\n config.onPipelineError(outcome.error, 'proxy');\n return rscErrorResponse(isRsc, 500);\n }\n\n if (outcome.phase === 'middleware') {\n logMiddlewareError({ method: ctx.method, path: ctx.path, error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'handler');\n if (config.onPipelineError && outcome.error instanceof Error) {\n config.onPipelineError(outcome.error, 'middleware');\n }\n return rscErrorResponse(isRsc, 500);\n }\n\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n logRenderError({ method: ctx.method, path: ctx.path, error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'render');\n if (config.onPipelineError && outcome.error instanceof Error)\n config.onPipelineError(outcome.error, 'render');\n\n if (isRsc) {\n return rscErrorResponse(true, 500, headers);\n }\n\n if (config.renderFallbackError) {\n try {\n // Clone user-supplied fallback error responses so downstream\n // Server-Timing writes are safe against frozen header bags.\n return cloneWithMutableHeaders(\n await config.renderFallbackError(outcome.error, ctx.req, headers)\n );\n } catch (fallbackRenderError) {\n // Fallback rendering itself failed — log the secondary error before\n // falling through to bare 500. The original render error was already\n // logged above; this captures the fallback renderer's own crash so it\n // doesn't vanish silently. See TIM-876.\n logRenderError({ method: ctx.method, path: ctx.path, error: fallbackRenderError });\n await fireOnRequestError(fallbackRenderError, ctx.req, 'render');\n if (config.onPipelineError && fallbackRenderError instanceof Error)\n config.onPipelineError(fallbackRenderError, 'render');\n }\n }\n return new Response(null, { status: 500 });\n }\n }\n}\n","/**\n * Pipeline phase functions — module-level free functions that take their\n * dependencies as explicit parameters. Each phase returns a `PhaseOutcome`\n * (a discriminated union over response / redirect / deny / error) defined\n * in `pipeline-outcome.ts`. The terminal `outcomeToResponse` (also in\n * `pipeline-outcome.ts`) translates outcomes into Responses.\n *\n * Lifted out of `createPipeline` so each phase can be unit-tested in\n * isolation. The lift is mechanical — these functions used to be closures\n * over `config`; they now take `config` as an explicit parameter.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\".\n */\n\nimport { canonicalize } from './canonicalize.js';\nimport { runProxy } from './proxy.js';\nimport { runMiddlewareChain, shouldBypassMiddleware } from './middleware-runner.js';\nimport { withTiming } from './server-timing.js';\nimport {\n applyRequestHeaderOverlay,\n setMutableCookieContext,\n setSegmentParams,\n setMatchedSegmentPath,\n} from './request-context.js';\nimport { withSpan } from './tracing.js';\nimport { logRenderError } from './logger.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { ParamCoercionError } from './route-element-builder.js';\nimport { checkVersionSkew, applyReloadHeaders } from './version-skew.js';\nimport { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';\nimport { loadModule } from './safe-load.js';\nimport { findInterceptionMatch } from './pipeline-interception.js';\nimport { applyCookieJar, cloneWithMutableHeaders, type ProxyResolver } from './pipeline-helpers.js';\nimport { coerceSegmentParams } from './param-coercion.js';\nimport { outcomeToResponse, type PhaseOutcome } from './pipeline-outcome.js';\nimport type { InterceptionContext, PipelineConfig, RouteMatch } from './pipeline.js';\nimport type { MetadataHandler, MetadataRoute, MiddlewareContext } from './types.js';\nimport { swallow } from './logger.js';\nimport { isDebug } from './debug.js';\n\ninterface RenderContext {\n canonicalPathname: string;\n interception?: InterceptionContext;\n}\n\n/**\n * Validate and canonicalize the X-Timber-URL header value.\n *\n * Returns the canonical pathname if valid, or null if rejected:\n * - Must start with '/' (relative pathname, no scheme/authority)\n * - Must not contain control characters\n * - Must pass canonicalization (no encoded separators, null bytes, etc.)\n */\nfunction validateInterceptionHeader(raw: string, stripTrailingSlash: boolean): string | null {\n if (!raw.startsWith('/')) return null;\n if (raw.startsWith('//')) return null;\n for (let i = 0; i < raw.length; i++) {\n const code = raw.charCodeAt(i);\n if (code <= 0x1f || code === 0x7f) return null;\n }\n const result = canonicalize(raw, stripTrailingSlash);\n if (!result.ok) return null;\n return result.pathname;\n}\n\n// ─── Phase: Proxy ──────────────────────────────────────────────────────────\n\n/**\n * Run the proxy.ts phase. Calls user proxy code and uses `handleRequest` as\n * the inner `next()` continuation. The proxy resolver was picked at pipeline\n * construction time so the hot path sees no per-request branching on the\n * `ProxyConfig` discriminant.\n */\nexport async function runProxyPhase(\n config: PipelineConfig,\n getProxy: ProxyResolver,\n req: Request,\n method: string,\n path: string\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n try {\n const proxyExport = await getProxy();\n const proxyFn = () =>\n runProxy(proxyExport, req, () => handleRequest(config, req, method, path, true));\n const response = await withSpan('timber.proxy', {}, () =>\n detailed ? withTiming('proxy', 'proxy.ts', proxyFn) : proxyFn()\n );\n return { kind: 'response', phase: 'proxy', response };\n } catch (error) {\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'proxy', signal: error };\n }\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'proxy', signal: error };\n }\n return { kind: 'error', phase: 'proxy', error };\n }\n}\n\n// ─── Phase: Middleware ─────────────────────────────────────────────────────\n\n/**\n * Run the middleware chain phase. If the chain short-circuits with a Response,\n * returns it as a 'response' outcome. Otherwise applies the request header\n * overlay and falls through to the render phase.\n */\nexport async function runMiddlewarePhase(\n config: PipelineConfig,\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n renderContext: RenderContext\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n const ctx: MiddlewareContext = {\n req,\n requestHeaders: requestHeaderOverlay,\n headers: responseHeaders,\n segmentParams: match.segmentParams,\n earlyHints: (hints) => {\n for (const hint of hints) {\n // Match Cloudflare's cached Early Hints attribute order: `as` before `rel`.\n // Cloudflare caches Link headers and re-emits them on subsequent 200s.\n // If our order differs, the browser sees duplicate preloads and warns.\n let value: string;\n if (hint.as !== undefined) {\n value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;\n } else {\n value = `<${hint.href}>; rel=${hint.rel}`;\n }\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n responseHeaders.append('Link', value);\n }\n },\n };\n\n try {\n const chainFn = () => runMiddlewareChain(match.middlewareChain, ctx);\n // Enable cookie mutation during middleware (design/29-cookies.md §\"Context Tracking\")\n const middlewareResponse = await (async () => {\n setMutableCookieContext(true);\n try {\n return await withSpan('timber.middleware', {}, () =>\n detailed ? withTiming('mw', 'middleware.ts', chainFn) : chainFn()\n );\n } finally {\n setMutableCookieContext(false);\n }\n })();\n if (middlewareResponse) {\n return { kind: 'response', phase: 'middleware', response: middlewareResponse };\n }\n // Middleware chain completed without short-circuiting — apply any\n // injected request headers so getHeaders() returns them downstream.\n applyRequestHeaderOverlay(requestHeaderOverlay);\n\n // Apply cookie jar to response headers before render commits them.\n // This preserves the historical ordering where middleware cookie writes\n // are visible to route-handler header merging, while handler Set-Cookie\n // values still come after middleware cookies and therefore take precedence.\n applyCookieJar(responseHeaders);\n\n return runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, renderContext);\n } catch (error) {\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'middleware', signal: error };\n }\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'middleware', signal: error };\n }\n return { kind: 'error', phase: 'middleware', error };\n }\n}\n\n// ─── Phase: Render ─────────────────────────────────────────────────────────\n\n/**\n * Run the render phase. Wraps the configured renderer in a span and a\n * timing scope, and translates thrown signals into outcome variants.\n */\nexport async function runRenderPhase(\n config: PipelineConfig,\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n { canonicalPathname, interception }: RenderContext\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n try {\n const renderFn = () =>\n config.render(req, match, responseHeaders, requestHeaderOverlay, interception);\n const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>\n detailed ? withTiming('render', 'RSC + SSR render', renderFn) : renderFn()\n );\n return { kind: 'response', phase: 'render', response };\n } catch (error) {\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'render', signal: error };\n }\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'render', signal: error };\n }\n return { kind: 'error', phase: 'render', error };\n }\n}\n\n// ─── Request Handler ───────────────────────────────────────────────────────\n\n/**\n * Process a single request from canonicalization through phase dispatch.\n *\n * Stages: canonicalize → metadata routes → auto-sitemap → version skew →\n * route match → interception → early hints → param coercion → middleware →\n * render → outcome translation. Pre-routing short-circuits return Responses\n * directly; post-match dispatch goes through `outcomeToResponse`.\n *\n * Used both as the top-level entry (when no proxy.ts is configured) and as\n * the `next()` continuation passed to `runProxy()`.\n *\n * @param pathIsCanonical When true, `path` has already been canonicalized by\n * `createPipeline` — skip re-canonicalization to prevent double-decode.\n * When false (default), runs canonicalize as a safety net for direct callers.\n */\nexport async function handleRequest(\n config: PipelineConfig,\n req: Request,\n method: string,\n path: string,\n pathIsCanonical?: boolean\n): Promise<Response> {\n const stripTrailingSlash = config.stripTrailingSlash ?? true;\n\n // Stage 1: URL canonicalization.\n // When pathIsCanonical is true, createPipeline has already run canonicalize()\n // and passed the result as `path`. Re-canonicalizing would double-decode\n // percent-encoded characters (e.g., %61dmin → admin). See TIM-1004.\n //\n // When pathIsCanonical is false, this is a safety net for direct callers\n // (tests, non-pipeline usage) that haven't pre-canonicalized.\n let canonicalPathname: string;\n if (pathIsCanonical) {\n canonicalPathname = path;\n } else {\n const result = canonicalize(path, stripTrailingSlash);\n if (!result.ok) {\n if (isDebug()) {\n console.warn(\n `[timber] URL canonicalization rejected ${method} ${path} — responding with ${result.status}\\n` +\n ` This usually means the URL contains encoded separators (%2f, %5c),\\n` +\n ` null bytes (%00), path traversal (..), or malformed percent-encoding.`\n );\n }\n return new Response(null, { status: result.status });\n }\n canonicalPathname = result.pathname;\n }\n\n // Stage 1b: Metadata route matching — runs before regular route matching.\n // Metadata routes skip middleware.ts and access.ts (public endpoints for crawlers).\n // See design/16-metadata.md §\"Pipeline Integration\"\n if (config.matchMetadataRoute) {\n const metaMatch = config.matchMetadataRoute(canonicalPathname);\n if (metaMatch) {\n try {\n // Static metadata files (.xml, .txt, .png, .ico, etc.) are served\n // directly from disk. Dynamic metadata routes (.ts, .tsx) export a\n // handler function that generates the response.\n if (metaMatch.isStatic) {\n return await serveStaticMetadataFile(metaMatch);\n }\n\n setSegmentParams(metaMatch.segmentParams);\n const mod = await loadModule<{ default?: MetadataHandler }>(metaMatch.file);\n if (typeof mod.default !== 'function') {\n if (isDebug()) {\n console.warn(\n `[timber] Metadata route ${metaMatch.file} does not export a default function — responding with 500\\n` +\n ` Metadata routes must export a default function that returns the metadata content.`\n );\n }\n return new Response('Metadata route must export a default function', { status: 500 });\n }\n const handlerResult = await mod.default();\n // If the handler returns a Response, normalize headers so the\n // outer Server-Timing writer can append without hitting an\n // immutable header bag (e.g. user returns Response.redirect()).\n if (handlerResult instanceof Response) {\n if (method === 'HEAD') {\n return new Response(null, {\n status: handlerResult.status,\n statusText: handlerResult.statusText,\n headers: new Headers(handlerResult.headers),\n });\n }\n return cloneWithMutableHeaders(handlerResult);\n }\n // Otherwise, serialize based on content type. The type discriminator\n // here is the metadata route's declared content type (from the file\n // convention), not the shape of `handlerResult` — TS can't narrow the\n // `MetadataResult` union on that, so we assert against the expected\n // shape at each branch.\n const contentType = metaMatch.contentType;\n let body: string;\n if (typeof handlerResult === 'string') {\n body = handlerResult;\n } else if (contentType === 'application/xml') {\n body = serializeSitemap(handlerResult as MetadataRoute.Sitemap);\n } else if (contentType === 'application/manifest+json') {\n body = JSON.stringify(handlerResult, null, 2);\n } else {\n body = String(handlerResult);\n }\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': `${contentType}; charset=utf-8` },\n });\n } catch (error) {\n // Control-flow signals from the metadata handler (e.g. a sitemap that\n // calls deny(404) for an unknown tenant, or a dynamic icon that\n // redirects to a CDN-hosted asset) translate to proper HTTP responses\n // instead of being logged as 500s.\n if (error instanceof RedirectSignal) {\n return new Response(null, {\n status: error.status,\n headers: { Location: error.location },\n });\n }\n if (error instanceof DenySignal) {\n return new Response(null, { status: error.status });\n }\n logRenderError({ method, path, error });\n if (config.onPipelineError && error instanceof Error)\n config.onPipelineError(error, 'metadata-route');\n return new Response(null, { status: 500 });\n }\n }\n }\n\n // Stage 1b.2: Auto-generated sitemap — serves /sitemap.xml and /sitemap/N.xml\n // when sitemap generation is enabled and no user-authored sitemap exists.\n // Runs after metadata route matching so user sitemaps always take precedence.\n // See design/16-metadata.md §\"Auto-generated Sitemap\"\n if (config.autoSitemapHandler) {\n try {\n const sitemapResponse = await config.autoSitemapHandler(canonicalPathname);\n if (sitemapResponse) return cloneWithMutableHeaders(sitemapResponse);\n } catch (error) {\n logRenderError({ method, path, error });\n if (config.onPipelineError && error instanceof Error)\n config.onPipelineError(error, 'auto-sitemap');\n return new Response(null, { status: 500 });\n }\n }\n\n // Stage 1c: Version skew detection (TIM-446).\n // For RSC payload requests (client navigation), check if the client's\n // deployment ID matches the current build. On mismatch, signal the\n // client to do a full page reload instead of returning an RSC payload\n // that references mismatched module IDs.\n const isRscRequest = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRscRequest) {\n const skewCheck = checkVersionSkew(req);\n if (!skewCheck.ok) {\n const reloadHeaders = new Headers();\n applyReloadHeaders(reloadHeaders);\n return new Response(null, { status: 204, headers: reloadHeaders });\n }\n }\n\n // Stage 2: Route matching\n let match = config.matchRoute(canonicalPathname);\n let interception: InterceptionContext | undefined;\n\n // Stage 2a: Intercepting route resolution (modal pattern).\n // Only honored on RSC client-navigation requests (Accept: text/x-component)\n // to prevent spoofing from plain HTML navigations or raw HTTP clients.\n // The header value must be a valid relative pathname — reject schemes,\n // authority, and control characters. Canonicalize before matching.\n if (isRscRequest && config.interceptionRewrites?.length) {\n const rawSourceUrl = req.headers.get('X-Timber-URL');\n const validatedSourceUrl = rawSourceUrl\n ? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash)\n : null;\n if (validatedSourceUrl) {\n const intercepted = findInterceptionMatch(\n canonicalPathname,\n validatedSourceUrl,\n config.interceptionRewrites\n );\n if (intercepted) {\n const sourceMatch = config.matchRoute(intercepted.sourcePathname);\n if (sourceMatch) {\n match = sourceMatch;\n interception = { targetPathname: canonicalPathname };\n }\n }\n }\n }\n\n if (!match) {\n // Dev-time diagnostic: log the unmatched pathname so developers can\n // see why a request 404'd without needing to add breakpoints.\n if (isDebug()) {\n console.warn(\n `[timber] No route matched for pathname: ${canonicalPathname}\\n` +\n ` Input path: ${path}\\n` +\n ` Method: ${method}`\n );\n }\n // No route matched — render 404.tsx in root layout if available,\n // otherwise fall back to a bare 404 Response.\n if (config.renderNoMatch) {\n const responseHeaders = new Headers();\n return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));\n }\n return new Response(null, { status: 404 });\n }\n\n // Response and request header containers — created before early hints so\n // the emitter can append Link headers (e.g. for Cloudflare CDN → 103).\n const responseHeaders = new Headers();\n const requestHeaderOverlay = new Headers();\n\n // Set Cache-Control for dynamic HTML responses. Without this header,\n // CDNs (particularly Cloudflare) may attempt to buffer/process the\n // response differently, causing intermittent multi-second delays.\n // This matches Next.js's default behavior.\n responseHeaders.set('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate');\n\n // Stage 2b: 103 Early Hints (before middleware, after match)\n // Fires before middleware so the browser can begin fetching critical\n // assets while middleware runs. Non-fatal — a failing emitter never\n // blocks the request.\n if (config.earlyHints) {\n try {\n await config.earlyHints(match, req, responseHeaders);\n } catch (err) {\n swallow(err, 'early hints hook threw');\n }\n }\n\n // Stage 2c: Param coercion (before middleware)\n // Load params.ts modules from matched segments and coerce raw string\n // params through defineSegmentParams codecs. Coercion failure → 404\n // (middleware never runs). See design/07-routing.md §\"Where Coercion Runs\"\n //\n // Snapshot raw params before coercion — slot resolution needs the\n // original string values to reconstruct URL parts for tree matching.\n // Coerced params may have been transformed by codecs.\n match.rawSegmentParams = { ...match.segmentParams };\n try {\n await coerceSegmentParams(match);\n } catch (error) {\n if (error instanceof ParamCoercionError) {\n // Dev-time diagnostic: log param coercion failures so developers can\n // see why a matched route 404'd. Silent coercion 404s are brutal to debug.\n if (isDebug()) {\n const segmentChain = match.segments.map((s) => s.segmentName || '/').join(' → ');\n console.warn(\n `[timber] Param coercion failed for ${method} ${canonicalPathname} — responding with 404\\n` +\n ` Matched segments: ${segmentChain}\\n` +\n ` Error: ${error.message}\\n` +\n ` This usually means a params.ts codec rejected the URL params.\\n` +\n ` Check that all fields in defineSegmentParams() are optional for params\\n` +\n ` that don't appear at every route depth (e.g. year, month, day).`\n );\n }\n // For API routes (route.ts), return a bare 404 — not an HTML page.\n // API consumers expect JSON/empty responses, not rendered HTML.\n const leafSegment = match.segments[match.segments.length - 1];\n if ((leafSegment as { route?: unknown }).route && !(leafSegment as { page?: unknown }).page) {\n return new Response(null, { status: 404 });\n }\n // Route through the app's 404 page (404.tsx in root layout) instead of\n // returning a bare empty 404 Response. Falls back to bare 404 only if\n // no renderNoMatch renderer is configured.\n if (config.renderNoMatch) {\n return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));\n }\n return new Response(null, { status: 404 });\n }\n throw error;\n }\n\n // Store coerced segment params in ALS so components can access them\n // via getSegmentParams() instead of receiving them as a prop.\n // See design/07-routing.md §\"params.ts — Convention File for Typed Params\"\n setSegmentParams(match.segmentParams);\n\n // Store the matched segment path for dev-mode validation in getSegmentParams().\n // Build the tree path from the segment chain (includes groups and slots).\n const segmentPath =\n match.segments\n .map((s) => s.segmentName)\n .filter(Boolean)\n .join('/') || '/';\n setMatchedSegmentPath(segmentPath.startsWith('/') ? segmentPath : `/${segmentPath}`);\n\n // Bypass middleware for synthetic re-render requests built by the\n // action-dispatch wrapper after a no-JS form validation failure.\n // The wrapper has already executed middleware once on the inbound POST;\n // running it again on the rerender GET would double-execute auth, rate\n // limiting, and request-header injection. See TIM-871.\n const skipMiddleware = shouldBypassMiddleware(req);\n const outcome =\n !skipMiddleware && match.middlewareChain.length > 0\n ? await runMiddlewarePhase(config, req, match, responseHeaders, requestHeaderOverlay, {\n canonicalPathname,\n interception,\n })\n : await runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, {\n canonicalPathname,\n interception,\n });\n\n return outcomeToResponse(config, outcome, {\n req,\n method,\n path,\n responseHeaders,\n match,\n });\n}\n","/**\n * Request pipeline — the central dispatch for all timber.js requests.\n *\n * Pipeline stages (in order):\n * proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render\n *\n * The phase functions live in `pipeline-phases.ts` so each phase can be\n * tested in isolation. The terminal `outcomeToResponse` translator and\n * stateless helpers live in `pipeline-phases.ts` and `pipeline-helpers.ts`\n * respectively. This file owns only the public type surface and the\n * `createPipeline` entry point: trace ID setup, request-context ALS,\n * Server-Timing wrapping, and the activeRequests counter.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\",\n * and design/17-logging.md §\"Production Logging\"\n */\n\nimport type { ProxyExport } from './proxy.js';\nimport type { MiddlewareFn } from './middleware-runner.js';\nimport { runWithTimingCollector, getServerTimingHeader } from './server-timing.js';\nimport { runWithRequestContext } from './request-context.js';\nimport {\n generateTraceId,\n runWithTraceId,\n getOtelTraceId,\n replaceTraceId,\n withSpan,\n setSpanAttribute,\n} from './tracing.js';\nimport { logRequestReceived, logRequestCompleted, logSlowRequest } from './logger.js';\nimport { DenySignal } from './primitives.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\nimport { makeProxyResolver } from './pipeline-helpers.js';\nimport { handleRequest, runProxyPhase } from './pipeline-phases.js';\nimport { outcomeToResponse } from './pipeline-outcome.js';\nimport { canonicalize } from './canonicalize.js';\nimport { isDebug } from './debug.js';\n\n// ─── Route Match Result ────────────────────────────────────────────────────\n\n/**\n * Result of matching a canonical pathname against the route tree.\n *\n * `segments` is the runtime (`ManifestFile`-specialized) shape — the same\n * nodes carried in the virtual route manifest. TIM-863 unified this: the\n * matcher produces `ManifestSegmentNode[]` directly and every consumer\n * (render, slots, params coercion, early hints, deny fallback) sees the\n * same structural type with no `as unknown as` laundering.\n */\nexport interface RouteMatch {\n /** The matched segment chain from root to leaf. */\n segments: ManifestSegmentNode[];\n /** Extracted segment params (catch-all segments produce string[]). */\n segmentParams: Record<string, string | string[]>;\n /**\n * Raw segment params before codec coercion. Always string or string[].\n * Used by slot resolution to reconstruct URL parts — coerced params may\n * have been transformed by codecs and are unsuitable for URL matching.\n * Set by the pipeline before coercion runs.\n */\n rawSegmentParams?: Record<string, string | string[]>;\n /** Middleware chain from the segment tree, ordered root-to-leaf. */\n middlewareChain: MiddlewareFn[];\n}\n\n/** Function that matches a canonical pathname to a route. */\nexport type RouteMatcher = (pathname: string) => RouteMatch | null;\n\n/** Function that matches a canonical pathname to a metadata route. */\nexport type MetadataRouteMatcher = (\n pathname: string\n) => import('./route-matcher.js').MetadataRouteMatch | null;\n\n/** Context for intercepting route resolution (modal pattern). */\nexport interface InterceptionContext {\n /** The URL the user is navigating TO (the intercepted route). */\n targetPathname: string;\n}\n\n/** Function that renders a matched route into a Response. */\nexport type RouteRenderer = (\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n interception?: InterceptionContext\n) => Response | Promise<Response>;\n\n/** Function that sends 103 Early Hints for a matched route. */\nexport type EarlyHintsEmitter = (\n match: RouteMatch,\n req: Request,\n responseHeaders: Headers\n) => void | Promise<void>;\n\n// ─── Pipeline Configuration ────────────────────────────────────────────────\n\n/**\n * Proxy source — a tagged union so the choice between \"already-resolved\n * export\" and \"lazy HMR-friendly loader\" is encoded in the type, not\n * inferred per-request.\n *\n * - `static` — the proxy export is already resolved (production, tests).\n * - `lazy` — a loader is called per-request for HMR freshness (dev).\n *\n * `PipelineConfig.proxy` also accepts a bare `ProxyExport` (a function or\n * function array) as shorthand for the static variant — convenient for tests\n * that construct a `createPipeline` config inline. Omit the field entirely\n * when the app has no `proxy.ts`.\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\".\n */\nexport type ProxyConfig =\n | { kind: 'static'; export: ProxyExport }\n | { kind: 'lazy'; loader: () => Promise<{ default: ProxyExport }> };\n\nexport interface PipelineConfig {\n /**\n * proxy.ts source. Undefined if the app has no proxy.ts. Accepts either a\n * tagged `ProxyConfig` (canonical) or a bare `ProxyExport` as sugar for the\n * static variant.\n */\n proxy?: ProxyConfig | ProxyExport;\n /** Route matcher — resolves a canonical pathname to a RouteMatch. */\n matchRoute: RouteMatcher;\n /** Metadata route matcher — resolves metadata route pathnames (sitemap.xml, robots.txt, etc.) */\n matchMetadataRoute?: MetadataRouteMatcher;\n /** Renderer — produces the final Response for a matched route. */\n render: RouteRenderer;\n /** Renderer for no-match 404 — renders 404.tsx in root layout. */\n renderNoMatch?: (req: Request, responseHeaders: Headers) => Response | Promise<Response>;\n /** Early hints emitter — fires 103 hints after route match, before middleware. */\n earlyHints?: EarlyHintsEmitter;\n /** Whether to strip trailing slashes during canonicalization. Default: true. */\n stripTrailingSlash?: boolean;\n /** Slow request threshold in ms. Requests exceeding this emit a warning. 0 to disable. Default: 3000. */\n slowRequestMs?: number;\n /**\n * Interception rewrites — conditional routes for the modal pattern.\n * Generated at build time from intercepting route directories.\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n interceptionRewrites?: import('../routing/interception.js').InterceptionRewrite[];\n /**\n * Control Server-Timing header output.\n *\n * - `'detailed'` — per-phase breakdown (proxy, middleware, render).\n * - `'total'` — single `total;dur=N` entry (production-safe).\n * - `false` — no Server-Timing header at all.\n *\n * Default: `'total'`.\n */\n serverTiming?: 'detailed' | 'total' | false;\n /**\n * Auto-generated sitemap handler. When provided, the pipeline intercepts\n * `/sitemap.xml` and `/sitemap/N.xml` requests and delegates to this\n * function. Returns a Response or null (pass-through to regular routing).\n *\n * See design/16-metadata.md §\"Auto-generated Sitemap\"\n */\n autoSitemapHandler?: (pathname: string) => Promise<Response | null>;\n /**\n * Dev pipeline error callback — called when a pipeline phase (proxy,\n * middleware, render) catches an unhandled error. Used to wire the error\n * into the Vite browser error overlay in dev mode.\n *\n * Undefined in production — zero overhead.\n */\n onPipelineError?: (error: Error, phase: string) => void;\n\n /**\n * Dev error handler with RSC debug context — set by the dev server after\n * the RSC entry module is imported. `onPipelineError` resolves debug\n * components from ALS and delegates to this handler.\n *\n * This is a mutable property (not a constructor argument) because the dev\n * server imports the RSC entry module first (which constructs the config),\n * then wires in the overlay handler. Moving the state here (from a\n * module-level `let`) keeps it co-located with the config it belongs to\n * and makes it visible in tests without module-level mutation.\n *\n * Undefined in production — zero overhead.\n */\n devPipelineErrorHandler?: (\n error: Error,\n phase: string,\n debugComponents?: Array<{ name: string; env: string | null; stack: unknown[] | null }>\n ) => void;\n\n /**\n * HMR connection options for dev-only HTML pages (dev 404 page, fallback\n * error page) — set by the dev server after the RSC entry module is\n * imported, like `devPipelineErrorHandler`. The RSC environment can't\n * read the Vite server config (separate module graph), so the dev server\n * plumbs the resolved HMR endpoint (protocol/host/port/path/token) here\n * for the pages' auto-reload WebSocket. See TIM-1067.\n *\n * Undefined in production — zero overhead.\n */\n devHmrOptions?: import('../dev-tools/dev-page-shell.js').DevErrorHmrOptions;\n\n /**\n * Fallback error renderer — called when a catastrophic error escapes the\n * render phase. Produces an HTML Response instead of a bare empty 500.\n *\n * In dev mode, this renders a styled error page with the error message\n * and stack trace. In production, this attempts to render the app's\n * error.tsx / 5xx.tsx / 500.tsx from the root segment.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: 500 })`.\n */\n renderFallbackError?: (\n error: unknown,\n req: Request,\n responseHeaders: Headers\n ) => Response | Promise<Response>;\n /**\n * Fallback deny page renderer — called when a DenySignal escapes from\n * middleware or the render phase. Renders the appropriate status-code\n * page (403.tsx, 404.tsx, etc.) instead of returning a bare empty response.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: denyStatus })`.\n */\n renderDenyFallback?: (\n deny: DenySignal,\n req: Request,\n responseHeaders: Headers,\n /**\n * The matched route, if available. Provided by both the middleware-stage\n * and render-stage catch blocks (matching runs before middleware). When\n * present, the renderer should resolve the deny status file against the\n * matched chain so colocated `403.tsx`/`4xx.tsx`/`401.json` files are\n * picked up. Falls back to the root-only chain when omitted (e.g. for\n * deny()s thrown before route matching could complete). See TIM-822.\n */\n match?: RouteMatch\n ) => Response | Promise<Response>;\n}\n\n// ─── Pipeline ──────────────────────────────────────────────────────────────\n\n/**\n * Create the request handler from a pipeline configuration.\n *\n * Returns a function that processes an incoming Request through all pipeline\n * stages and produces a Response. This is the top-level entry point for the\n * server. The body is intentionally small — phase logic lives in\n * `pipeline-phases.ts`. This function only owns the per-request setup that\n * has to wrap the entire dispatch: trace ID, request context ALS, span\n * scope, Server-Timing header emission, and the active-request counter.\n */\nexport function createPipeline(config: PipelineConfig): (req: Request) => Promise<Response> {\n // Resolve the proxy source once. The request hot path calls this closure\n // directly with no discriminant check — the branch is taken here during\n // setup. For the lazy variant, `loader()` still runs per-request so HMR\n // continues to re-import the user's proxy.ts.\n const proxyResolver = makeProxyResolver(config.proxy);\n const slowRequestMs = config.slowRequestMs ?? 3000;\n const serverTiming = config.serverTiming ?? 'total';\n\n // Concurrent request counter — tracks how many requests are in-flight.\n // Logged with each request for diagnosing resource contention.\n let activeRequests = 0;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const method = req.method;\n const path = url.pathname;\n const startTime = performance.now();\n activeRequests++;\n\n // Establish per-request trace ID scope (design/17-logging.md §\"trace_id is Always Set\").\n // This runs before runWithRequestContext so traceId() is available from the\n // very first line of proxy.ts, middleware.ts, and all server code.\n const traceIdValue = generateTraceId();\n\n return runWithTraceId(traceIdValue, async () => {\n // Establish request context ALS scope so getHeaders() and getCookies() work\n // throughout the entire request lifecycle (proxy, middleware, render).\n return runWithRequestContext(req, async () => {\n // In dev mode, wrap with timing collector for Server-Timing header.\n // The collector uses ALS so timing entries are per-request.\n const runRequest = async () => {\n logRequestReceived({ method, path });\n\n const response = await withSpan(\n 'http.server.request',\n { 'http.request.method': method, 'url.path': path },\n async () => {\n // If OTEL is active, the root span now exists — replace the UUID\n // fallback with the real OTEL trace ID for log–trace correlation.\n const otelIds = await getOtelTraceId();\n if (otelIds) {\n replaceTraceId(otelIds.traceId, otelIds.spanId);\n }\n\n // Stage 0: Canonicalize URL at the outer boundary — before\n // proxy.ts sees the request. Every layer (proxy, middleware,\n // access, render) must see the same canonical path.\n // See design/07-routing.md §\"URL Canonicalization & Security\".\n const stripTrailingSlash = config.stripTrailingSlash ?? true;\n const canonResult = canonicalize(url.pathname, stripTrailingSlash);\n if (!canonResult.ok) {\n if (isDebug()) {\n console.warn(\n `[timber] URL canonicalization rejected ${method} ${url.pathname} — responding with ${canonResult.status}\\n` +\n ` This usually means the URL contains encoded separators (%2f, %5c),\\n` +\n ` null bytes (%00), path traversal (..), or malformed percent-encoding.`\n );\n }\n return new Response(null, { status: canonResult.status });\n }\n const canonicalPath = canonResult.pathname;\n\n // Construct a Request with the canonical URL so proxy.ts sees\n // the same path that route matching will use. This prevents\n // auth bypass via /admin/, /%61dmin, etc.\n let canonicalReq = req;\n if (url.pathname !== canonicalPath) {\n const canonicalUrl = new URL(req.url);\n canonicalUrl.pathname = canonicalPath;\n canonicalReq = new Request(canonicalUrl.toString(), req);\n }\n\n let result: Response;\n if (proxyResolver) {\n const outcome = await runProxyPhase(\n config,\n proxyResolver,\n canonicalReq,\n method,\n canonicalPath\n );\n result = await outcomeToResponse(config, outcome, {\n req: canonicalReq,\n method,\n path: canonicalPath,\n });\n } else {\n result = await handleRequest(config, canonicalReq, method, canonicalPath, true);\n }\n\n // Set response status on the root span before it ends —\n // DevSpanProcessor reads this for tree/summary output.\n await setSpanAttribute('http.response.status_code', result.status);\n\n // Vary: Accept — CDNs must cache HTML and RSC payload responses\n // separately for the same URL. Without this, a shared cache may\n // serve an RSC Flight payload to a browser expecting HTML (or\n // vice versa). See GHSA-wfc6-r584-vfw7, design/13-security.md.\n //\n // Vary: X-Timber-URL — intercepting route responses depend on\n // the source URL, so caches must store them separately.\n const varyTokens = ['Accept'];\n if (config.interceptionRewrites?.length) {\n varyTokens.push('X-Timber-URL');\n }\n const existingVary = result.headers.get('Vary');\n const existingTokens = existingVary\n ? existingVary\n .toLowerCase()\n .split(',')\n .map((t) => t.trim())\n : [];\n const newTokens = varyTokens.filter((t) => !existingTokens.includes(t.toLowerCase()));\n if (newTokens.length > 0) {\n result.headers.set(\n 'Vary',\n existingVary ? `${existingVary}, ${newTokens.join(', ')}` : newTokens.join(', ')\n );\n }\n\n // Append Server-Timing header based on configured mode.\n // Header mutability is guaranteed by the producer-side clone\n // in `outcomeToResponse` and the metadata-route / auto-sitemap\n // user-handler clones in `handleRequest`, so we can write\n // directly without a runtime probe. See TIM-866.\n if (serverTiming === 'detailed') {\n // Detailed: per-phase breakdown (proxy, middleware, render).\n const timingHeader = getServerTimingHeader();\n if (timingHeader) {\n result.headers.set('Server-Timing', timingHeader);\n }\n } else if (serverTiming === 'total') {\n // Total only: single `total;dur=N` — no phase names.\n // Prevents information disclosure while giving browser\n // DevTools useful timing data.\n const totalMs = Math.round(performance.now() - startTime);\n result.headers.set('Server-Timing', `total;dur=${totalMs}`);\n }\n // serverTiming === false: no header at all\n\n return result;\n }\n );\n\n // Post-span: structured production logging\n const durationMs = Math.round(performance.now() - startTime);\n const status = response.status;\n const concurrency = activeRequests;\n activeRequests--;\n logRequestCompleted({ method, path, status, durationMs, concurrency });\n\n if (slowRequestMs > 0 && durationMs > slowRequestMs) {\n logSlowRequest({ method, path, durationMs, threshold: slowRequestMs, concurrency });\n }\n\n return response;\n };\n\n return serverTiming === 'detailed' ? runWithTimingCollector(runRequest) : runRequest();\n });\n });\n };\n}\n","/**\n * Build manifest types and utilities for CSS and JS asset tracking.\n *\n * The build manifest maps route segment file paths to their output\n * chunks from Vite's client build. This enables:\n * - <link rel=\"stylesheet\"> injection in HTML <head>\n * - <script type=\"module\"> with hashed URLs in production\n * - <link rel=\"modulepreload\"> for client chunk dependencies\n * - Link preload headers for Early Hints (103)\n *\n * In dev mode, Vite's HMR client handles CSS/JS injection, so the build\n * manifest is empty. In production, it's populated from Vite's\n * .vite/manifest.json after the client build.\n *\n * Design docs: 18-build-system.md §\"Build Manifest\", 02-rendering-pipeline.md §\"Early Hints\"\n */\n\n/** A font asset entry in the build manifest. */\nexport interface ManifestFontEntry {\n /** URL path to the font file (e.g. `/_timber/fonts/inter-latin-400-abc123.woff2`). */\n href: string;\n /** Font format (e.g. `woff2`). */\n format: string;\n /** Crossorigin attribute — always `anonymous` for fonts. */\n crossOrigin: string;\n}\n\n/** Build manifest mapping input file paths to output asset URLs. */\nexport interface BuildManifest {\n /** Map from input file path (relative to project root) to output CSS URLs. */\n css: Record<string, string[]>;\n /** Map from input file path to output JS chunk URL (hashed filename). */\n js: Record<string, string>;\n /** Map from input file path to transitive JS dependency URLs for modulepreload. */\n modulepreload: Record<string, string[]>;\n /** Map from input file path to font assets used by that module. */\n fonts: Record<string, ManifestFontEntry[]>;\n /**\n * Cache-busting hashes for metadata route files (opengraph-image, etc.).\n * Map from file path (relative to project root) to short content hash.\n * In dev mode this is empty — a startup nonce is used instead.\n */\n metadataRouteHashes?: Record<string, string>;\n}\n\n/** Empty build manifest used in dev mode. */\nexport const EMPTY_BUILD_MANIFEST: BuildManifest = {\n css: {},\n js: {},\n modulepreload: {},\n fonts: {},\n metadataRouteHashes: {},\n};\n\n/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n/**\n * Collect all CSS files needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting CSS for each layout and page.\n * Deduplicates while preserving order (root layout CSS first).\n */\nexport function collectRouteCss(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const cssFiles = manifest.css[file.filePath];\n if (!cssFiles) continue;\n for (const url of cssFiles) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"stylesheet\"> tags for CSS URLs.\n *\n * Returns an HTML string to prepend to headHtml for injection\n * via injectHead() before </head>.\n */\nexport function buildCssLinkTags(cssUrls: string[]): string {\n // Emit only <link rel=\"stylesheet\"> — no preload tags. CSS preloading\n // is handled by 103 Early Hints (Link header) which fires before the\n // HTML stream. Float also emits `<link rel=\"stylesheet\" data-precedence>`\n // at the top of <head> during Fizz rendering — the browser discovers\n // CSS from that tag. A <link rel=\"preload\"> here would arrive *after*\n // Float's stylesheet tag and trigger \"Preload was ignored\" warnings.\n return cssUrls.map((url) => `<link rel=\"stylesheet\" href=\"${url}\">`).join('');\n}\n\n// ─── Font utilities ──────────────────────────────────────────────────────\n\n/**\n * Collect all font entries needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting fonts for each layout and page.\n * Deduplicates by href while preserving order.\n */\nexport function collectRouteFonts(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): ManifestFontEntry[] {\n const seen = new Set<string>();\n const result: ManifestFontEntry[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const fonts = manifest.fonts[file.filePath];\n if (!fonts) continue;\n for (const entry of fonts) {\n if (!seen.has(entry.href)) {\n seen.add(entry.href);\n result.push(entry);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"preload\"> tags for font assets.\n *\n * Font preloads use `as=font` and always include `crossorigin` (required\n * for font preloads even for same-origin resources per the spec).\n */\nexport function buildFontPreloadTags(fonts: ManifestFontEntry[]): string {\n return fonts\n .map(\n (f) =>\n `<link rel=\"preload\" href=\"${f.href}\" as=\"font\" type=\"font/${f.format}\" crossorigin=\"${f.crossOrigin}\">`\n )\n .join('');\n}\n\n// ─── JS chunk utilities ──────────────────────────────────────────────────\n\n/**\n * Collect modulepreload URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting transitive JS dependencies\n * for each layout and page. Deduplicates across segments.\n */\nexport function collectRouteModulepreloads(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const preloads = manifest.modulepreload[file.filePath];\n if (!preloads) continue;\n for (const url of preloads) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"modulepreload\"> tags for JS dependency URLs.\n *\n * Modulepreload hints tell the browser to fetch and parse JS modules\n * before they're needed, reducing waterfall latency for dynamic imports.\n */\nexport function buildModulepreloadTags(urls: string[]): string {\n return urls.map((url) => `<link rel=\"modulepreload\" href=\"${url}\">`).join('');\n}\n","/**\n * 103 Early Hints utilities.\n *\n * Early Hints are sent before the final response to let the browser\n * start fetching critical resources (CSS, fonts, JS) while the server\n * is still rendering.\n *\n * The framework collects hints from two sources:\n * 1. Build manifest — CSS, fonts, and JS chunks known at route-match time\n * 2. ctx.earlyHints() — explicit hints added by middleware or route handlers\n *\n * Both are emitted as Link headers. Cloudflare CDN automatically converts\n * Link headers into 103 Early Hints responses.\n *\n * Design docs: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport {\n collectRouteCss,\n collectRouteFonts,\n collectRouteModulepreloads,\n} from './build-manifest.js';\nimport type { BuildManifest } from './build-manifest.js';\n\n/** Minimal segment shape needed for early hint collection. */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n// ─── EarlyHint type ───────────────────────────────────────────────────────\n\n/**\n * A single Link header hint for 103 Early Hints.\n *\n * ```ts\n * ctx.earlyHints([\n * { href: '/styles/critical.css', rel: 'preload', as: 'style' },\n * { href: 'https://fonts.googleapis.com', rel: 'preconnect' },\n * ])\n * ```\n */\nexport interface EarlyHint {\n /** The resource URL (absolute or root-relative). */\n href: string;\n /** Link relation — `preload`, `modulepreload`, or `preconnect`. */\n rel: 'preload' | 'modulepreload' | 'preconnect';\n /** Resource type for `preload` hints (omit for `modulepreload` / `preconnect`). */\n as?: 'style' | 'script' | 'font' | 'image' | 'fetch' | 'document';\n /** Crossorigin attribute — required for font preloads per spec. */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /** Fetch priority hint — `high`, `low`, or `auto`. */\n fetchPriority?: 'high' | 'low' | 'auto';\n}\n\n// ─── formatLinkHeader ─────────────────────────────────────────────────────\n\n/**\n * Format a single EarlyHint as a Link header value.\n *\n * Attribute order: `as` before `rel` to match Cloudflare CDN's cached\n * Early Hints format. Cloudflare caches Link headers from 200 responses\n * and re-emits them as 103 Early Hints on subsequent requests. If our\n * attribute order differs from Cloudflare's cached copy, the browser\n * sees two preload headers for the same URL (different attribute order)\n * and warns \"Preload was ignored.\" Matching the order ensures the\n * browser deduplicates them correctly.\n *\n * Examples:\n * `</styles/root.css>; as=style; rel=preload`\n * `</fonts/inter.woff2>; as=font; rel=preload; crossorigin=anonymous`\n * `</_timber/client.js>; rel=modulepreload`\n * `<https://fonts.googleapis.com>; rel=preconnect`\n */\nexport function formatLinkHeader(hint: EarlyHint): string {\n // For preload hints, emit `as` before `rel` to match Cloudflare's\n // cached header format and avoid duplicate preload warnings.\n if (hint.as !== undefined) {\n let value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n }\n // For modulepreload / preconnect (no `as`), emit rel first.\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n}\n\n// ─── collectEarlyHintHeaders ──────────────────────────────────────────────\n\n/** Options for early hint collection. */\nexport interface EarlyHintOptions {\n /** Skip JS modulepreload hints (e.g. when client JavaScript is disabled). */\n skipJs?: boolean;\n}\n\n/**\n * Collect all Link header strings for a matched route's segment chain.\n *\n * Walks the build manifest to emit hints for:\n * - CSS stylesheets (as=style; rel=preload)\n * - Font assets (as=font; rel=preload; crossorigin)\n * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set\n *\n * Returns formatted Link header strings, deduplicated by URL, root → leaf order.\n * Returns an empty array in dev mode (manifest is empty).\n */\nexport function collectEarlyHintHeaders(\n segments: SegmentWithFiles[],\n manifest: BuildManifest,\n options?: EarlyHintOptions\n): string[] {\n const result: string[] = [];\n // Dedup by URL (href), not by full formatted header string.\n // Different code paths can produce the same URL with different attribute\n // ordering, which would bypass a full-string dedup and produce duplicate\n // Link headers that trigger browser \"preload was ignored\" warnings.\n const seenUrls = new Set<string>();\n\n const add = (url: string, header: string) => {\n if (!seenUrls.has(url)) {\n seenUrls.add(url);\n result.push(header);\n }\n };\n\n // Per-route CSS — as=style; rel=preload\n // The HTML <head> also contains a matching <link rel=\"preload\" as=\"style\">\n // tag so browsers can deduplicate the 103 hint against the HTML tag.\n for (const url of collectRouteCss(segments, manifest)) {\n add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Fonts — as=font; rel=preload; crossorigin (crossorigin required per spec)\n for (const font of collectRouteFonts(segments, manifest)) {\n add(\n font.href,\n formatLinkHeader({ href: font.href, rel: 'preload', as: 'font', crossOrigin: 'anonymous' })\n );\n }\n\n // JS chunks — rel=modulepreload (skip when client JS is disabled)\n if (!options?.skipJs) {\n for (const url of collectRouteModulepreloads(segments, manifest)) {\n add(url, formatLinkHeader({ href: url, rel: 'modulepreload' }));\n }\n }\n\n return result;\n}\n","/**\n * Per-request 103 Early Hints sender — ALS bridge for platform adapters.\n *\n * The pipeline collects Link headers for CSS, fonts, and JS chunks at\n * route-match time. On platforms that support it (Node.js v18.11+, Bun),\n * the adapter can send these as a 103 Early Hints interim response before\n * the final response is ready.\n *\n * This module provides an ALS-based bridge: the generated entry point\n * (e.g., the Nitro entry) wraps the handler with `runWithEarlyHintsSender`,\n * binding a per-request sender function. The pipeline calls\n * `sendEarlyHints103()` to fire the 103 if a sender is available.\n *\n * On platforms where 103 is handled at the CDN level (e.g., Cloudflare\n * converts Link headers into 103 automatically), no sender is installed\n * and `sendEarlyHints103()` is a no-op.\n *\n * Design doc: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport { earlyHintsSenderAls } from './als-registry.js';\nimport { swallow } from './logger.js';\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/**\n * Run a function with a per-request early hints sender installed.\n *\n * Called by generated entry points (e.g., Nitro node-server/bun) to\n * bind the platform's writeEarlyHints capability for the request duration.\n */\nexport function runWithEarlyHintsSender<T>(sender: EarlyHintsSenderFn, fn: () => T): T {\n return earlyHintsSenderAls.run(sender, fn);\n}\n\n/**\n * Send collected Link headers as a 103 Early Hints response.\n *\n * No-op if no sender is installed for the current request (e.g., on\n * Cloudflare where the CDN handles 103 automatically, or in dev mode).\n *\n * Non-fatal: errors from the sender are caught and silently ignored.\n */\nexport function sendEarlyHints103(links: string[]): void {\n if (!links.length) return;\n const sender = earlyHintsSenderAls.getStore();\n if (!sender) return;\n try {\n sender(links);\n } catch (err) {\n swallow(err, 'early hints 103 send failed');\n }\n}\n","/**\n * Element tree construction for timber.js rendering.\n *\n * Builds a unified React element tree from a matched segment chain, bottom-up:\n * page → status-code error boundaries → access gates → layout → repeat up segment chain\n *\n * The tree is rendered via a single `renderToReadableStream` call,\n * giving one `React.cache` scope for the entire route.\n *\n * See design/02-rendering-pipeline.md §\"Element Tree Construction\"\n */\n\nimport type { ReactNode } from 'react';\nimport type { RouteFile, SegmentNode } from '../routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A loaded module for a route file convention. */\nexport interface LoadedModule {\n /** The default export (component, access function, etc.) */\n default?: unknown;\n /** Named exports (for route.ts method handlers, metadata, etc.) */\n [key: string]: unknown;\n}\n\n/** Function that loads a route file's module. */\nexport type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;\n\n/**\n * A React component reference loaded from a route module's default export.\n *\n * Loaded modules' `default` is typed as `unknown` (modules are dynamic), so\n * call sites narrow it through `isValidElementType` (below) before treating\n * it as a component. The signature is a callable returning `ReactNode` —\n * TypeScript's view of every valid React component shape, including exotic\n * components (`memo`, `forwardRef`, `lazy`) which the type system treats as\n * callable even though their runtime values are objects with `$$typeof`\n * markers rather than functions.\n */\nexport type LoadedComponent = (...args: unknown[]) => ReactNode;\n\n// Marker symbols React stamps onto exotic component types. Mirrors the\n// internal `isValidElementType` check in `react.development.js` — React\n// doesn't export it, and we don't want to add `react-is` just for this one\n// validation. Inlined the same way `isClientReference` in\n// `route-element-builder.ts` inlines the client-reference marker.\nconst REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');\nconst REACT_MEMO_TYPE = Symbol.for('react.memo');\nconst REACT_LAZY_TYPE = Symbol.for('react.lazy');\nconst REACT_PROVIDER_TYPE = Symbol.for('react.provider');\nconst REACT_CONTEXT_TYPE = Symbol.for('react.context');\nconst REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');\nconst REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');\nconst REACT_CLIENT_REFERENCE_TYPE = Symbol.for('react.client.reference');\n\nconst REACT_COMPONENT_TYPE_MARKERS: ReadonlySet<symbol> = new Set([\n REACT_FORWARD_REF_TYPE,\n REACT_MEMO_TYPE,\n REACT_LAZY_TYPE,\n REACT_PROVIDER_TYPE,\n REACT_CONTEXT_TYPE,\n REACT_SUSPENSE_TYPE,\n REACT_SUSPENSE_LIST_TYPE,\n REACT_CLIENT_REFERENCE_TYPE,\n]);\n\n/**\n * Validate that a loaded module's `default` export is something React\n * accepts as the first argument to `createElement` — i.e. a valid component\n * type. React doesn't export `isValidElementType` (only `isValidElement`,\n * which checks for *elements*, not *component types*), so this mirrors\n * React's internal check:\n *\n * - functions → function or class components\n * - objects with a `$$typeof` matching one of React's known component\n * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,\n * suspense, client references via `@vitejs/plugin-rsc`)\n *\n * Strings (HTML tag names) are valid for `createElement` but never appear\n * as a route module's default export, so they're not recognized here.\n *\n * Anything else (numbers, plain config objects, JSON, etc.) is rejected so\n * the boundary wrapper is skipped rather than crashing inside React.\n */\nfunction isValidElementType(value: unknown): value is LoadedComponent {\n if (typeof value === 'function') return true;\n if (typeof value !== 'object' || value === null) return false;\n const marker = (value as { $$typeof?: unknown }).$$typeof;\n return typeof marker === 'symbol' && REACT_COMPONENT_TYPE_MARKERS.has(marker);\n}\n\n/**\n * Function that creates a React element. Matches React.createElement signature.\n *\n * `props` is typed as `object | null` rather than `Record<string, unknown>` so\n * that interface types with known keys (e.g. `AccessGateProps`,\n * `ErrorBoundaryProps`) flow through without an explicit index-signature cast.\n */\nexport type CreateElement = (\n type: unknown,\n props: object | null,\n ...children: unknown[]\n) => ReactNode;\n\n/**\n * Resolved slot content for a layout.\n * Key is slot name (without @), value is the element tree for that slot.\n */\nexport type SlotElements = Map<string, ReactNode>;\n\n/** Configuration for the tree builder. */\nexport interface TreeBuilderConfig {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Loads a route file's module. */\n loadModule: ModuleLoader;\n /** React.createElement or equivalent. */\n createElement: CreateElement;\n /**\n * Error boundary component for wrapping segments.\n *\n * This is injected by the caller rather than imported directly to avoid\n * pulling 'use client' code into the server barrel (@timber-js/app/server).\n * In the RSC environment, the RSC plugin transforms this import to a\n * client reference proxy — the caller handles the import so the server\n * barrel stays free of client dependencies.\n */\n errorBoundaryComponent?: unknown;\n}\n\n// ─── Component wrappers ──────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate component.\n *\n * When `verdict` is provided (from the pre-render pass), AccessGate replays\n * the stored result synchronously — no re-execution, no async, immune to\n * Suspense timing. When `verdict` is absent, falls back to calling `accessFn`\n * (backward compat for tree-builder.ts which doesn't run a pre-render pass).\n */\nexport interface AccessGateProps {\n accessFn: () => unknown;\n /** Segment name for dev logging (e.g. \"authenticated\", \"dashboard\"). */\n segmentName?: string;\n /**\n * Pre-computed verdict from the pre-render pass. When set, AccessGate\n * replays this verdict synchronously instead of calling accessFn.\n * - 'pass': render children\n * - DenySignal/RedirectSignal: throw synchronously\n */\n verdict?:\n | 'pass'\n | import('./primitives.js').DenySignal\n | import('./primitives.js').RedirectSignal;\n /**\n * Deny page fallback chain. When provided and a DenySignal is caught,\n * AccessGate renders the matching deny page in-tree instead of throwing.\n * This prevents the error from reaching React Flight, eliminating the\n * second render pass. See TIM-666.\n */\n denyPages?: import('./deny-boundary.js').DenyPageEntry[];\n children: ReactNode;\n}\n\n/**\n * Framework-injected slot access gate component.\n * On denial, renders denied.tsx → default.tsx → null instead of failing the page.\n *\n * DeniedComponent is passed instead of a pre-built element so that\n * SlotAccessGate can forward DenySignal.data as dangerouslyPassData\n * and slotName as the slot prop after catching the signal.\n */\nexport interface SlotAccessGateProps {\n accessFn: () => unknown;\n /** The denied.tsx component (not a pre-built element). null if no denied.tsx exists. */\n DeniedComponent: LoadedComponent | null;\n /** Slot directory name without @ prefix (e.g. \"admin\", \"sidebar\"). */\n slotName: string;\n /** createElement function for building elements dynamically. */\n createElement: CreateElement;\n defaultFallback: ReactNode;\n children: ReactNode;\n}\n\n/**\n * Framework-injected error boundary wrapper.\n * Wraps content with status-code error boundary handling.\n *\n * Field types must agree with `TimberErrorBoundaryProps` in\n * `client/error-boundary.tsx`. The two are kept structurally compatible by\n * convention rather than by direct type import — tree-builder.ts is the\n * server-side construction site and may not import types from a 'use client'\n * module to keep the server barrel free of client coupling.\n */\nexport interface ErrorBoundaryProps {\n /** The component to render when an error is caught (TSX status files). */\n fallbackComponent?: LoadedComponent;\n /** Pre-rendered fallback element (MDX status files — see TIM-503). */\n fallbackElement?: ReactNode;\n /** Status code filter: 400 = any 4xx, 500 = any 5xx, specific number = exact match. */\n status?: number;\n children: ReactNode;\n}\n\n// ─── Tree Builder ────────────────────────────────────────────────────────────\n\n/**\n * Result of building the element tree.\n */\nexport interface TreeBuildResult {\n /**\n * The root React element tree ready for renderToReadableStream.\n * `null` for API routes (route.ts), which don't render a React tree.\n */\n tree: ReactNode;\n /** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */\n isApiRoute: boolean;\n}\n\n/**\n * Build the unified element tree from a matched segment chain.\n *\n * Construction is bottom-up:\n * 1. Start with the page component (leaf segment)\n * 2. Wrap in status-code error boundaries (fallback chain)\n * 3. Wrap in AccessGate (if segment has access.ts)\n * 4. Pass as children to the segment's layout\n * 5. Repeat up the segment chain to root\n *\n * Parallel slots are resolved at each layout level and composed as named props.\n */\nexport async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {\n const { segments, loadModule, createElement, errorBoundaryComponent } = config;\n\n if (segments.length === 0) {\n throw new Error('[timber] buildElementTree: empty segment chain');\n }\n\n const leaf = segments[segments.length - 1];\n\n // API routes (route.ts) don't build a React tree\n if (leaf.route && !leaf.page) {\n return { tree: null, isApiRoute: true };\n }\n\n // Start with the page component\n const pageModule = leaf.page ? await loadModule(leaf.page) : null;\n const PageComponent = pageModule?.default as LoadedComponent | undefined;\n\n if (!PageComponent) {\n throw new Error(\n `[timber] No page component found for route at ${leaf.urlPath}. ` +\n 'Each route must have a page.tsx or route.ts.'\n );\n }\n\n // Build the page element — params are accessed via getSegmentParams() from ALS\n let element: ReactNode = createElement(PageComponent, {});\n\n // Build tree bottom-up: wrap page, then walk segments from leaf to root\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Wrap in error boundaries (status-code files + error.tsx)\n element = await wrapWithErrorBoundaries(\n segment,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in AccessGate if segment has access.ts\n if (segment.access) {\n const accessModule = await loadModule(segment.access);\n const accessFn = accessModule.default as AccessGateProps['accessFn'];\n element = createElement('timber:access-gate', {\n accessFn,\n segmentName: segment.segmentName,\n children: element,\n } satisfies AccessGateProps);\n }\n\n // Wrap in layout (if exists and not the leaf's page-level wrapping)\n if (segment.layout) {\n const layoutModule = await loadModule(segment.layout);\n const LayoutComponent = layoutModule.default as LoadedComponent | undefined;\n\n if (LayoutComponent) {\n // Resolve parallel slots for this layout\n const slotProps: Record<string, ReactNode> = {};\n const slotNames = Object.keys(segment.slots);\n if (slotNames.length > 0) {\n for (const slotName of slotNames) {\n const slotNode = segment.slots[slotName]!;\n slotProps[slotName] = await buildSlotElement(\n slotNode,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n }\n }\n\n /* eslint-disable react/no-children-prop -- createElement API */\n element = createElement(LayoutComponent, {\n ...slotProps,\n children: element,\n });\n /* eslint-enable react/no-children-prop */\n }\n }\n }\n\n return { tree: element, isApiRoute: false };\n}\n\n// ─── Slot Element Builder ────────────────────────────────────────────────────\n\n/**\n * Build the element tree for a parallel slot.\n *\n * Slots have their own access.ts (SlotAccessGate) and error boundaries.\n * On access denial: denied.tsx → default.tsx → null (graceful degradation).\n */\nasync function buildSlotElement(\n slotNode: SegmentNode,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactNode> {\n // Load slot page\n const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;\n const PageComponent = pageModule?.default as LoadedComponent | undefined;\n\n // Load default.tsx fallback\n const defaultModule = slotNode.default ? await loadModule(slotNode.default) : null;\n const DefaultComponent = defaultModule?.default as LoadedComponent | undefined;\n\n // If no page, render default.tsx or null\n if (!PageComponent) {\n return DefaultComponent ? createElement(DefaultComponent, {}) : null;\n }\n\n let element: ReactNode = createElement(PageComponent, {});\n\n // Wrap in error boundaries\n element = await wrapWithErrorBoundaries(\n slotNode,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in SlotAccessGate if slot has access.ts\n if (slotNode.access) {\n const accessModule = await loadModule(slotNode.access);\n const accessFn = accessModule.default as SlotAccessGateProps['accessFn'];\n\n // Load denied.tsx — pass component (not pre-built element) so\n // SlotAccessGate can forward DenySignal.data dynamically. See TIM-488.\n const deniedModule = slotNode.denied ? await loadModule(slotNode.denied) : null;\n const DeniedComponent = (deniedModule?.default as LoadedComponent | undefined) ?? null;\n\n const defaultFallback = DefaultComponent ? createElement(DefaultComponent, {}) : null;\n\n element = createElement('timber:slot-access-gate', {\n accessFn,\n DeniedComponent,\n slotName: slotNode.segmentName.replace(/^@/, ''),\n createElement,\n defaultFallback,\n children: element,\n } satisfies SlotAccessGateProps);\n }\n\n return element;\n}\n\n// ─── Error Boundary Wrapping ─────────────────────────────────────────────────\n\n/** MDX/markdown extensions — these are server components that cannot be passed as function props. */\nconst MDX_EXTENSIONS = new Set(['mdx', 'md']);\n\n/**\n * Check if a route file is an MDX/markdown file based on its extension.\n * MDX components are server components by default and cannot cross the\n * RSC→client boundary as function props. They must be pre-rendered as\n * elements and passed as fallbackElement instead of fallbackComponent.\n */\nfunction isMdxFile(file: RouteFile): boolean {\n return MDX_EXTENSIONS.has(file.extension);\n}\n\n/**\n * Wrap an element with error boundaries from a segment's status-code files.\n *\n * Wrapping order (innermost to outermost):\n * 1. Specific status files (503.tsx, 429.tsx, etc.)\n * 2. Category catch-alls (4xx.tsx, 5xx.tsx)\n * 3. error.tsx (general error boundary)\n *\n * This creates the fallback chain described in design/10-error-handling.md.\n *\n * MDX status files are server components and cannot be passed as function\n * props to TimberErrorBoundary (a 'use client' component). Instead, they\n * are pre-rendered as elements and passed as fallbackElement. The error\n * boundary renders the element directly when an error is caught.\n * See TIM-503.\n */\nasync function wrapWithErrorBoundaries(\n segment: SegmentNode,\n element: ReactNode,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactNode> {\n // Wrapping is applied inside-out. The last wrap call produces the outermost boundary.\n // Order: specific status → category → error.tsx (outermost)\n\n if (segment.statusFiles) {\n // Wrap with specific status files (innermost — highest priority at runtime)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file);\n // mod.default is `unknown` — narrow to a component reference.\n // `isValidElementType` accepts memo/forwardRef objects in addition to\n // bare functions; non-component values fall through.\n const Component = isValidElementType(mod.default) ? mod.default : null;\n if (Component) {\n const boundaryProps: ErrorBoundaryProps = isMdxFile(file)\n ? {\n fallbackElement: createElement(Component, { status }),\n status,\n children: element,\n }\n : {\n fallbackComponent: Component,\n status,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n }\n }\n\n // Wrap with category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file);\n const Component = isValidElementType(mod.default) ? mod.default : null;\n if (Component) {\n const categoryStatus = key === '4xx' ? 400 : 500;\n const boundaryProps: ErrorBoundaryProps = isMdxFile(file)\n ? {\n fallbackElement: createElement(Component, {}),\n status: categoryStatus,\n children: element,\n }\n : {\n fallbackComponent: Component,\n status: categoryStatus,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n }\n }\n\n // Wrap with error.tsx (outermost — catches anything not matched by status files)\n // Note: error.tsx/error.mdx receives { error, digest, reset } props.\n // MDX error files are pre-rendered without those props (they're static content).\n if (segment.error) {\n const errorModule = await loadModule(segment.error);\n const ErrorComponent = isValidElementType(errorModule.default) ? errorModule.default : null;\n if (ErrorComponent) {\n const boundaryProps: ErrorBoundaryProps = isMdxFile(segment.error)\n ? {\n fallbackElement: createElement(ErrorComponent, {}),\n children: element,\n }\n : {\n fallbackComponent: ErrorComponent,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n\n return element;\n}\n","/**\n * CSRF protection — Origin header validation.\n *\n * Auto-derived from the Host header for single-origin deployments.\n * Configurable via allowedOrigins for multi-origin setups.\n * Disable with csrf: false (not recommended outside local dev).\n *\n * See design/08-forms-and-actions.md §\"CSRF Protection\"\n * See design/13-security.md §\"Security Testing Checklist\" #6\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface CsrfConfig {\n /** Explicit list of allowed origins. Replaces Host-based auto-derivation. */\n allowedOrigins?: string[];\n /** Set to false to disable CSRF validation entirely. */\n csrf?: boolean;\n}\n\nexport type CsrfResult = { ok: true } | { ok: false; status: 403 };\n\n// ─── Constants ────────────────────────────────────────────────────────────\n\n/** HTTP methods that are considered safe (no mutation). */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n// ─── Implementation ───────────────────────────────────────────────────────\n\n/**\n * Derive the request's scheme from trusted sources.\n *\n * Priority:\n * 1. X-Forwarded-Proto (set by reverse proxies / load balancers)\n * 2. Request URL protocol\n *\n * Falls back to 'https' (fail closed) if neither is available.\n */\nfunction deriveRequestScheme(req: Request): string {\n const forwarded = req.headers.get('X-Forwarded-Proto');\n if (forwarded) {\n const proto = forwarded.split(',')[0].trim().toLowerCase();\n if (proto === 'http' || proto === 'https') return proto;\n }\n\n try {\n return new URL(req.url).protocol.replace(':', '');\n } catch {\n return 'https';\n }\n}\n\n/**\n * Validate the Origin header against the request's full origin\n * (scheme + host + port).\n *\n * For mutation methods (POST, PUT, PATCH, DELETE):\n * - If `csrf: false`, skip validation.\n * - If `allowedOrigins` is set, Origin must match one exactly (no wildcards).\n * - Otherwise, Origin must match the derived request origin.\n *\n * Safe methods (GET, HEAD, OPTIONS) always pass.\n */\nexport function validateCsrf(req: Request, config: CsrfConfig): CsrfResult {\n // Safe methods don't need CSRF protection\n if (SAFE_METHODS.has(req.method)) {\n return { ok: true };\n }\n\n // Explicitly disabled\n if (config.csrf === false) {\n return { ok: true };\n }\n\n const origin = req.headers.get('Origin');\n\n // No Origin header on a mutation → reject\n if (!origin) {\n return { ok: false, status: 403 };\n }\n\n // If allowedOrigins is configured, use that instead of Host-based derivation\n if (config.allowedOrigins) {\n const allowed = config.allowedOrigins.includes(origin);\n return allowed ? { ok: true } : { ok: false, status: 403 };\n }\n\n // Auto-derive from Host header\n const host = req.headers.get('Host');\n if (!host) {\n return { ok: false, status: 403 };\n }\n\n // Compare full origins (scheme + host + port) using URL.origin for\n // canonical port normalization (e.g. https://x:443 → https://x).\n let originOrigin: string;\n try {\n originOrigin = new URL(origin).origin;\n } catch {\n return { ok: false, status: 403 };\n }\n\n const scheme = deriveRequestScheme(req);\n let expectedOrigin: string;\n try {\n expectedOrigin = new URL(`${scheme}://${host}`).origin;\n } catch {\n return { ok: false, status: 403 };\n }\n\n return originOrigin === expectedOrigin ? { ok: true } : { ok: false, status: 403 };\n}\n","/**\n * Request body size limits — returns 413 when exceeded.\n * See design/08-forms-and-actions.md §\"FormData Limits\"\n */\n\nexport interface BodyLimitsConfig {\n limits?: {\n actionBodySize?: string;\n uploadBodySize?: string;\n maxFields?: number;\n };\n}\n\nexport type BodyLimitResult = { ok: true } | { ok: false; status: 411 | 413 };\n\nexport type BodyKind = 'action' | 'upload';\n\nconst KB = 1024;\nconst MB = 1024 * KB;\nconst GB = 1024 * MB;\n\nexport const DEFAULT_LIMITS = {\n actionBodySize: 1 * MB,\n uploadBodySize: 10 * MB,\n maxFields: 100,\n} as const;\n\nconst SIZE_PATTERN = /^(\\d+(?:\\.\\d+)?)\\s*(kb|mb|gb)?$/i;\n\n/** Parse a human-readable size string (\"1mb\", \"512kb\", \"1024\") into bytes. */\nexport function parseBodySize(size: string): number {\n const match = SIZE_PATTERN.exec(size.trim());\n if (!match) {\n throw new Error(\n `Invalid body size format: \"${size}\". Expected format like \"1mb\", \"512kb\", or \"1024\".`\n );\n }\n\n const value = Number.parseFloat(match[1]);\n const unit = (match[2] ?? '').toLowerCase();\n\n switch (unit) {\n case 'kb':\n return Math.floor(value * KB);\n case 'mb':\n return Math.floor(value * MB);\n case 'gb':\n return Math.floor(value * GB);\n case '':\n return Math.floor(value);\n default:\n throw new Error(`Unknown size unit: \"${unit}\"`);\n }\n}\n\n/** Strict integer pattern: one or more digits, nothing else. */\nconst STRICT_INTEGER_RE = /^\\d+$/;\n\n/** Check whether a request body exceeds the configured size limit (stateless, no ALS). */\nexport function enforceBodyLimits(\n req: Request,\n kind: BodyKind,\n config: BodyLimitsConfig\n): BodyLimitResult {\n const contentLength = req.headers.get('Content-Length');\n if (!contentLength) {\n // Reject requests without Content-Length — prevents body limit bypass via\n // chunked transfer-encoding. Browsers always send Content-Length for form POSTs.\n return { ok: false, status: 411 };\n }\n\n // Reject malformed values: multiple values (\"100, 999999\"), negative numbers,\n // floats (\"100.5\"), or non-numeric strings. parseInt would silently parse\n // the first number from \"100, 999999\" as 100, letting the real body through.\n const trimmed = contentLength.trim();\n if (!STRICT_INTEGER_RE.test(trimmed)) {\n return { ok: false, status: 411 };\n }\n\n const bodySize = Number.parseInt(trimmed, 10);\n\n const limit = resolveLimit(kind, config);\n return bodySize <= limit ? { ok: true } : { ok: false, status: 413 };\n}\n\n/** Check whether a FormData payload exceeds the configured field count limit. */\nexport function enforceFieldLimit(formData: FormData, config: BodyLimitsConfig): BodyLimitResult {\n const maxFields = config.limits?.maxFields ?? DEFAULT_LIMITS.maxFields;\n // Count unique keys — FormData.keys() yields duplicates for multi-value fields,\n // so we use a Set to count distinct field names.\n const fieldCount = new Set(formData.keys()).size;\n return fieldCount <= maxFields ? { ok: true } : { ok: false, status: 413 };\n}\n\n/**\n * Resolve the byte limit for a given body kind, using config overrides or defaults.\n */\nfunction resolveLimit(kind: BodyKind, config: BodyLimitsConfig): number {\n const userLimits = config.limits;\n\n if (kind === 'action') {\n return userLimits?.actionBodySize\n ? parseBodySize(userLimits.actionBodySize)\n : DEFAULT_LIMITS.actionBodySize;\n }\n\n return userLimits?.uploadBodySize\n ? parseBodySize(userLimits.uploadBodySize)\n : DEFAULT_LIMITS.uploadBodySize;\n}\n","/**\n * Route handler for route.ts API endpoints.\n *\n * route.ts files export named HTTP method handlers (GET, POST, etc.).\n * They share the same pipeline (proxy → match → middleware → access → handler)\n * but don't render React trees.\n *\n * See design/07-routing.md §\"route.ts — API Endpoints\"\n */\n\nimport type { RouteContext } from './types.js';\nimport { logRouteError } from './logger.js';\nimport { DenySignal, RedirectSignal } from './primitives.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** HTTP methods that route.ts can export as named handlers. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n/** A single route handler function — one-arg signature. */\nexport type RouteHandler = (ctx: RouteContext) => Response | Promise<Response>;\n\n/** A route.ts module — named exports for each supported HTTP method. */\nexport type RouteModule = {\n [K in HttpMethod]?: RouteHandler;\n};\n\n/** All recognized HTTP method export names. */\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n// ─── Allowed Methods ─────────────────────────────────────────────────────\n\n/**\n * Resolve the full list of allowed methods for a route module.\n *\n * Includes:\n * - All explicitly exported methods\n * - HEAD (implicit when GET is exported)\n * - OPTIONS (always implicit)\n */\nexport function resolveAllowedMethods(mod: RouteModule): HttpMethod[] {\n const methods: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (method === 'HEAD' || method === 'OPTIONS') continue;\n if (mod[method]) {\n methods.push(method);\n }\n }\n\n // HEAD is implicit when GET is exported\n if (mod.GET && !mod.HEAD) {\n methods.push('HEAD');\n } else if (mod.HEAD) {\n methods.push('HEAD');\n }\n\n // OPTIONS is always implicit\n if (!mod.OPTIONS) {\n methods.push('OPTIONS');\n } else {\n methods.push('OPTIONS');\n }\n\n return methods;\n}\n\n// ─── Route Request Handler ───────────────────────────────────────────────\n\n/**\n * Handle an incoming request against a route.ts module.\n *\n * Dispatches to the named method handler, auto-generates 405/OPTIONS,\n * and merges response headers from ctx.headers.\n */\nexport async function handleRouteRequest(mod: RouteModule, ctx: RouteContext): Promise<Response> {\n const method = ctx.req.method.toUpperCase() as HttpMethod;\n const allowed = resolveAllowedMethods(mod);\n const allowHeader = allowed.join(', ');\n\n // Auto OPTIONS — 204 with Allow header\n if (method === 'OPTIONS') {\n if (mod.OPTIONS) {\n return runHandler(mod.OPTIONS, ctx);\n }\n return new Response(null, {\n status: 204,\n headers: { Allow: allowHeader },\n });\n }\n\n // HEAD fallback — run GET, strip body\n if (method === 'HEAD') {\n if (mod.HEAD) {\n return runHandler(mod.HEAD, ctx);\n }\n if (mod.GET) {\n const res = await runHandler(mod.GET, ctx);\n // Return headers + status but no body\n return new Response(null, {\n status: res.status,\n headers: res.headers,\n });\n }\n }\n\n // Dispatch to the named handler\n const handler = mod[method];\n if (!handler) {\n return new Response(null, {\n status: 405,\n headers: { Allow: allowHeader },\n });\n }\n\n return runHandler(handler, ctx);\n}\n\n/**\n * Run a handler, merge ctx.headers into the response, and catch errors.\n */\nasync function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Response> {\n try {\n const res = await handler(ctx);\n return mergeResponseHeaders(res, ctx.headers);\n } catch (error) {\n // Control-flow signals — propagate to the API route dispatcher so deny()\n // gets the JSON status-file chain and redirect() gets a proper 3xx with\n // Location. Not errors; don't log them.\n if (error instanceof DenySignal || error instanceof RedirectSignal) {\n throw error;\n }\n logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });\n return new Response(null, { status: 500 });\n }\n}\n\n/**\n * Merge response headers from ctx.headers into the handler's response.\n * ctx.headers (set by middleware or the handler) are applied to the final response.\n * Handler-set headers take precedence over ctx.headers.\n */\nfunction mergeResponseHeaders(res: Response, ctxHeaders: Headers): Response {\n // If no ctx headers to merge, return as-is\n let hasCtxHeaders = false;\n ctxHeaders.forEach(() => {\n hasCtxHeaders = true;\n });\n if (!hasCtxHeaders) return res;\n\n // Merge: ctx.headers first, then handler response headers override.\n // Set-Cookie needs special handling: Headers.set() replaces all values\n // for a key, but each Set-Cookie must be its own header per RFC 6265 §4.1.\n // Use append for Set-Cookie, set for everything else.\n const merged = new Headers();\n ctxHeaders.forEach((value, key) => {\n if (key.toLowerCase() === 'set-cookie') {\n merged.append(key, value);\n } else {\n merged.set(key, value);\n }\n });\n // Response Set-Cookie headers: use getSetCookie() to preserve individual\n // cookies (forEach joins them with \", \" into one entry).\n const resCookies = res.headers.getSetCookie();\n for (const cookie of resCookies) {\n merged.append('Set-Cookie', cookie);\n }\n res.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'set-cookie') {\n merged.set(key, value);\n }\n });\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: merged,\n });\n}\n","/**\n * Render timeout utilities for SSR streaming pipeline.\n *\n * Provides a RenderTimeoutError class and a helper to create\n * timeout-guarded AbortSignals. Used to defend against hung RSC\n * streams and infinite SSR renders.\n *\n * Design doc: 02-rendering-pipeline.md §\"Streaming Constraints\"\n */\n\n/**\n * Error thrown when an SSR render or RSC stream read exceeds the\n * configured timeout. Callers can check `instanceof RenderTimeoutError`\n * to distinguish timeout from other errors and return a 504 or close\n * the connection cleanly.\n */\nexport class RenderTimeoutError extends Error {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number, context?: string) {\n const message = context\n ? `Render timeout after ${timeoutMs}ms: ${context}`\n : `Render timeout after ${timeoutMs}ms`;\n super(message);\n this.name = 'RenderTimeoutError';\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Result of createRenderTimeout — an AbortSignal that fires after\n * the given duration, plus a cancel function to clear the timer\n * when the render completes normally.\n */\nexport interface RenderTimeout {\n /** AbortSignal that aborts after timeoutMs. */\n signal: AbortSignal;\n /** Cancel the timeout timer. Call this when the render completes. */\n cancel: () => void;\n}\n\n/**\n * Create a render timeout that aborts after the given duration.\n *\n * Returns an AbortSignal and a cancel function. The signal fires\n * with a RenderTimeoutError as the abort reason after `timeoutMs`.\n * Call `cancel()` when the render completes to prevent the timeout\n * from firing.\n *\n * If an existing `parentSignal` is provided, the returned signal\n * aborts when either the parent signal or the timeout fires —\n * whichever comes first.\n */\nexport function createRenderTimeout(timeoutMs: number, parentSignal?: AbortSignal): RenderTimeout {\n const controller = new AbortController();\n const reason = new RenderTimeoutError(timeoutMs, 'RSC stream read timed out');\n\n const timer = setTimeout(() => controller.abort(reason), timeoutMs);\n\n let onParentAbort: (() => void) | null = null;\n\n if (parentSignal) {\n if (parentSignal.aborted) {\n clearTimeout(timer);\n controller.abort(parentSignal.reason);\n } else {\n onParentAbort = () => {\n clearTimeout(timer);\n controller.abort(parentSignal.reason);\n };\n parentSignal.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (onParentAbort && parentSignal) {\n parentSignal.removeEventListener('abort', onParentAbort);\n onParentAbort = null;\n }\n },\n };\n}\n\n/**\n * Race a promise against a timeout. Rejects with RenderTimeoutError\n * if the promise does not resolve within `timeoutMs`.\n *\n * Used to guard individual `rscReader.read()` calls inside pullLoop.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n context?: string\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n const timeoutPromise = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(new RenderTimeoutError(timeoutMs, context));\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timer!);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal.js","names":[],"sources":["../../src/server/server-timing.ts","../../src/server/instrumentation.ts","../../src/server/pipeline-helpers.ts","../../src/server/proxy.ts","../../src/server/middleware-runner.ts","../../src/server/metadata-social.ts","../../src/server/metadata-platform.ts","../../src/server/metadata-render.ts","../../src/server/metadata.ts","../../src/client/error-reconstituter.tsx","../../src/server/safe-load.ts","../../src/server/status-code-resolver.ts","../../src/server/deny-boundary.ts","../../src/server/access-gate.tsx","../../src/server/param-coercion.ts","../../src/client/segment-update-context.ts","../../src/client/segment-outlet.tsx","../../src/server/route-element-builder.ts","../../src/server/version-skew.ts","../../src/server/pipeline-metadata.ts","../../src/server/pipeline-interception.ts","../../src/server/pipeline-outcome.ts","../../src/server/pipeline-phases.ts","../../src/server/pipeline.ts","../../src/server/build-manifest.ts","../../src/server/early-hints.ts","../../src/server/early-hints-sender.ts","../../src/server/tree-builder.ts","../../src/server/csrf.ts","../../src/server/body-limits.ts","../../src/server/route-handler.ts","../../src/server/render-timeout.ts"],"sourcesContent":["/**\n * Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.\n *\n * Collects timing entries per request using ALS. Each pipeline phase\n * (proxy, middleware, render, SSR, access, fetch) records an entry.\n * Before response flush, entries are formatted into a Server-Timing header.\n *\n * Only active in dev mode — zero overhead in production.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing\n * Task: LOCAL-290\n */\n\nimport { timingAls } from './als-registry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface TimingEntry {\n /** Metric name (alphanumeric + hyphens, no spaces). */\n name: string;\n /** Duration in milliseconds. */\n dur: number;\n /** Human-readable description (shown in DevTools). */\n desc?: string;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Run a callback with a per-request timing collector.\n * Must be called at the top of the request pipeline (wraps the full request).\n */\nexport function runWithTimingCollector<T>(fn: () => T): T {\n return timingAls.run({ entries: [] }, fn);\n}\n\n/**\n * Record a timing entry for the current request.\n * No-ops if called outside a timing collector (e.g. in production).\n */\nexport function recordTiming(entry: TimingEntry): void {\n const store = timingAls.getStore();\n if (!store) return;\n store.entries.push(entry);\n}\n\n/**\n * Run a function and automatically record its duration as a timing entry.\n * Returns the function's result. No-ops the recording if outside a collector.\n */\nexport async function withTiming<T>(\n name: string,\n desc: string | undefined,\n fn: () => T | Promise<T>\n): Promise<T> {\n const store = timingAls.getStore();\n if (!store) return fn();\n\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const dur = Math.round(performance.now() - start);\n store.entries.push({ name, dur, desc });\n }\n}\n\n/**\n * Get the Server-Timing header value for the current request.\n * Returns null if no entries exist or outside a collector.\n *\n * Format: `name;dur=123;desc=\"description\", name2;dur=456`\n * See RFC 6797 / Server-Timing spec for format details.\n */\nexport function getServerTimingHeader(): string | null {\n const store = timingAls.getStore();\n if (!store || store.entries.length === 0) return null;\n\n // Deduplicate names — if a name appears multiple times, suffix with index\n const nameCounts = new Map<string, number>();\n const entries = store.entries.map((entry) => {\n const count = nameCounts.get(entry.name) ?? 0;\n nameCounts.set(entry.name, count + 1);\n const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;\n return { ...entry, name: uniqueName };\n });\n\n const parts = entries.map((entry) => {\n let part = `${entry.name};dur=${entry.dur}`;\n if (entry.desc) {\n // Escape quotes in desc per Server-Timing spec\n const safeDesc = entry.desc.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n part += `;desc=\"${safeDesc}\"`;\n }\n return part;\n });\n\n // Respect header size limits — browsers typically handle up to 8KB headers.\n // Truncate if the header exceeds 4KB to leave room for other headers.\n const MAX_HEADER_SIZE = 4096;\n let result = '';\n for (let i = 0; i < parts.length; i++) {\n const candidate = result ? `${result}, ${parts[i]}` : parts[i]!;\n if (candidate.length > MAX_HEADER_SIZE) break;\n result = candidate;\n }\n\n return result || null;\n}\n\n/**\n * Sanitize a URL for use in Server-Timing desc.\n * Strips query params and truncates long paths to avoid information leakage.\n */\nexport function sanitizeUrlForTiming(url: string): string {\n try {\n const parsed = new URL(url);\n const origin = parsed.host;\n let path = parsed.pathname;\n // Truncate long paths\n if (path.length > 50) {\n path = path.slice(0, 47) + '...';\n }\n return `${origin}${path}`;\n } catch {\n // Not a valid URL — truncate raw string\n if (url.length > 60) {\n return url.slice(0, 57) + '...';\n }\n return url;\n }\n}\n","/**\n * Instrumentation — loads and runs the user's instrumentation.ts file.\n *\n * instrumentation.ts is a file convention at the project root that exports:\n * - register() — called once at server startup, before the first request\n * - onRequestError() — called for every unhandled server error\n * - logger — any object with info/warn/error/debug methods\n *\n * See design/17-logging.md §\"instrumentation.ts — The Entry Point\"\n */\n\nimport { setLogger, type TimberLogger } from './logger.js';\n\n// ─── Instrumentation Types ────────────────────────────────────────────────\n\nexport type InstrumentationOnRequestError = (\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n) => void | Promise<void>;\n\nexport interface InstrumentationRequestInfo {\n /** HTTP method: 'GET', 'POST', etc. */\n method: string;\n /** Request path: '/dashboard/projects/123' */\n path: string;\n /** Request headers as a plain object. */\n headers: Record<string, string>;\n}\n\nexport interface InstrumentationErrorContext {\n /** Which pipeline phase the error occurred in. */\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route';\n /** The route pattern: '/dashboard/projects/[id]' */\n routePath: string;\n /** Type of route that was matched. */\n routeType: 'page' | 'route' | 'action';\n /** Always set — OTEL trace ID or UUID fallback. */\n traceId: string;\n}\n\n// ─── Instrumentation Module Shape ─────────────────────────────────────────\n\ninterface InstrumentationModule {\n register?: () => void | Promise<void>;\n onRequestError?: InstrumentationOnRequestError;\n logger?: TimberLogger;\n}\n\n// ─── State ────────────────────────────────────────────────────────────────\n//\n// Intentional per-app singletons (not per-request). Instrumentation loads\n// once at server startup and persists for the lifetime of the process/isolate.\n// These must NOT be migrated to ALS — they are correctly scoped to the app.\n\nlet _initialized = false;\nlet _onRequestError: InstrumentationOnRequestError | null = null;\n\n/**\n * Load and initialize the user's instrumentation.ts module.\n *\n * - Awaits register() before returning (server blocks on this).\n * - Picks up the logger export and wires it into the framework logger.\n * - Stores onRequestError for later invocation.\n *\n * @param loader - Function that dynamically imports the user's instrumentation module.\n * Returns null if no instrumentation.ts exists.\n */\nexport async function loadInstrumentation(\n loader: () => Promise<InstrumentationModule | null>\n): Promise<void> {\n if (_initialized) return;\n _initialized = true;\n\n let mod: InstrumentationModule | null;\n try {\n mod = await loader();\n } catch (error) {\n console.error('[timber] Failed to load instrumentation.ts:', error);\n return;\n }\n\n if (!mod) return;\n\n // Wire up the logger export\n if (mod.logger && typeof mod.logger.info === 'function') {\n setLogger(mod.logger);\n }\n\n // Store onRequestError for later\n if (typeof mod.onRequestError === 'function') {\n _onRequestError = mod.onRequestError;\n }\n\n // Await register() — server does not accept requests until this resolves\n if (typeof mod.register === 'function') {\n try {\n await mod.register();\n } catch (error) {\n console.error('[timber] instrumentation.ts register() threw:', error);\n throw error;\n }\n }\n}\n\n/**\n * Call the user's onRequestError hook. Catches and logs any errors thrown\n * by the hook itself — it must not affect the response.\n */\nexport async function callOnRequestError(\n error: unknown,\n request: InstrumentationRequestInfo,\n context: InstrumentationErrorContext\n): Promise<void> {\n if (!_onRequestError) return;\n try {\n await _onRequestError(error, request, context);\n } catch (hookError) {\n console.error('[timber] onRequestError hook threw:', hookError);\n }\n}\n\n/**\n * Check if onRequestError is registered.\n */\nexport function hasOnRequestError(): boolean {\n return _onRequestError !== null;\n}\n\n/**\n * Reset instrumentation state. Test-only.\n */\nexport function resetInstrumentation(): void {\n _initialized = false;\n _onRequestError = null;\n}\n","/**\n * Pipeline helpers — small utility functions used by `pipeline.ts` and\n * `pipeline-phases.ts`. Lifted out of `pipeline.ts` to keep that file\n * focused on the request handler entry point.\n *\n * Each helper is intentionally a free function with no closure capture, so\n * it can be unit-tested in isolation.\n *\n * See design/07-routing.md §\"Request Lifecycle\".\n */\n\nimport type { ProxyExport } from './proxy.js';\nimport { getSetCookieHeaders } from './cookie-context.js';\nimport { callOnRequestError } from './instrumentation.js';\nimport { getTraceId } from './tracing.js';\nimport { RedirectSignal } from './primitives.js';\nimport type { ProxyConfig } from './pipeline.js';\n\n// ─── Prototype-Pollution-Safe Sanitizer ────────────────────────────────────\n\n/**\n * Only __proto__ needs stripping — it has a language-level setter that\n * changes the prototype chain of spread copies. constructor and prototype\n * are harmless own properties on null-prototype objects.\n */\nconst DANGEROUS_KEYS = new Set(['__proto__']);\n\n/**\n * Deep-walk a value returned by a segment param codec, producing a\n * sanitized copy where every plain object is null-prototype and\n * dangerous keys (__proto__, constructor, prototype) are stripped at\n * every depth.\n *\n * Non-plain objects (Date, Map, class instances, etc.) are returned\n * as-is — they cannot be poisoned by `{...x}` spread and may carry\n * author-intended prototype methods.\n *\n * Arrays are walked element-wise.\n *\n * Performance: URL params are bounded by URL length (~8 KB). Realistic\n * trees are <1 KB. The recursive walk is sub-microsecond.\n *\n * See TIM-655, TIM-855, TIM-873, design/13-security.md\n */\nexport function sanitizeParamValue(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n\n if (Array.isArray(value)) {\n return value.map(sanitizeParamValue);\n }\n\n // Only walk plain objects — anything with a custom prototype (Date, Map,\n // class instances) is left untouched.\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n\n const out: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(value as Record<string, unknown>)) {\n if (!DANGEROUS_KEYS.has(key)) {\n out[key] = sanitizeParamValue((value as Record<string, unknown>)[key]);\n }\n }\n return out;\n}\n\n// ─── Proxy Resolver ────────────────────────────────────────────────────────\n\n/**\n * Resolver closure produced once at pipeline construction. The lazy variant\n * still calls `loader()` per-request (HMR relies on re-importing), but the\n * choice of which branch to take is made once, not on every request.\n */\nexport type ProxyResolver = () => ProxyExport | Promise<ProxyExport>;\n\n/**\n * Build a proxy resolver closure from the declared source. Called exactly\n * once at `createPipeline` setup time, so the hot path sees only the branch\n * that corresponds to this pipeline's configured variant.\n *\n * Returns `null` when the app has no proxy.ts — the hot path short-circuits\n * around `runProxyPhase` entirely in that case.\n *\n * Accepts the sugar form (a bare `ProxyExport` — function or function array)\n * and normalises it to the static variant. Functions and arrays are\n * structurally distinct from the tagged `{ kind: 'lazy', loader }` object,\n * so discrimination is unambiguous.\n */\nexport function makeProxyResolver(\n proxy: ProxyConfig | ProxyExport | undefined\n): ProxyResolver | null {\n if (proxy === undefined) return null;\n // Sugar: a bare ProxyExport (function or function array) — treat as static.\n if (typeof proxy === 'function' || Array.isArray(proxy)) {\n const exp = proxy;\n return () => exp;\n }\n if (proxy.kind === 'static') {\n const exp = proxy.export;\n return () => exp;\n }\n const loader = proxy.loader;\n return async () => (await loader()).default;\n}\n\n// ─── Cookie / Header Helpers ───────────────────────────────────────────────\n\n/**\n * Apply all Set-Cookie headers from the cookie jar to a Headers object.\n * Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.\n */\nexport function applyCookieJar(headers: Headers): void {\n for (const value of getSetCookieHeaders()) {\n headers.append('Set-Cookie', value);\n }\n}\n\n/**\n * Merge framework-managed response headers onto a terminal response without\n * overwriting headers the terminal response already set itself.\n */\nexport function mergeMissingHeaders(target: Headers, source: Headers): void {\n const existingKeys = new Set([...target.keys()].map((key) => key.toLowerCase()));\n for (const [key, value] of source.entries()) {\n if (!existingKeys.has(key.toLowerCase())) {\n target.append(key, value);\n }\n }\n}\n\n// ─── Mutable Response Cloning ──────────────────────────────────────────────\n\n/**\n * Clone a Response into a fresh one whose header bag is guaranteed mutable.\n *\n * `Response.redirect()` and some platform-level passthrough responses (notably\n * on Cloudflare Workers) return objects with frozen header bags. Calling\n * `.set()` or `.append()` on them throws `TypeError: immutable`, which the\n * pipeline can hit when it appends Set-Cookie or Server-Timing entries.\n *\n * The pipeline calls this at the producer sites where user-controlled\n * responses enter the framework — `outcomeToResponse` for all phase outcomes,\n * and `handleRequest` for metadata-route and auto-sitemap user handlers — so\n * downstream code can write headers without runtime feature-detection.\n *\n * The clone is unconditional. This is a deliberate trade: we avoid a\n * try/catch + thrown `TypeError` on every request (the previous probe-based\n * approach paid that cost on the hot path) and accept one cheap Response\n * rewrap at the framework boundary instead.\n */\nexport function cloneWithMutableHeaders(response: Response): Response {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n}\n\n// ─── Redirect Builder ──────────────────────────────────────────────────────\n\n/**\n * Build a redirect Response from a RedirectSignal.\n *\n * For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect\n * so the client router can perform a soft SPA redirect. A raw 302 would be\n * turned into an opaque redirect by fetch({redirect:'manual'}), crashing\n * createFromFetch. See design/19-client-navigation.md.\n */\nexport function buildRedirectResponse(\n signal: RedirectSignal,\n req: Request,\n headers: Headers\n): Response {\n const isRsc = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRsc) {\n headers.set('X-Timber-Redirect', signal.location);\n return new Response(null, { status: 204, headers });\n }\n headers.set('Location', signal.location);\n return new Response(null, { status: signal.status, headers });\n}\n\n// ─── Instrumentation ───────────────────────────────────────────────────────\n\n/**\n * Fire the user's onRequestError hook with request context.\n * Extracts request info from the Request object and calls the instrumentation hook.\n */\nexport async function fireOnRequestError(\n error: unknown,\n req: Request,\n phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'\n): Promise<void> {\n const url = new URL(req.url);\n const headersObj: Record<string, string> = {};\n req.headers.forEach((v, k) => {\n headersObj[k] = v;\n });\n\n await callOnRequestError(\n error,\n { method: req.method, path: url.pathname, headers: headersObj },\n { phase, routePath: url.pathname, routeType: 'page', traceId: getTraceId() }\n );\n}\n","/**\n * Proxy runner — executes app/proxy.ts before route matching.\n *\n * Supports two forms:\n * - Function: (req, next) => Promise<Response>\n * - Array: middleware functions composed left-to-right\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\"\n */\n\n/** Signature for a single proxy middleware function. */\nexport type ProxyFn = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** The proxy.ts default export — either a function or an array of functions. */\nexport type ProxyExport = ProxyFn | ProxyFn[];\n\n/**\n * Run the proxy pipeline.\n *\n * @param proxyExport - The default export from proxy.ts (function or array)\n * @param req - The incoming request\n * @param next - The continuation that proceeds to route matching and rendering\n * @returns The final response\n */\nexport async function runProxy(\n proxyExport: ProxyExport,\n req: Request,\n next: () => Promise<Response>\n): Promise<Response> {\n const fns = Array.isArray(proxyExport) ? proxyExport : [proxyExport];\n\n // Compose left-to-right: first item's next() calls the second, etc.\n // The last item's next() calls the original `next` (route matching + render).\n let i = fns.length;\n let composed = next;\n while (i--) {\n const fn = fns[i]!;\n const downstream = composed;\n composed = () => Promise.resolve(fn(req, downstream));\n }\n\n return composed();\n}\n","/**\n * Middleware runner — executes a route's middleware.ts chain before rendering.\n *\n * All middleware.ts files in the segment chain run, root to leaf (top-down).\n * The first middleware that returns a Response short-circuits the chain.\n * There is no next() — each middleware is independent.\n *\n * See design/07-routing.md §\"middleware.ts\"\n */\n\nimport type { MiddlewareContext } from './types.js';\n\n/** Signature of a middleware.ts default export. */\nexport type MiddlewareFn = (ctx: MiddlewareContext) => Response | void | Promise<Response | void>;\n\n/**\n * Run a route's middleware function.\n *\n * @param middlewareFn - The default export from the route's middleware.ts\n * @param ctx - The middleware context (req, params, headers, requestHeaders, searchParams)\n * @returns A Response if middleware short-circuited, or undefined to continue\n */\nexport async function runMiddleware(\n middlewareFn: MiddlewareFn,\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n const result = await middlewareFn(ctx);\n if (result instanceof Response) {\n return result;\n }\n return undefined;\n}\n\n/**\n * Run all middleware functions in the segment chain, root to leaf.\n *\n * Execution is top-down: root middleware runs first, leaf middleware runs last.\n * All middleware share the same MiddlewareContext — a parent that sets\n * ctx.requestHeaders makes it visible to child middleware and downstream components.\n *\n * Short-circuits on the first middleware that returns a Response.\n * Remaining middleware in the chain do not execute.\n *\n * @param chain - Middleware functions ordered root-to-leaf\n * @param ctx - Shared middleware context\n * @returns A Response if any middleware short-circuited, or undefined to continue\n */\nexport async function runMiddlewareChain(\n chain: MiddlewareFn[],\n ctx: MiddlewareContext\n): Promise<Response | undefined> {\n for (const fn of chain) {\n const result = await fn(ctx);\n if (result instanceof Response) {\n return result;\n }\n }\n return undefined;\n}\n\n// ─── Per-Request Middleware Bypass ─────────────────────────────────────────\n\n/**\n * Per-request marker for synthetic re-render requests that should NOT\n * re-execute `middleware.ts`. The action-dispatch wrapper runs middleware\n * once on the inbound action POST; when validation fails on the no-JS\n * path, it builds a synthetic GET that flows through the normal pipeline\n * to render the page with `getFormFlash()` data. Without this marker, the\n * pipeline would run middleware a second time on that synthetic GET.\n *\n * The set is keyed by the synthetic Request object itself, so the entry\n * lives exactly as long as the request and is garbage-collected with it.\n * Cannot be set or detected by user code — there is no header, no URL\n * parameter, nothing on the wire that an attacker could spoof.\n *\n * See TIM-871.\n *\n * @internal — framework use only.\n */\nconst middlewareBypassRequests = new WeakSet<Request>();\n\n/**\n * Mark a request so the pipeline skips its middleware phase.\n *\n * Used by `wrap-action-dispatch.ts` for the no-JS form-rerender path.\n *\n * @internal\n */\nexport function markRequestBypassMiddleware(req: Request): void {\n middlewareBypassRequests.add(req);\n}\n\n/**\n * Check whether a request was marked to bypass middleware.\n *\n * Called by `handleRequest` in pipeline-phases.ts before invoking the\n * middleware phase. Returns false for any request not explicitly marked.\n *\n * @internal\n */\nexport function shouldBypassMiddleware(req: Request): boolean {\n return middlewareBypassRequests.has(req);\n}\n","/**\n * Social metadata rendering — Open Graph and Twitter Card meta tags.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render Open Graph metadata into head element descriptors.\n *\n * Handles og:title, og:description, og:image (with dimensions/alt),\n * og:video, og:audio, og:article:author, and other OG properties.\n */\nexport function renderOpenGraph(\n og: NonNullable<Metadata['openGraph']>,\n elements: HeadElement[]\n): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['og:title', og.title],\n ['og:description', og.description],\n ['og:url', og.url],\n ['og:site_name', og.siteName],\n ['og:locale', og.locale],\n ['og:type', og.type],\n ['og:article:published_time', og.publishedTime],\n ['og:article:modified_time', og.modifiedTime],\n ];\n\n for (const [property, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { property, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (og.images) {\n if (typeof og.images === 'string') {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: og.images } });\n } else {\n const imgList = Array.isArray(og.images) ? og.images : [og.images];\n for (const img of imgList) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image', content: img.url } });\n if (img.width) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:width', content: String(img.width) },\n });\n }\n if (img.height) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:image:height', content: String(img.height) },\n });\n }\n if (img.alt) {\n elements.push({ tag: 'meta', attrs: { property: 'og:image:alt', content: img.alt } });\n }\n }\n }\n }\n\n // Videos\n if (og.videos) {\n for (const video of og.videos) {\n elements.push({ tag: 'meta', attrs: { property: 'og:video', content: video.url } });\n }\n }\n\n // Audio\n if (og.audio) {\n for (const audio of og.audio) {\n elements.push({ tag: 'meta', attrs: { property: 'og:audio', content: audio.url } });\n }\n }\n\n // Authors\n if (og.authors) {\n for (const author of og.authors) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'og:article:author', content: author },\n });\n }\n }\n}\n\n/**\n * Render Twitter Card metadata into head element descriptors.\n *\n * Handles twitter:card, twitter:site, twitter:title, twitter:image,\n * twitter:player, and twitter:app (per-platform name/id/url).\n */\nexport function renderTwitter(tw: NonNullable<Metadata['twitter']>, elements: HeadElement[]): void {\n const simpleProps: Array<[string, string | undefined]> = [\n ['twitter:card', tw.card],\n ['twitter:site', tw.site],\n ['twitter:site:id', tw.siteId],\n ['twitter:title', tw.title],\n ['twitter:description', tw.description],\n ['twitter:creator', tw.creator],\n ['twitter:creator:id', tw.creatorId],\n ];\n\n for (const [name, content] of simpleProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Images — normalize single object to array for uniform handling\n if (tw.images) {\n if (typeof tw.images === 'string') {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: tw.images } });\n } else {\n const imgList = Array.isArray(tw.images) ? tw.images : [tw.images];\n for (const img of imgList) {\n const url = typeof img === 'string' ? img : img.url;\n elements.push({ tag: 'meta', attrs: { name: 'twitter:image', content: url } });\n }\n }\n }\n\n // Player card fields\n if (tw.players) {\n for (const player of tw.players) {\n elements.push({ tag: 'meta', attrs: { name: 'twitter:player', content: player.playerUrl } });\n if (player.width) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:width', content: String(player.width) },\n });\n }\n if (player.height) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:height', content: String(player.height) },\n });\n }\n if (player.streamUrl) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'twitter:player:stream', content: player.streamUrl },\n });\n }\n }\n }\n\n // App card fields — 3 platforms × 3 attributes (name, id, url)\n if (tw.app) {\n const platforms: Array<[keyof NonNullable<typeof tw.app.id>, string]> = [\n ['iPhone', 'iphone'],\n ['iPad', 'ipad'],\n ['googlePlay', 'googleplay'],\n ];\n\n // App name is shared across platforms but the spec uses per-platform names.\n // Emit for each platform that has an ID.\n if (tw.app.name) {\n for (const [key, tag] of platforms) {\n if (tw.app.id?.[key]) {\n elements.push({\n tag: 'meta',\n attrs: { name: `twitter:app:name:${tag}`, content: tw.app.name },\n });\n }\n }\n }\n\n for (const [key, tag] of platforms) {\n const id = tw.app.id?.[key];\n if (id) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:id:${tag}`, content: id } });\n }\n }\n\n for (const [key, tag] of platforms) {\n const url = tw.app.url?.[key];\n if (url) {\n elements.push({ tag: 'meta', attrs: { name: `twitter:app:url:${tag}`, content: url } });\n }\n }\n }\n}\n","/**\n * Platform-specific metadata rendering — icons, Apple Web App, App Links, iTunes.\n *\n * Extracted from metadata-render.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\n\n/**\n * Render icon link elements (favicon, shortcut, apple-touch-icon, custom).\n */\nexport function renderIcons(icons: NonNullable<Metadata['icons']>, elements: HeadElement[]): void {\n // Icon\n if (icons.icon) {\n if (typeof icons.icon === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'icon', href: icons.icon } });\n } else if (Array.isArray(icons.icon)) {\n for (const icon of icons.icon) {\n const attrs: Record<string, string> = { rel: 'icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Shortcut\n if (icons.shortcut) {\n const urls = Array.isArray(icons.shortcut) ? icons.shortcut : [icons.shortcut];\n for (const url of urls) {\n elements.push({ tag: 'link', attrs: { rel: 'shortcut icon', href: url } });\n }\n }\n\n // Apple\n if (icons.apple) {\n if (typeof icons.apple === 'string') {\n elements.push({ tag: 'link', attrs: { rel: 'apple-touch-icon', href: icons.apple } });\n } else if (Array.isArray(icons.apple)) {\n for (const icon of icons.apple) {\n const attrs: Record<string, string> = { rel: 'apple-touch-icon', href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n elements.push({ tag: 'link', attrs });\n }\n }\n }\n\n // Other\n if (icons.other) {\n for (const icon of icons.other) {\n const attrs: Record<string, string> = { rel: icon.rel, href: icon.url };\n if (icon.sizes) attrs.sizes = icon.sizes;\n if (icon.type) attrs.type = icon.type;\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render alternate link elements (canonical, hreflang, media, types).\n */\nexport function renderAlternates(\n alternates: NonNullable<Metadata['alternates']>,\n elements: HeadElement[]\n): void {\n if (alternates.canonical) {\n elements.push({ tag: 'link', attrs: { rel: 'canonical', href: alternates.canonical } });\n }\n\n if (alternates.languages) {\n for (const [lang, href] of Object.entries(alternates.languages)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', hreflang: lang, href },\n });\n }\n }\n\n if (alternates.media) {\n for (const [media, href] of Object.entries(alternates.media)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', media, href },\n });\n }\n }\n\n if (alternates.types) {\n for (const [type, href] of Object.entries(alternates.types)) {\n elements.push({\n tag: 'link',\n attrs: { rel: 'alternate', type, href },\n });\n }\n }\n}\n\n/**\n * Render site verification meta tags (Google, Yahoo, Yandex, custom).\n */\nexport function renderVerification(\n verification: NonNullable<Metadata['verification']>,\n elements: HeadElement[]\n): void {\n const verificationProps: Array<[string, string | undefined]> = [\n ['google-site-verification', verification.google],\n ['y_key', verification.yahoo],\n ['yandex-verification', verification.yandex],\n ];\n\n for (const [name, content] of verificationProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n if (verification.other) {\n for (const [name, value] of Object.entries(verification.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n}\n\n/**\n * Render Apple Web App meta tags and startup image links.\n */\nexport function renderAppleWebApp(\n appleWebApp: NonNullable<Metadata['appleWebApp']>,\n elements: HeadElement[]\n): void {\n if (appleWebApp.capable) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-capable', content: 'yes' },\n });\n }\n if (appleWebApp.title) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-mobile-web-app-title', content: appleWebApp.title },\n });\n }\n if (appleWebApp.statusBarStyle) {\n elements.push({\n tag: 'meta',\n attrs: {\n name: 'apple-mobile-web-app-status-bar-style',\n content: appleWebApp.statusBarStyle,\n },\n });\n }\n if (appleWebApp.startupImage) {\n const images = Array.isArray(appleWebApp.startupImage)\n ? appleWebApp.startupImage\n : [{ url: appleWebApp.startupImage }];\n for (const img of images) {\n const url = typeof img === 'string' ? img : img.url;\n const attrs: Record<string, string> = { rel: 'apple-touch-startup-image', href: url };\n if (typeof img === 'object' && img.media) {\n attrs.media = img.media;\n }\n elements.push({ tag: 'link', attrs });\n }\n }\n}\n\n/**\n * Render App Links (al:*) meta tags for deep linking across platforms.\n */\nexport function renderAppLinks(\n appLinks: NonNullable<Metadata['appLinks']>,\n elements: HeadElement[]\n): void {\n const platformEntries: Array<[string, Array<Record<string, unknown>> | undefined]> = [\n ['ios', appLinks.ios],\n ['android', appLinks.android],\n ['windows', appLinks.windows],\n ['windows_phone', appLinks.windowsPhone],\n ['windows_universal', appLinks.windowsUniversal],\n ];\n\n for (const [platform, entries] of platformEntries) {\n if (!entries) continue;\n for (const entry of entries) {\n for (const [key, value] of Object.entries(entry)) {\n if (value !== undefined && value !== null) {\n elements.push({\n tag: 'meta',\n attrs: { property: `al:${platform}:${key}`, content: String(value) },\n });\n }\n }\n }\n }\n\n if (appLinks.web) {\n if (appLinks.web.url) {\n elements.push({\n tag: 'meta',\n attrs: { property: 'al:web:url', content: appLinks.web.url },\n });\n }\n if (appLinks.web.shouldFallback !== undefined) {\n elements.push({\n tag: 'meta',\n attrs: {\n property: 'al:web:should_fallback',\n content: appLinks.web.shouldFallback ? 'true' : 'false',\n },\n });\n }\n }\n}\n\n/**\n * Render Apple iTunes smart banner meta tag.\n */\nexport function renderItunes(\n itunes: NonNullable<Metadata['itunes']>,\n elements: HeadElement[]\n): void {\n const parts = [`app-id=${itunes.appId}`];\n if (itunes.affiliateData) parts.push(`affiliate-data=${itunes.affiliateData}`);\n if (itunes.appArgument) parts.push(`app-argument=${itunes.appArgument}`);\n elements.push({\n tag: 'meta',\n attrs: { name: 'apple-itunes-app', content: parts.join(', ') },\n });\n}\n","/**\n * Metadata rendering — converts resolved Metadata into HeadElement descriptors.\n *\n * Extracted from metadata.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\nimport type { HeadElement } from './metadata.js';\nimport { renderOpenGraph, renderTwitter } from './metadata-social.js';\nimport {\n renderIcons,\n renderAlternates,\n renderVerification,\n renderAppleWebApp,\n renderAppLinks,\n renderItunes,\n} from './metadata-platform.js';\n\n// ─── Render to Elements ──────────────────────────────────────────────────────\n\n/**\n * Convert resolved metadata into an array of head element descriptors.\n *\n * Each descriptor has a `tag` ('title', 'meta', 'link') and either\n * `content` (for <title>) or `attrs` (for <meta>/<link>).\n *\n * The framework's MetadataResolver component consumes these descriptors\n * and renders them into the <head>.\n */\nexport function renderMetadataToElements(metadata: Metadata): HeadElement[] {\n const elements: HeadElement[] = [];\n\n // Title\n if (typeof metadata.title === 'string') {\n elements.push({ tag: 'title', content: metadata.title });\n }\n\n // Simple string meta tags\n const simpleMetaProps: Array<[string, string | undefined]> = [\n ['description', metadata.description],\n ['generator', metadata.generator],\n ['application-name', metadata.applicationName],\n ['referrer', metadata.referrer],\n ['category', metadata.category],\n ['creator', metadata.creator],\n ['publisher', metadata.publisher],\n ];\n\n for (const [name, content] of simpleMetaProps) {\n if (content) {\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n // Keywords (array or string)\n if (metadata.keywords) {\n const content = Array.isArray(metadata.keywords)\n ? metadata.keywords.join(', ')\n : metadata.keywords;\n elements.push({ tag: 'meta', attrs: { name: 'keywords', content } });\n }\n\n // Robots\n if (metadata.robots) {\n const content =\n typeof metadata.robots === 'string' ? metadata.robots : renderRobotsObject(metadata.robots);\n elements.push({ tag: 'meta', attrs: { name: 'robots', content } });\n\n // googleBot as separate tag\n if (typeof metadata.robots === 'object' && metadata.robots.googleBot) {\n const gbContent =\n typeof metadata.robots.googleBot === 'string'\n ? metadata.robots.googleBot\n : renderRobotsObject(metadata.robots.googleBot);\n elements.push({ tag: 'meta', attrs: { name: 'googlebot', content: gbContent } });\n }\n }\n\n // Open Graph\n if (metadata.openGraph) {\n renderOpenGraph(metadata.openGraph, elements);\n }\n\n // Twitter\n if (metadata.twitter) {\n renderTwitter(metadata.twitter, elements);\n }\n\n // Icons\n if (metadata.icons) {\n renderIcons(metadata.icons, elements);\n }\n\n // Manifest\n if (metadata.manifest) {\n elements.push({ tag: 'link', attrs: { rel: 'manifest', href: metadata.manifest } });\n }\n\n // Alternates\n if (metadata.alternates) {\n renderAlternates(metadata.alternates, elements);\n }\n\n // Verification\n if (metadata.verification) {\n renderVerification(metadata.verification, elements);\n }\n\n // Format detection\n if (metadata.formatDetection) {\n const parts: string[] = [];\n if (metadata.formatDetection.telephone === false) parts.push('telephone=no');\n if (metadata.formatDetection.email === false) parts.push('email=no');\n if (metadata.formatDetection.address === false) parts.push('address=no');\n if (parts.length > 0) {\n elements.push({\n tag: 'meta',\n attrs: { name: 'format-detection', content: parts.join(', ') },\n });\n }\n }\n\n // Authors\n if (metadata.authors) {\n const authorList = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];\n for (const author of authorList) {\n if (author.name) {\n elements.push({ tag: 'meta', attrs: { name: 'author', content: author.name } });\n }\n if (author.url) {\n elements.push({ tag: 'link', attrs: { rel: 'author', href: author.url } });\n }\n }\n }\n\n // Apple Web App\n if (metadata.appleWebApp) {\n renderAppleWebApp(metadata.appleWebApp, elements);\n }\n\n // App Links (al:*)\n if (metadata.appLinks) {\n renderAppLinks(metadata.appLinks, elements);\n }\n\n // iTunes\n if (metadata.itunes) {\n renderItunes(metadata.itunes, elements);\n }\n\n // Other (custom meta tags)\n if (metadata.other) {\n for (const [name, value] of Object.entries(metadata.other)) {\n const content = Array.isArray(value) ? value.join(', ') : value;\n elements.push({ tag: 'meta', attrs: { name, content } });\n }\n }\n\n return elements;\n}\n\n// ─── Rendering Helpers ───────────────────────────────────────────────────────\n\nfunction renderRobotsObject(robots: Record<string, unknown>): string {\n const parts: string[] = [];\n if (robots.index === true) parts.push('index');\n if (robots.index === false) parts.push('noindex');\n if (robots.follow === true) parts.push('follow');\n if (robots.follow === false) parts.push('nofollow');\n return parts.join(', ');\n}\n","/**\n * Metadata resolution for timber.js.\n *\n * Resolves metadata from a segment chain (layouts + page), applies title\n * templates, shallow-merges entries, and produces head element descriptors.\n *\n * Resolution happens inside the render pass — React.cache is active,\n * metadata is outside Suspense, and the flush point guarantees completeness.\n *\n * Rendering (Metadata → HeadElement[]) is in metadata-render.ts.\n *\n * See design/16-metadata.md\n */\n\nimport type { Metadata } from './types.js';\n\n// Re-export renderMetadataToElements from the rendering module so existing\n// consumers (route-element-builder, tests) can keep importing from here.\nexport { renderMetadataToElements } from './metadata-render.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A single metadata entry from a layout or page module. */\nexport interface SegmentMetadataEntry {\n /** The resolved metadata object (from static or async `metadata` export). */\n metadata: Metadata;\n /** Whether this entry is from the page (leaf) module. */\n isPage: boolean;\n}\n\n/** Options for resolveMetadata. */\nexport interface ResolveMetadataOptions {\n /**\n * When true, the page's metadata is discarded (simulating a render error)\n * and `<meta name=\"robots\" content=\"noindex\">` is injected.\n */\n errorState?: boolean;\n}\n\n/** A rendered head element descriptor. */\nexport interface HeadElement {\n tag: 'title' | 'meta' | 'link';\n content?: string;\n attrs?: Record<string, string>;\n}\n\n// ─── Title Resolution ────────────────────────────────────────────────────────\n\n/**\n * Resolve a title value with an optional template.\n *\n * - string → apply template if present\n * - { absolute: '...' } → use as-is, skip template\n * - { default: '...' } → use as fallback (no template applied)\n * - undefined → undefined\n */\nexport function resolveTitle(\n title: Metadata['title'],\n template: string | undefined\n): string | undefined {\n if (title === undefined || title === null) {\n return undefined;\n }\n\n if (typeof title === 'string') {\n return template ? template.replace('%s', title) : title;\n }\n\n // Object form\n if (title.absolute !== undefined) {\n return title.absolute;\n }\n\n if (title.default !== undefined) {\n return title.default;\n }\n\n return undefined;\n}\n\n// ─── Metadata Resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve metadata from a segment chain.\n *\n * Processes entries from root layout to page (in segment order).\n * The merge algorithm:\n * 1. Shallow-merge all keys except title (later wins)\n * 2. Track the most recent title template\n * 3. Resolve the final title using the template\n *\n * In error state, the page entry is dropped and noindex is injected.\n *\n * See design/16-metadata.md §\"Merge Algorithm\"\n */\nexport function resolveMetadata(\n entries: SegmentMetadataEntry[],\n options: ResolveMetadataOptions = {}\n): Metadata {\n const { errorState = false } = options;\n\n const merged: Metadata = {};\n let titleTemplate: string | undefined;\n let lastDefault: string | undefined;\n let rawTitle: Metadata['title'];\n\n for (const { metadata, isPage } of entries) {\n // In error state, skip the page's metadata entirely\n if (errorState && isPage) {\n continue;\n }\n\n // Track title template\n if (metadata.title !== undefined && typeof metadata.title === 'object') {\n if (metadata.title.template !== undefined) {\n titleTemplate = metadata.title.template;\n }\n if (metadata.title.default !== undefined) {\n lastDefault = metadata.title.default;\n }\n }\n\n // Shallow-merge all keys except title\n for (const key of Object.keys(metadata) as Array<keyof Metadata>) {\n if (key === 'title') continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (merged as any)[key] = metadata[key];\n }\n\n // Track raw title (will be resolved after the loop)\n if (metadata.title !== undefined) {\n rawTitle = metadata.title;\n }\n }\n\n // In error state, we lost page title — use the most recent default\n if (errorState) {\n rawTitle = lastDefault !== undefined ? { default: lastDefault } : rawTitle;\n // Don't apply template in error state\n titleTemplate = undefined;\n }\n\n // Resolve the final title\n const resolvedTitle = resolveTitle(rawTitle, titleTemplate);\n if (resolvedTitle !== undefined) {\n merged.title = resolvedTitle;\n }\n\n // Error state: inject noindex, overriding any user robots\n if (errorState) {\n merged.robots = 'noindex';\n }\n\n return merged;\n}\n\n// ─── URL Resolution ──────────────────────────────────────────────────────────\n\n/**\n * Check if a string is an absolute URL.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//');\n}\n\n/**\n * Resolve a relative URL against a base URL.\n */\nfunction resolveUrl(url: string, base: URL): string {\n if (isAbsoluteUrl(url)) return url;\n return new URL(url, base).toString();\n}\n\n/**\n * Resolve relative URLs in metadata fields against metadataBase.\n *\n * Returns a new metadata object with URLs resolved. Absolute URLs are not modified.\n * If metadataBase is not set, returns the metadata unchanged.\n */\nexport function resolveMetadataUrls(metadata: Metadata): Metadata {\n const base = metadata.metadataBase;\n if (!base) return metadata;\n\n const result = { ...metadata };\n\n // Resolve openGraph images\n if (result.openGraph) {\n result.openGraph = { ...result.openGraph };\n if (typeof result.openGraph.images === 'string') {\n result.openGraph.images = resolveUrl(result.openGraph.images, base);\n } else if (Array.isArray(result.openGraph.images)) {\n result.openGraph.images = result.openGraph.images.map((img) => ({\n ...img,\n url: resolveUrl(img.url, base),\n }));\n } else if (result.openGraph.images) {\n // Single object: { url, width?, height?, alt? }\n result.openGraph.images = {\n ...result.openGraph.images,\n url: resolveUrl(result.openGraph.images.url, base),\n };\n }\n if (result.openGraph.url && !isAbsoluteUrl(result.openGraph.url)) {\n result.openGraph.url = resolveUrl(result.openGraph.url, base);\n }\n }\n\n // Resolve twitter images\n if (result.twitter) {\n result.twitter = { ...result.twitter };\n if (typeof result.twitter.images === 'string') {\n result.twitter.images = resolveUrl(result.twitter.images, base);\n } else if (Array.isArray(result.twitter.images)) {\n // Resolve each image URL, preserving the union type structure\n const resolved = result.twitter.images.map((img) =>\n typeof img === 'string' ? resolveUrl(img, base) : { ...img, url: resolveUrl(img.url, base) }\n );\n // If all entries are strings, assign as string[]; otherwise as object[]\n const allStrings = resolved.every((r) => typeof r === 'string');\n result.twitter.images = allStrings\n ? (resolved as string[])\n : (resolved as Array<{ url: string; alt?: string; width?: number; height?: number }>);\n } else if (result.twitter.images) {\n // Single object: { url, alt?, width?, height? }\n result.twitter.images = {\n ...result.twitter.images,\n url: resolveUrl(result.twitter.images.url, base),\n };\n }\n }\n\n // Resolve alternates\n if (result.alternates) {\n result.alternates = { ...result.alternates };\n if (result.alternates.canonical && !isAbsoluteUrl(result.alternates.canonical)) {\n result.alternates.canonical = resolveUrl(result.alternates.canonical, base);\n }\n if (result.alternates.languages) {\n const langs: Record<string, string> = {};\n for (const [lang, url] of Object.entries(result.alternates.languages)) {\n langs[lang] = isAbsoluteUrl(url) ? url : resolveUrl(url, base);\n }\n result.alternates.languages = langs;\n }\n }\n\n // Resolve icon URLs\n if (result.icons) {\n result.icons = { ...result.icons };\n if (typeof result.icons.icon === 'string') {\n result.icons.icon = resolveUrl(result.icons.icon, base);\n } else if (Array.isArray(result.icons.icon)) {\n result.icons.icon = result.icons.icon.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n if (typeof result.icons.apple === 'string') {\n result.icons.apple = resolveUrl(result.icons.apple, base);\n } else if (Array.isArray(result.icons.apple)) {\n result.icons.apple = result.icons.apple.map((i) => ({ ...i, url: resolveUrl(i.url, base) }));\n }\n }\n\n return result;\n}\n","'use client';\n\n/**\n * Reconstitutes a SerializableError into a real Error instance before\n * passing to the user's error component.\n *\n * TSX error pages are 'use client' components that receive { error: Error, digest, reset }.\n * Error objects are not RSC-serializable (React Flight throws \"Only plain objects\n * can be passed to Client Components\"). This wrapper receives the error as a plain\n * SerializableError object, reconstitutes a real Error instance, and passes it\n * to the user's error component — ensuring error instanceof Error works correctly.\n *\n * See design/spike-TIM-565-unify-error-pages.md §\"Edge Case B\"\n * See design/10-error-handling.md §\"RSC → SSR for Error Pages via SerializableError\"\n */\n\nimport { createElement, type ReactNode, type ComponentType } from 'react';\n\n/**\n * Plain-object representation of an Error that can cross the RSC → client boundary.\n * Stack is only included in dev mode (gated by isDevMode() on the server).\n */\nexport interface SerializableError {\n message: string;\n name: string;\n stack?: string;\n}\n\n/**\n * Props for the ErrorReconstituter wrapper component.\n * All props are RSC-serializable:\n * - error: plain object (SerializableError)\n * - digest: plain JSON or null\n * - reset: undefined (only meaningful on client after boundary catch)\n * - component: client module reference (RSC Flight serializes as opaque ref)\n * - status / dangerouslyPassData: set only when error.tsx serves a deny()\n * as the last entry in the 4xx fallback chain — forwarded so dual-shape\n * error.tsx implementations can branch on the deny status (TIM-1081)\n */\ninterface ErrorReconstituterProps {\n error: SerializableError;\n digest: { code: string; data: unknown } | null;\n reset: undefined;\n component: ComponentType<{\n error: Error;\n digest: { code: string; data: unknown } | null;\n reset: (() => void) | undefined;\n status?: number;\n dangerouslyPassData?: unknown;\n }>;\n status?: number;\n dangerouslyPassData?: unknown;\n}\n\n/**\n * Reconstitute a SerializableError into a real Error instance and render\n * the user's error component with the proper props.\n */\nexport function ErrorReconstituter({\n error: serialized,\n digest,\n reset,\n component,\n status,\n dangerouslyPassData,\n}: ErrorReconstituterProps): ReactNode {\n // Reconstitute a real Error so instanceof checks work in user code\n const error = Object.assign(new Error(serialized.message), {\n name: serialized.name,\n ...(serialized.stack != null ? { stack: serialized.stack } : {}),\n });\n\n return createElement(component, {\n error,\n digest,\n reset,\n ...(status != null ? { status, dangerouslyPassData } : {}),\n });\n}\n","/**\n * loadModule — enriched error context for route manifest .load() failures.\n *\n * Wraps the lazy `load()` functions from the route manifest with a\n * try/catch that re-throws with the file path and original cause.\n *\n * Callers that need fallthrough behavior (error renderers) can use\n * `.catch(() => null)` or try/catch — the decision stays at the call site.\n *\n * See design/spike-TIM-551-dynamic-import-audit.md §\"Proposed Wrapping Strategy\"\n */\n\n/** A manifest file reference with a lazy import function and file path. */\nexport interface ManifestLoader {\n load: () => Promise<unknown>;\n filePath: string;\n}\n\n/**\n * Custom error class for module load failures.\n *\n * Preserves the original error as `cause` while providing a\n * human-readable message with the file path.\n */\nexport class ModuleLoadError extends Error {\n /** The file path that failed to load. */\n readonly filePath: string;\n\n constructor(filePath: string, cause: unknown) {\n const originalMessage = cause instanceof Error ? cause.message : String(cause);\n super(`[timber] Failed to load module ${filePath}\\n ${originalMessage}`, { cause });\n this.name = 'ModuleLoadError';\n this.filePath = filePath;\n }\n}\n\n/**\n * Load a route manifest module with enriched error context.\n *\n * On success: returns the module object (same as `loader.load()`).\n * On failure: throws `ModuleLoadError` with file path and original cause.\n *\n * For error rendering paths that need fallthrough instead of throwing,\n * callers should catch at the call site:\n *\n * ```ts\n * // Throwing (default) — route-element-builder, api-handler, etc.\n * const mod = await loadModule(segment.page);\n *\n * // Fallthrough — error-renderer, error-boundary-wrapper\n * const mod = await loadModule(segment.error).catch(() => null);\n * ```\n */\nexport async function loadModule<T = Record<string, unknown>>(loader: ManifestLoader): Promise<T> {\n try {\n return (await loader.load()) as T;\n } catch (error) {\n throw new ModuleLoadError(loader.filePath, error);\n }\n}\n","/**\n * Status-code file resolver for timber.js error/denial rendering.\n *\n * Given an HTTP status code and a matched segment chain, resolves the\n * correct file to render by walking the fallback chain described in\n * design/10-error-handling.md §\"Status-Code Files\".\n *\n * **Generic over `TFile`** (TIM-848). Walks `SegmentNode<TFile>` trees\n * regardless of whether `TFile` is the build-time `RouteFile` or the\n * runtime `ManifestFile`. Before TIM-848 there were two near-identical\n * resolvers — one for the Map-based scanner output and one for the\n * object-based runtime manifest. Now there is one.\n *\n * Supports two format families:\n * - 'component' (default): .tsx/.jsx/.mdx status files → React rendering pipeline\n * - 'json': .json status files → raw JSON response, no React\n *\n * Fallback chains operate within the same format family (no cross-format fallback).\n *\n * **Component chain (4xx):**\n * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx\n * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx\n * Pass 3 — error.tsx (leaf → root)\n * Pass 4 — framework default (returns null)\n *\n * **JSON chain (4xx and 5xx):**\n * Pass 1 — json status files (leaf → root): {status}.json → {category}.json\n * Pass 2 — framework default JSON (returns null, caller provides bare JSON)\n *\n * **5xx component:**\n * Per-segment (leaf → root): {status}.tsx → 5xx.tsx → error.tsx\n * Then framework default (returns null)\n */\n\nimport type { SegmentNode } from '../routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** How the status-code file was matched. */\nexport type StatusFileKind =\n | 'exact' // e.g. 403.tsx matched status 403\n | 'category' // e.g. 4xx.tsx matched status 403\n | 'legacy' // e.g. not-found.tsx matched status 404\n | 'error'; // error.tsx as last resort\n\n/** Response format family for status-code resolution. */\nexport type StatusFileFormat = 'component' | 'json';\n\n/** Result of resolving a status-code file for a segment chain. */\nexport interface StatusFileResolution<TFile> {\n /** The matched route file. */\n file: TFile;\n /** The HTTP status code (always the original status, not the file's code). */\n status: number;\n /** How the file was matched. */\n kind: StatusFileKind;\n /** Index into the segments array where the file was found. */\n segmentIndex: number;\n}\n\n/** How a slot denial file was matched. */\nexport type SlotDeniedKind = 'denied' | 'default';\n\n/** Result of resolving a slot denied file. */\nexport interface SlotDeniedResolution<TFile> {\n /** The matched route file (denied.tsx or default.tsx). */\n file: TFile;\n /** Slot name without @ prefix. */\n slotName: string;\n /** How the file was matched. */\n kind: SlotDeniedKind;\n}\n\n// ─── Legacy Compat Mapping ───────────────────────────────────────────────────\n\n/**\n * Maps legacy file convention names to their corresponding HTTP status codes.\n * Only used in the 4xx component fallback chain. Exported so the in-tree\n * deny chain (deny-boundary.ts) uses the same mapping — see TIM-1081.\n */\nexport const LEGACY_FILE_TO_STATUS: Record<string, number> = {\n 'not-found': 404,\n 'forbidden': 403,\n 'unauthorized': 401,\n};\n\n/** Reverse index: status code → legacy file name. Built once at module load. */\nconst STATUS_TO_LEGACY_FILE: Record<number, string> = Object.fromEntries(\n Object.entries(LEGACY_FILE_TO_STATUS).map(([name, status]) => [status, name])\n);\n\n// ─── Lookup Helpers ──────────────────────────────────────────────────────\n\n/**\n * Look up `{statusStr}` then `{categoryKey}` (e.g. \"4xx\" / \"5xx\") in a\n * status-file group on a single segment. Shared by all three fallback\n * chains — the only structural difference between component 4xx,\n * component 5xx, and JSON resolution is *which* group is searched and\n * how the per-segment loop is layered around it.\n */\nfunction lookupInGroup<TFile>(\n group: Record<string, TFile> | undefined,\n statusStr: string,\n categoryKey: string,\n segmentIndex: number,\n status: number\n): StatusFileResolution<TFile> | null {\n if (!group) return null;\n const exact = group[statusStr];\n if (exact) return { file: exact, status, kind: 'exact', segmentIndex };\n const category = group[categoryKey];\n if (category) return { file: category, status, kind: 'category', segmentIndex };\n return null;\n}\n\n/**\n * Look up the legacy convention file (`not-found.tsx` / `forbidden.tsx` /\n * `unauthorized.tsx`) for `status` on a single segment. Returns null if\n * `status` has no legacy mapping or the file isn't present.\n */\nfunction lookupLegacy<TFile>(\n group: Record<string, TFile> | undefined,\n status: number,\n segmentIndex: number\n): StatusFileResolution<TFile> | null {\n if (!group) return null;\n const name = STATUS_TO_LEGACY_FILE[status];\n if (!name) return null;\n const file = group[name];\n return file ? { file, status, kind: 'legacy', segmentIndex } : null;\n}\n\n// ─── Resolver ────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the status-code file to render for a given HTTP status code.\n *\n * Walks the segment chain from leaf to root following the fallback chain\n * defined in design/10-error-handling.md. Returns null if no file is found\n * (caller should render the framework default).\n *\n * @param status - The HTTP status code (4xx or 5xx).\n * @param segments - The matched segment chain from root (index 0) to leaf (last).\n * @param format - The response format family ('component' or 'json'). Defaults to 'component'.\n */\nexport function resolveStatusFile<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>,\n format: StatusFileFormat = 'component'\n): StatusFileResolution<TFile> | null {\n if (status < 400 || status > 599) return null;\n if (format === 'json') return resolveJson(status, segments);\n if (status <= 499) return resolve4xx(status, segments);\n return resolve5xx(status, segments);\n}\n\n/**\n * 4xx component fallback chain — three separate full passes leaf→root.\n *\n * The passes must be separate (not interleaved per-segment) so that a\n * root-level `404.tsx` beats a leaf-level `error.tsx`. The 5xx chain\n * inverts this and is per-segment: a leaf's `error.tsx` beats a root's\n * `5xx.tsx`. This asymmetry is the only reason these two functions exist\n * separately.\n *\n * Pass 1 — {status}.tsx → 4xx.tsx (statusFiles)\n * Pass 2 — not-found / forbidden / unauthorized (legacyStatusFiles)\n * Pass 3 — error.tsx (error)\n */\nfunction resolve4xx<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupInGroup(segments[i].statusFiles, statusStr, '4xx', i, status);\n if (r) return r;\n }\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupLegacy(segments[i].legacyStatusFiles, status, i);\n if (r) return r;\n }\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const errorFile = segments[i].error;\n if (errorFile) {\n return { file: errorFile, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * 5xx component fallback chain — single pass, per-segment leaf→root.\n *\n * At each segment: {status}.tsx → 5xx.tsx → error.tsx. A leaf's\n * `error.tsx` therefore beats a root's `5xx.tsx`, which is the\n * intentional inverse of the 4xx chain.\n */\nfunction resolve5xx<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n const r = lookupInGroup(segment.statusFiles, statusStr, '5xx', i, status);\n if (r) return r;\n if (segment.error) {\n return { file: segment.error, status, kind: 'error', segmentIndex: i };\n }\n }\n\n return null;\n}\n\n/**\n * JSON fallback chain (for both 4xx and 5xx) — single pass leaf→root.\n *\n * At each segment: {status}.json → {category}.json. No legacy compat,\n * no error.tsx — the JSON chain terminates at the category catch-all\n * and the caller falls back to a bare-JSON framework default.\n */\nfunction resolveJson<TFile>(\n status: number,\n segments: ReadonlyArray<SegmentNode<TFile>>\n): StatusFileResolution<TFile> | null {\n const statusStr = String(status);\n const categoryKey = status >= 500 ? '5xx' : '4xx';\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const r = lookupInGroup(segments[i].jsonStatusFiles, statusStr, categoryKey, i, status);\n if (r) return r;\n }\n\n return null;\n}\n\n// ─── Slot Denied Resolver ────────────────────────────────────────────────────\n\n/**\n * Resolve the denial file for a parallel route slot.\n *\n * Slot denial is graceful degradation — no HTTP status on the wire.\n * Fallback chain: denied.tsx → default.tsx → null.\n *\n * @param slotNode - The segment node for the slot (segmentType === 'slot').\n */\nexport function resolveSlotDenied<TFile>(\n slotNode: SegmentNode<TFile>\n): SlotDeniedResolution<TFile> | null {\n const slotName = slotNode.segmentName.replace(/^@/, '');\n\n if (slotNode.denied) {\n return { file: slotNode.denied, slotName, kind: 'denied' };\n }\n\n if (slotNode.default) {\n return { file: slotNode.default, slotName, kind: 'default' };\n }\n\n return null;\n}\n","/**\n * Deny boundary subsystem — the in-tree DenySignal flow.\n *\n * Three things live together here because they form a single flow:\n *\n * 1. **Chain construction** (`buildDenyPageChain`) — walks the matched\n * segment chain at element-tree build time and produces a list of\n * `DenyPageEntry` records ordered by specificity (specific status →\n * category catch-all → `error.tsx`).\n *\n * 2. **Runtime matching** (`renderMatchingDenyPage`) — picks the first\n * chain entry whose status filter matches the thrown DenySignal and\n * returns a React element for the matching component. Used by\n * `AccessGate` and `PageDenyBoundary` when they catch a deny.\n *\n * 3. **The page boundary itself** (`PageDenyBoundary`) — the async server\n * component that wraps a server-component page, calls it, and catches\n * `DenySignal` so the deny page renders in-tree (no throw reaches\n * React Flight, single render pass).\n *\n * Plus the ALS helpers (`setDenyStatus` / `getDenyStatus`) the boundary\n * uses to thread the matched status code back to the pipeline so the\n * HTTP status reflects the deny.\n *\n * Folded into one module from the former `deny-page-resolver.ts` and\n * `page-deny-boundary.tsx` (TIM-853) — the names were misleading and the\n * three pieces only made sense together.\n *\n * See design/04-authorization.md, design/10-error-handling.md, TIM-666.\n */\n\nimport { createElement } from 'react';\n\nimport { ErrorReconstituter } from '../client/error-reconstituter.js';\nimport type { SerializableError } from '../client/error-reconstituter.js';\nimport { requestContextAls } from './als-registry.js';\nimport { DenySignal } from './primitives.js';\nimport { loadModule } from './safe-load.js';\nimport { LEGACY_FILE_TO_STATUS } from './status-code-resolver.js';\nimport { withSpan } from './tracing.js';\nimport { isMdxFilePath } from './utils/mdx-file.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/** A single entry in the deny page fallback chain. */\nexport interface DenyPageEntry {\n /** Status code filter: specific (403), category (400 = any 4xx), or null (catch-all). */\n status: number | null;\n /** The component to render (server or client — both work). */\n component: (...args: unknown[]) => unknown;\n /**\n * How the entry matched: a status-code file (404.tsx/4xx.tsx), a legacy\n * compat file (not-found.tsx/forbidden.tsx/unauthorized.tsx), or the\n * error.tsx catch-all. error.tsx entries render with their documented\n * { error, digest, reset } contract, not bare deny props. See TIM-1081.\n */\n kind: 'status' | 'legacy' | 'error';\n /** MDX files are server components — rendered with plain props, never ErrorReconstituter. */\n isMdx: boolean;\n}\n\n// ─── Chain Construction ──────────────────────────────────────────────────\n\n/**\n * Build the deny page fallback chain from the segment chain.\n *\n * Walks segments from `startIndex` outward (toward root) and collects\n * status-code file components in fallback order:\n * 1. Specific status files (403.tsx, 404.tsx) — exact match\n * 2. Category catch-alls (4xx.tsx) — matches any 4xx\n * 3. Legacy compat files (not-found.tsx → 404, forbidden.tsx → 403,\n * unauthorized.tsx → 401) — exact match\n * 4. error.tsx — catches everything\n *\n * Each segment is checked in this order. The chain is ordered so the\n * FIRST match wins at catch time. This mirrors resolveStatusFile's 4xx\n * component chain (status-code-resolver.ts) — the two walks must stay in\n * sync or in-tree and re-render deny paths resolve different files.\n */\nexport async function buildDenyPageChain(\n segments: ManifestSegmentNode[],\n startIndex: number\n): Promise<DenyPageEntry[]> {\n const chain: DenyPageEntry[] = [];\n\n // Pass 1: Status files (specific + category) across ALL segments.\n // These have higher priority than error.tsx — a root 4xx.tsx should\n // match before a leaf error.tsx. Walking inner → outer ensures the\n // nearest match wins within each priority tier.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.statusFiles) continue;\n\n // Specific status files (403.tsx, 404.tsx, etc.)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n chain.push({\n status,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'status',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n }\n\n // Category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n const categoryStatus = key === '4xx' ? 400 : 500;\n chain.push({\n status: categoryStatus,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'status',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n }\n\n // Pass 2: legacy compat files (not-found.tsx → 404, forbidden.tsx → 403,\n // unauthorized.tsx → 401). Lower priority than status files (a root\n // 4xx.tsx beats a leaf not-found.tsx) but higher than error.tsx — the\n // same ordering as resolveStatusFile's resolve4xx. See TIM-1081.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (!segment.legacyStatusFiles) continue;\n for (const [name, status] of Object.entries(LEGACY_FILE_TO_STATUS)) {\n const file = segment.legacyStatusFiles[name];\n if (!file) continue;\n const mod = await loadModule(file).catch(() => null);\n if (mod?.default) {\n chain.push({\n status,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'legacy',\n isMdx: isMdxFilePath(file.filePath),\n });\n }\n }\n }\n\n // Pass 3: error.tsx files — lowest priority catch-all.\n // Only added AFTER all status and legacy files so they never shadow a\n // more specific file from an ancestor segment.\n for (let i = startIndex; i >= 0; i--) {\n const segment = segments[i];\n if (segment.error) {\n const mod = await loadModule(segment.error).catch(() => null);\n if (mod?.default) {\n chain.push({\n status: null,\n component: mod.default as (...args: unknown[]) => unknown,\n kind: 'error',\n isMdx: isMdxFilePath(segment.error.filePath),\n });\n }\n }\n }\n\n return chain;\n}\n\n// ─── Runtime Matcher ──────────────────────────────────────────────────────\n\n/**\n * Find the first deny page in the chain that matches the given status code.\n * Returns a React element for the matching component, or null if no match.\n */\nexport function renderMatchingDenyPage(\n chain: DenyPageEntry[],\n status: number,\n data: unknown\n): React.ReactElement | null {\n for (const entry of chain) {\n if (entry.status === status) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === 400 && status >= 400 && status <= 499) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === 500 && status >= 500 && status <= 599) {\n return renderDenyEntry(entry, status, data);\n }\n if (entry.status === null) {\n return renderDenyEntry(entry, status, data);\n }\n }\n return null;\n}\n\n/**\n * Build the element for a matched deny chain entry with the props contract\n * the file's convention documents:\n *\n * - Status-code and legacy files: { status, dangerouslyPassData }\n * - error.tsx (TSX): { error, digest, reset } via ErrorReconstituter, plus\n * { status, dangerouslyPassData } for dual-shape implementations. Without\n * the Error prop, any error.tsx written per the docs (reading\n * error.message) crashes — turning a clean deny(404) into a 500. TIM-1081.\n * - error.mdx: plain { status } — server component, static content.\n */\nexport function renderDenyEntry(\n entry: DenyPageEntry,\n status: number,\n data: unknown\n): React.ReactElement {\n const h = createElement as (...args: unknown[]) => React.ReactElement;\n\n if (entry.kind !== 'error') {\n return h(entry.component, { status, dangerouslyPassData: data });\n }\n\n // MDX error pages receive plain props — no Error serialization possible.\n if (entry.isMdx) {\n return h(entry.component, { status });\n }\n\n // The message is derived solely from the status code (already on the\n // wire) — safe to cross the RSC→client boundary in production. No stack,\n // no user data. See design/13-security.md §\"Errors don't leak\".\n const serializableDeny: SerializableError = {\n message: `Access denied with status ${status}`,\n name: 'DenySignal',\n };\n return h(ErrorReconstituter, {\n error: serializableDeny,\n digest: null,\n reset: undefined,\n component: entry.component,\n status,\n dangerouslyPassData: data,\n });\n}\n\n// ─── Page Boundary ────────────────────────────────────────────────────────\n\n/**\n * Async server component that wraps a page call with DenySignal catching.\n *\n * Calls the page component as an async function (the same thing React\n * Flight does internally), awaits it, and catches DenySignal. On catch,\n * renders the matching deny page in-tree. On success, returns the page's\n * rendered output normally.\n *\n * Client component pages ('use client') are NOT wrapped — they can't call\n * deny() (server-only API) and must go through createElement normally.\n *\n * No error reaches React Flight — the Flight stream is clean, SSR succeeds,\n * and the entire request uses a single renderToReadableStream call.\n */\nexport async function PageDenyBoundary({\n Page,\n route,\n denyPages,\n}: {\n /** The page server component function. */\n Page: (...args: unknown[]) => unknown;\n /** Route path for OTEL tracing. */\n route: string;\n /** Deny page fallback chain from the segment chain. */\n denyPages: DenyPageEntry[];\n}): Promise<React.ReactElement> {\n try {\n // Call the page as an async function — same as React Flight does.\n // Wrap in OTEL span for tracing (replaces the TracedPage wrapper).\n const result = await withSpan('timber.page', { 'timber.route': route }, () => Page({}));\n return result as React.ReactElement;\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n // Non-deny errors (RedirectSignal, runtime errors) propagate normally.\n throw error;\n }\n}\n\n// ─── ALS Helpers ──────────────────────────────────────────────────────────\n\n/**\n * Set the deny status in the request context ALS.\n * Called from AccessGate / PageDenyBoundary when a DenySignal is caught.\n * The pipeline reads this after render to set the HTTP status code.\n */\nexport function setDenyStatus(status: number): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.denyStatus = status;\n }\n}\n\n/**\n * Read the deny status from the request context ALS.\n * Returns undefined if no deny was caught during render.\n */\nexport function getDenyStatus(): number | undefined {\n return requestContextAls.getStore()?.denyStatus;\n}\n","/**\n * AccessGate and SlotAccessGate — framework-injected async server components.\n *\n * AccessGate wraps each segment's layout in the element tree. It calls the\n * segment's access.ts before the layout renders. If access.ts calls deny()\n * or redirect(), the signal propagates as a render-phase throw — caught by\n * the flush controller to produce the correct HTTP status code.\n *\n * SlotAccessGate wraps parallel slot content. On denial, it renders the\n * graceful degradation chain: denied.tsx → default.tsx → null. Slot denial\n * does not affect the HTTP status code.\n *\n * See design/04-authorization.md and design/02-rendering-pipeline.md §\"AccessGate\"\n */\n\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport type { AccessGateProps, SlotAccessGateProps } from './tree-builder.js';\nimport { withSpan, setSpanAttribute } from './tracing.js';\nimport { isDebug } from './debug.js';\nimport type { DenyPageEntry } from './deny-boundary.js';\nimport { renderMatchingDenyPage, setDenyStatus } from './deny-boundary.js';\nimport type { ReactNode } from 'react';\n\n// ─── AccessGate ─────────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for segments.\n *\n * When a pre-computed `verdict` prop is provided (from the pre-render pass\n * in route-element-builder.ts), AccessGate replays it synchronously — no\n * async, no re-execution of access.ts, immune to Suspense timing. The OTEL\n * span was already emitted during the pre-render pass.\n *\n * When no verdict is provided (backward compat with tree-builder.ts),\n * AccessGate calls accessFn directly with OTEL instrumentation.\n *\n * access.ts is a pure gate — return values are discarded. The layout below\n * gets the same data by calling the same cached functions (React.cache dedup).\n */\nexport function AccessGate(props: AccessGateProps): ReactNode | Promise<ReactNode> {\n const { accessFn, segmentName, verdict, denyPages, children } = props;\n\n // Fast path: replay pre-computed verdict from the pre-render pass.\n if (verdict !== undefined) {\n if (verdict === 'pass') {\n return children;\n }\n // Render deny page in-tree when possible (same as the fallback path).\n if (verdict instanceof DenySignal && denyPages) {\n const denyElement = renderMatchingDenyPage(denyPages, verdict.status, verdict.data);\n if (denyElement) {\n setDenyStatus(verdict.status);\n return denyElement;\n }\n }\n throw verdict;\n }\n\n // Primary path: call accessFn directly during render.\n // If denyPages is provided, catch DenySignal and render the deny page\n // in-tree — no throw reaches React Flight, no second render pass.\n return accessGateFallback(accessFn, segmentName, denyPages, children);\n}\n\n/**\n * Async fallback for AccessGate when no pre-computed verdict is available.\n * Calls accessFn with OTEL instrumentation.\n */\nasync function accessGateFallback(\n accessFn: AccessGateProps['accessFn'],\n segmentName: AccessGateProps['segmentName'],\n denyPages: DenyPageEntry[] | undefined,\n children: ReactNode\n): Promise<ReactNode> {\n try {\n await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {\n try {\n await accessFn();\n await setSpanAttribute('timber.result', 'pass');\n } catch (error: unknown) {\n if (error instanceof DenySignal) {\n await setSpanAttribute('timber.result', 'deny');\n await setSpanAttribute('timber.deny_status', error.status);\n if (error.sourceFile) {\n await setSpanAttribute('timber.deny_file', error.sourceFile);\n }\n } else if (error instanceof RedirectSignal) {\n await setSpanAttribute('timber.result', 'redirect');\n }\n throw error;\n }\n });\n } catch (error: unknown) {\n // Catch DenySignal and render the deny page in-tree.\n // No throw reaches React Flight — clean stream, single render pass.\n // RedirectSignal and other errors propagate normally.\n if (error instanceof DenySignal && denyPages) {\n const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n throw error;\n }\n\n return children;\n}\n\n// ─── SlotAccessGate ─────────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate for parallel slots.\n *\n * On denial, graceful degradation: denied.tsx → default.tsx → null.\n * The HTTP status code is unaffected — slot denial is a UI concern, not\n * a protocol concern. The parent layout and sibling slots still render.\n *\n * DeniedComponent is passed instead of a pre-built element so that\n * DenySignal.data can be forwarded as the dangerouslyPassData prop\n * and the slot name can be passed as the slot prop. See TIM-488.\n *\n * redirect() in slot access.ts is a dev-mode error — redirecting from a\n * slot doesn't make architectural sense.\n */\nexport async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactNode> {\n const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;\n\n try {\n await accessFn();\n } catch (error: unknown) {\n // DenySignal → graceful degradation (denied.tsx → default.tsx → null)\n // Build the denied element dynamically so DenySignal.data is forwarded.\n if (error instanceof DenySignal) {\n return (\n buildDeniedFallback(DeniedComponent, slotName, error.data, createElement) ??\n defaultFallback ??\n null\n );\n }\n\n // RedirectSignal in slot access → dev-mode error.\n // Slot access should use deny(), not redirect(). Redirecting from a\n // slot would redirect the entire page, which breaks the contract that\n // slot failure is graceful degradation.\n if (error instanceof RedirectSignal) {\n if (isDebug()) {\n console.error(\n '[timber] redirect() is not allowed in slot access.ts. ' +\n 'Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. ' +\n \"If you need to redirect, move the logic to the parent segment's access.ts.\"\n );\n }\n // In production, treat as a deny — render fallback rather than crash.\n return (\n buildDeniedFallback(DeniedComponent, slotName, undefined, createElement) ??\n defaultFallback ??\n null\n );\n }\n\n // Unhandled error — re-throw so error boundaries can catch it.\n // Dev-mode warning: slot access should use deny(), not throw.\n if (isDebug()) {\n console.warn(\n '[timber] Unhandled error in slot access.ts. ' +\n 'Use deny() for access control, not unhandled throws.',\n error\n );\n }\n throw error;\n }\n\n // Access passed — render slot content.\n return children;\n}\n\n/**\n * Build the denied fallback element dynamically with DenySignal data.\n * Returns null if no DeniedComponent is available.\n */\nfunction buildDeniedFallback(\n DeniedComponent: SlotAccessGateProps['DeniedComponent'],\n slotName: string,\n data: unknown,\n createElement: SlotAccessGateProps['createElement']\n): ReactNode | null {\n if (!DeniedComponent) return null;\n return createElement(DeniedComponent, {\n slot: slotName,\n dangerouslyPassData: data,\n });\n}\n","/**\n * Segment param coercion — runs the matched route's `params.ts` codecs\n * over the raw matcher output before middleware and rendering.\n *\n * Lifted out of `pipeline-phases.ts` (TIM-853) so the coercer can be\n * imported directly by other entry points (the action-dispatch wrapper,\n * the revalidation renderer in `rsc-entry/index.ts`) without pulling\n * the entire pipeline phase module along with it.\n *\n * The function throws `ParamCoercionError` from `route-element-builder.ts`\n * on any codec failure; the pipeline catches that and dispatches to the\n * 404 page. See design/07-routing.md §\"Where Coercion Runs\".\n */\n\nimport type { Codec } from '../codec.js';\nimport { toBracketKey } from '../params/resolve-schema.js';\nimport type { RouteMatch } from './pipeline.js';\nimport { sanitizeParamValue } from './pipeline-helpers.js';\nimport { loadModule } from './safe-load.js';\nimport { ParamCoercionError } from './route-element-builder.js';\nimport { isDebug } from './debug.js';\n\n// ---------------------------------------------------------------------------\n// Module-level global codec store (TIM-931)\n// ---------------------------------------------------------------------------\n\n/** Global schema codecs, set once at startup from virtual:timber-schema. */\nlet _globalCodecs: Record<string, Codec<unknown>> | null = null;\n\n/**\n * Register the global schema codecs. Called once from the RSC entry\n * at server startup when app/schema.ts provides segment param codecs.\n * @internal\n */\nexport function setGlobalSchemaCodecs(codecs: Record<string, Codec<unknown>> | null): void {\n _globalCodecs = codecs;\n}\n\n/**\n * Coerce raw slot params through global schema codecs.\n *\n * Each slot segment that has a paramName is looked up in the global codec\n * map by its bracket key (e.g. '[...year]' for catch-all). If a codec\n * exists, the raw value is parsed through it. If no codec exists or no\n * global codecs are registered, the raw value passes through unchanged.\n *\n * Unlike main route coercion, this does NOT throw ParamCoercionError on\n * failure — slots degrade gracefully. A failed coercion logs a warning\n * in dev mode and keeps the raw value.\n *\n * @internal — framework use only\n */\nexport function coerceSlotParams(\n slotChain: Array<{ segmentType: string; paramName?: string }>,\n rawParams: Record<string, string | string[]>\n): Record<string, string | string[]> {\n const globalCodecs = _globalCodecs;\n if (!globalCodecs) return rawParams;\n\n const result: Record<string, string | string[]> = Object.create(null);\n for (const key of Object.keys(rawParams)) {\n result[key] = rawParams[key];\n }\n\n for (const segment of slotChain) {\n if (!segment.paramName) continue;\n const bracketKey = toBracketKey(segment.segmentType, segment.paramName);\n const codec = globalCodecs[bracketKey];\n if (!codec) continue;\n\n const key = segment.paramName;\n if (!(key in result)) continue;\n\n try {\n result[key] = sanitizeParamValue(codec.parse(result[key] as string | string[])) as\n | string\n | string[];\n } catch (err) {\n // Slot param coercion failures are non-fatal — keep the raw value.\n // The main route already validated the URL; the slot just interprets\n // the same parts differently.\n if (isDebug()) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\n `[timber] Slot param coercion failed for \"${key}\" (codec: ${bracketKey})\\n` +\n ` Error: ${message}\\n` +\n ` Raw value: ${JSON.stringify(result[key])}\\n` +\n ` Keeping raw value.`\n );\n }\n }\n }\n\n return result as Record<string, string | string[]>;\n}\n\n/**\n * Run segment param coercion on the matched route's segments.\n *\n * When `globalCodecs` is provided (from app/schema.ts), uses the global\n * codec map keyed by bare param name. Otherwise falls back to loading\n * per-segment params.ts modules.\n *\n * Throws ParamCoercionError if any codec fails (→ 404).\n *\n * This runs BEFORE middleware, so ctx.segmentParams is already typed.\n * See design/07-routing.md §\"Where Coercion Runs\"\n * See design/41-global-params.md §\"Pipeline Integration\"\n */\nexport async function coerceSegmentParams(match: RouteMatch): Promise<void> {\n const globalCodecs = _globalCodecs;\n // Unconditionally install a null-prototype target so the invariant\n // \"match.segmentParams is null-prototype\" holds from the first line,\n // regardless of whether any segment has a codec.\n const mergeTarget: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(match.segmentParams)) {\n if (key !== '__proto__') {\n mergeTarget[key] = match.segmentParams[key as keyof typeof match.segmentParams];\n }\n }\n match.segmentParams = mergeTarget as RouteMatch['segmentParams'];\n\n // TIM-931/TIM-936: When a global schema is available, use it for coercion\n // instead of per-segment params.ts files. The global codec map is keyed\n // by bracket name (e.g. '[id]', '[...slug]') to avoid collisions between\n // distinct segment types that share a param name.\n if (globalCodecs) {\n for (const segment of match.segments) {\n if (!segment.paramName) continue;\n const bracketKey = toBracketKey(segment.segmentType, segment.paramName);\n const codec = globalCodecs[bracketKey];\n if (!codec) continue; // no codec in schema → keep raw string\n\n const key = segment.paramName;\n try {\n mergeTarget[key] = sanitizeParamValue(codec.parse(mergeTarget[key] as string | string[]));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isDebug()) {\n console.warn(\n `[timber] Global schema codec rejected value for param \"${key}\"\\n` +\n ` Error: ${message}\\n` +\n ` Raw value: ${JSON.stringify(mergeTarget[key])}\\n` +\n ` Hint: check the codec in app/schema.ts for key '${bracketKey}'.`\n );\n }\n throw new ParamCoercionError(message);\n }\n }\n return;\n }\n\n // Legacy path: per-segment params.ts coercion\n for (const segment of match.segments) {\n // Only process segments that have a params.ts convention file\n if (!segment.params) continue;\n\n let mod: Record<string, unknown>;\n try {\n mod = await loadModule(segment.params);\n } catch (err) {\n const message = `Failed to load params module for segment \"${segment.segmentName}\": ${err instanceof Error ? err.message : String(err)}`;\n if (isDebug()) {\n console.warn(\n `[timber] Param coercion error: ${message}\\n` +\n ` Segment: ${segment.segmentName}\\n` +\n ` Params file: ${segment.params}`\n );\n }\n throw new ParamCoercionError(message);\n }\n\n const segmentParamsDef = mod.segmentParams as\n | { parse(raw: Record<string, string | string[]>): Record<string, unknown> }\n | undefined;\n\n if (!segmentParamsDef || typeof segmentParamsDef.parse !== 'function') continue;\n\n try {\n const coerced = segmentParamsDef.parse(match.segmentParams);\n\n // Deep-sanitize codec output: every nested plain object becomes\n // null-prototype with dangerous keys stripped at every depth.\n // See TIM-873, design/13-security.md\n for (const key of Object.keys(coerced as Record<string, unknown>)) {\n if (key !== '__proto__') {\n mergeTarget[key] = sanitizeParamValue((coerced as Record<string, unknown>)[key]);\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isDebug()) {\n const rawKeys = Object.keys(match.segmentParams).join(', ');\n console.warn(\n `[timber] Param codec rejected values for segment \"${segment.segmentName}\"\\n` +\n ` Error: ${message}\\n` +\n ` Available raw params: { ${rawKeys} }\\n` +\n ` Params file: ${segment.params}\\n` +\n ` Hint: this usually means a codec threw for the raw URL value.\\n` +\n ` Check that the regex/schema accepts the actual path segment string.`\n );\n }\n throw new ParamCoercionError(message);\n }\n }\n}\n","/**\n * SegmentUpdateContext — React context for partial navigation updates.\n *\n * During partial navigation (server skips unchanged sync layouts), the\n * router builds a Map of segment path → ReactNode updates and passes it\n * as the context value. Mounted SegmentOutlet components read from this\n * context to decide whether to render the update or their cached content.\n *\n * SINGLETON GUARANTEE: Uses globalThis + Symbol.for — same pattern as\n * NavigationContext. The RSC client bundler can duplicate this module\n * across chunks (browser-entry graph + client-reference graph). With\n * ESM output, each chunk gets its own module scope — a bare createContext\n * at module level would create separate instances per chunk. globalThis\n * guarantees a single instance regardless of duplication.\n *\n * See design/19-client-navigation.md §\"Singleton Guarantee via globalThis\"\n */\n\n'use client';\n\nimport React, { type ReactNode } from 'react';\n\nexport const EMPTY_SEGMENT_UPDATES = new Map<string, ReactNode>();\n\nconst CTX_KEY = Symbol.for('__timber_segment_update_ctx');\n\nfunction getOrCreateContext(): React.Context<Map<string, ReactNode>> {\n const existing = (globalThis as Record<symbol, unknown>)[CTX_KEY] as\n | React.Context<Map<string, ReactNode>>\n | undefined;\n if (existing !== undefined) return existing;\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<Map<string, ReactNode>>(EMPTY_SEGMENT_UPDATES);\n (globalThis as Record<symbol, unknown>)[CTX_KEY] = ctx;\n return ctx;\n }\n // RSC environment — createContext not available. Return a dummy that\n // won't be used (outlets only render on the client).\n return undefined as unknown as React.Context<Map<string, ReactNode>>;\n}\n\nexport const SegmentUpdateContext = getOrCreateContext();\n","/**\n * SegmentOutlet — client component boundary at each layout segment.\n *\n * Each layout in the segment tree is wrapped with a SegmentOutlet that:\n * 1. Knows its segment path (prop from the server)\n * 2. Reads from SegmentUpdateContext for partial navigation updates\n * 3. Caches its rendered content in a ref across navigations\n *\n * On full navigation: receives new children via props, caches and renders them.\n * On partial navigation (this segment skipped): the context map has no entry\n * for this path, so the outlet returns cached content — preserving layout state.\n * On partial navigation (this segment updated): the context map has content\n * for this path, so the outlet renders the update.\n *\n * Uses React context instead of useSyncExternalStore to stay compatible\n * with concurrent rendering (transitions). All outlets re-render when the\n * context value changes, but each bails out quickly if its segment has\n * no update — same approach as Next.js LayoutRouter.\n *\n * Security: performance optimization only. The server always runs all\n * access.ts files regardless of segment skipping.\n * See design/13-security.md §\"State tree manipulation\".\n */\n\n'use client';\n\nimport { useContext, useRef, type ReactNode } from 'react';\nimport { SegmentUpdateContext } from './segment-update-context.js';\n\nexport interface SegmentOutletProps {\n /**\n * Unique identifier for this segment. For normal segments this is the\n * urlPath (e.g., \"/\", \"/dashboard\"). For route groups this includes the\n * group name (e.g., \"/(marketing)\") to distinguish siblings that share\n * the same urlPath. Must match the segmentId used in state-tree-diff.ts.\n */\n segmentPath: string;\n\n /** The segment's React subtree (layout + inner content). */\n children: ReactNode;\n}\n\n/**\n * Client component boundary at each layout segment in the element tree.\n *\n * On full navigation (context map is empty): renders children, caches in ref.\n * On partial navigation: checks the context map for an update at segmentPath.\n * - Update found → renders update, caches in ref.\n * - No update → returns cached content (layout state preserved).\n *\n * React preserves component instances across reactRoot.render() calls\n * when the same type appears at the same tree position. The ref persists\n * across navigations because SegmentOutlet is reconciled, not remounted.\n */\nexport function SegmentOutlet({ segmentPath, children }: SegmentOutletProps) {\n const updates = useContext(SegmentUpdateContext);\n const contentRef = useRef<ReactNode>(null);\n\n const update = updates.get(segmentPath);\n\n if (update !== undefined) {\n contentRef.current = update;\n return update;\n }\n\n if (contentRef.current === null) {\n contentRef.current = children;\n return children;\n }\n\n if (children !== contentRef.current) {\n contentRef.current = children;\n return children;\n }\n\n return contentRef.current;\n}\n","/**\n * Route Element Builder — constructs a React element tree from a matched route.\n *\n * Extracted from rsc-entry.ts to enable reuse by the revalidation renderer\n * (which needs the element tree without RSC serialization) and to keep\n * rsc-entry.ts under the 500-line limit.\n *\n * This module handles:\n * 1. Running access.ts checks eagerly to gate metadata resolution (TIM-1027)\n * 2. Loading page/layout components from the segment chain\n * 3. Resolving metadata (skipped for denied segments to prevent side effects)\n * 4. Building the React element tree (page → error boundaries → access gates → layouts)\n * 5. Resolving parallel slots\n *\n * See design/02-rendering-pipeline.md, design/04-authorization.md\n */\n\nimport { createElement } from 'react';\nimport { randomUUID } from 'node:crypto';\n\nimport { withSpan } from './tracing.js';\nimport type { RouteMatch } from './pipeline.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\nimport { resolveMetadata, renderMetadataToElements } from './metadata.js';\nimport type { Metadata } from './types.js';\nimport { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata-routes.js';\n\n// In dev mode, use a per-startup nonce for metadata route cache busting\n// instead of per-file content hashes (avoids rehashing on every request).\nlet _devNonce: string | undefined;\nfunction getDevNonce(): string {\n _devNonce ??= randomUUID().slice(0, 8);\n return _devNonce;\n}\nimport { DenySignal, RedirectSignal } from './primitives.js';\nimport { AccessGate } from './access-gate.js';\nimport {\n PageDenyBoundary,\n buildDenyPageChain,\n renderMatchingDenyPage,\n setDenyStatus,\n} from './deny-boundary.js';\nimport type { DenyPageEntry } from './deny-boundary.js';\nimport { resolveSlotElement } from './slot-resolver.js';\nimport { SegmentProvider } from '../client/segment-context.js';\nimport { SegmentOutlet } from '../client/segment-outlet.js';\n\nimport { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';\nimport type { InterceptionContext } from './pipeline.js';\nimport { shouldSkipSegment } from './state-tree-diff.js';\nimport { loadModule } from './safe-load.js';\n\n/**\n * Replace the outermost SegmentOutlet in a partial payload with its\n * SegmentProvider child. Walks through AccessGate wrappers (which sit\n * outside SegmentOutlet) to find the outlet.\n */\nfunction replaceOutermostSegmentOutlet(\n element: React.ReactElement,\n replacement: React.ReactElement\n): React.ReactElement {\n if (element.type === SegmentOutlet) {\n return replacement;\n }\n // AccessGate wraps outside SegmentOutlet — recurse through it.\n const props = element.props as Record<string, unknown>;\n if (element.type === AccessGate && props.children) {\n const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);\n /* eslint-disable react/no-children-prop -- createElement API */\n return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {\n ...props,\n children: inner,\n });\n /* eslint-enable react/no-children-prop */\n }\n return element;\n}\n\n// ─── Client Reference Detection ──────────────────────────────────────────\n\n/**\n * Symbol used by React Flight to mark client references.\n * Client references are proxy objects created by @vitejs/plugin-rsc for\n * 'use client' modules in the RSC environment. They must be passed to\n * createElement() — calling them as functions throws:\n * \"Unexpectedly client reference export 'default' is called on server\"\n */\nconst CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');\n\n/**\n * Detect whether a component is a React client reference.\n * Client references have $$typeof set to Symbol.for('react.client.reference')\n * by registerClientReference() in the React Flight server runtime.\n *\n * Used to skip OTEL tracing wrappers that would call the component as a\n * function. Client components must go through createElement only — they are\n * serialized as references in the RSC Flight stream, not executed on the server.\n */\nexport function isClientReference(component: unknown): boolean {\n return (\n component != null &&\n typeof component === 'function' &&\n (component as unknown as Record<string, unknown>).$$typeof === CLIENT_REFERENCE_TAG\n );\n}\n\n// ─── Param Coercion Error ─────────────────────────────────────────────────\n\n/**\n * Thrown when a defineSegmentParams codec's parse() fails.\n * The pipeline catches this and responds with 404.\n */\nexport class ParamCoercionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ParamCoercionError';\n }\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/** Head element for client-side metadata updates. */\nexport interface HeadElement {\n tag: string;\n content?: string;\n attrs?: Record<string, string | null>;\n}\n\n/** Layout entry with component and segment. */\nexport interface LayoutComponentEntry {\n component: (...args: unknown[]) => unknown;\n segment: ManifestSegmentNode;\n}\n\n/** Result of building a route element tree. */\nexport interface RouteElementResult {\n /** The React element tree (page wrapped in layouts, access gates, error boundaries). */\n element: React.ReactElement;\n /** Resolved head elements for metadata. */\n headElements: HeadElement[];\n /** Layout components loaded along the segment chain. */\n layoutComponents: LayoutComponentEntry[];\n /** Segments from the route match. */\n segments: ManifestSegmentNode[];\n /** Max deferSuspenseFor hold window across all segments. */\n deferSuspenseFor: number;\n /**\n * Segment paths that were skipped because the client already has them cached.\n * Ordered outermost to innermost. Empty when no segments were skipped.\n * The client uses this to merge the partial payload with cached segments.\n * See design/19-client-navigation.md §\"X-Timber-State-Tree Header\"\n */\n skippedSegments: string[];\n}\n\n// ─── Module Processing Helpers ─────────────────────────────────────────────\n\n/**\n * Reject the legacy `generateMetadata` export with a helpful migration message.\n * Throws if the module exports `generateMetadata` instead of `metadata`.\n */\nfunction rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {\n if ('generateMetadata' in mod) {\n throw new Error(\n `${filePath}: \"generateMetadata\" is not a valid export. ` +\n `Export an async function named \"metadata\" instead.\\n\\n` +\n ` // Before\\n` +\n ` export async function generateMetadata({ params }) { ... }\\n\\n` +\n ` // After\\n` +\n ` export async function metadata() { ... }`\n );\n }\n}\n\n/**\n * Extract and resolve metadata from a module (layout or page).\n * Handles both static metadata objects and async metadata functions.\n * Returns the resolved Metadata, or null if none exported.\n *\n * Metadata functions no longer receive { params } — they access params\n * via getSegmentParams() from ALS, same as page/layout components.\n */\nasync function extractMetadata(\n mod: Record<string, unknown>,\n segment: ManifestSegmentNode\n): Promise<Metadata | null> {\n if (typeof mod.metadata === 'function') {\n type MetadataFn = () => Promise<Metadata>;\n return (\n (await withSpan(\n 'timber.metadata',\n { 'timber.segment': segment.segmentName ?? segment.urlPath },\n () => (mod.metadata as MetadataFn)()\n )) ?? null\n );\n }\n if (mod.metadata) {\n return mod.metadata as Metadata;\n }\n return null;\n}\n\n/**\n * Extract `deferSuspenseFor` from a module and return the maximum\n * of the current value and the module's value.\n */\nfunction extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {\n if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {\n return mod.deferSuspenseFor;\n }\n return current;\n}\n\n// ─── Builder ──────────────────────────────────────────────────────────────\n\n/**\n * Build a React element tree from a matched route.\n *\n * Runs access checks eagerly to gate metadata resolution, then loads\n * modules, resolves metadata (skipping denied segments), and constructs\n * the element tree. DenySignal and RedirectSignal from access are stored\n * as AccessGate verdicts for synchronous replay during render.\n *\n * Does NOT serialize to RSC Flight — the caller decides whether to render\n * to a stream or use the element directly (e.g., for action revalidation).\n *\n * For passing segments, AccessGate re-runs access during render so that\n * React.cache is populated for layout dedup (TIM-662). For denied\n * segments, the pre-computed verdict is replayed — no re-execution,\n * no metadata side effects, no head leak (TIM-1027).\n */\nexport async function buildRouteElement(\n req: Request,\n match: RouteMatch,\n interception?: InterceptionContext,\n clientStateTree?: Set<string> | null,\n metadataRouteHashes?: Record<string, string>\n): Promise<RouteElementResult> {\n const segments = match.segments;\n\n // ── Access pre-pass ──────────────────────────────────────────────────\n // Run access checks eagerly to determine which segments are denied.\n // This gates metadata() resolution: denied segments' metadata functions\n // are never called, preventing unauthorized side effects (DB queries)\n // and metadata content leaking into the <head> of denied responses.\n //\n // Verdicts are passed to AccessGate for synchronous replay (DenySignal\n // renders the deny page in-tree; RedirectSignal propagates). For PASSING\n // segments, no verdict is passed — AccessGate re-runs access during\n // renderToReadableStream to populate React.cache for layout dedup.\n //\n // See TIM-1027, design/04-authorization.md, design/13-security.md.\n const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();\n let firstDeniedIndex = Infinity;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n if (!segment.access || i >= firstDeniedIndex) continue;\n\n try {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => Promise<void>) | undefined;\n if (accessFn) {\n await withSpan(\n 'timber.access.pre',\n { 'timber.segment': segment.segmentName ?? 'unknown' },\n () => accessFn()\n );\n }\n } catch (error: unknown) {\n if (error instanceof DenySignal || error instanceof RedirectSignal) {\n accessVerdicts.set(i, error);\n firstDeniedIndex = i;\n } else {\n throw error;\n }\n }\n }\n\n // ── Module loading + metadata resolution ─────────────────────────────\n const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];\n const layoutComponents: LayoutComponentEntry[] = [];\n let PageComponent: ((...args: unknown[]) => unknown) | null = null;\n let deferSuspenseFor = 0;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const isLeaf = i === segments.length - 1;\n const isDenied = i >= firstDeniedIndex;\n\n // Load layout\n if (segment.layout) {\n const mod = await loadModule(segment.layout);\n if (mod.default) {\n layoutComponents.push({\n component: mod.default as (...args: unknown[]) => unknown,\n segment,\n });\n }\n\n // Param coercion is handled in the pipeline (Stage 2c) before\n // middleware and rendering. See coerceSegmentParams() in pipeline.ts.\n\n rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);\n if (!isDenied) {\n const layoutMetadata = await extractMetadata(mod, segment);\n if (layoutMetadata) {\n metadataEntries.push({ metadata: layoutMetadata, isPage: false });\n }\n }\n deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);\n }\n\n // Load page (leaf segment only)\n if (isLeaf && segment.page) {\n const mod = await loadModule(segment.page);\n\n // Param coercion is handled in the pipeline (Stage 2c) before\n // middleware and rendering. See coerceSegmentParams() in pipeline.ts.\n\n if (mod.default) {\n PageComponent = mod.default as (...args: unknown[]) => unknown;\n }\n rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);\n if (!isDenied) {\n const pageMetadata = await extractMetadata(mod, segment);\n if (pageMetadata) {\n metadataEntries.push({ metadata: pageMetadata, isPage: true });\n }\n }\n deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);\n }\n }\n\n if (!PageComponent) {\n const segmentInfo = segments\n .map(\n (s, i) =>\n ` [${i}] ${s.segmentName} (page: ${s.page ? 'yes' : 'no'}, layout: ${s.layout ? 'yes' : 'no'}, children: ${s.children?.length ?? 0})${i === segments.length - 1 ? ' ← leaf' : ''}`\n )\n .join('\\n');\n throw new Error(\n `No page component found for route: ${new URL(req.url).pathname}\\nMatched segments:\\n${segmentInfo}`\n );\n }\n\n // Build deny page fallback chains for each segment position.\n // When AccessGate or PageDenyBoundary catches a DenySignal, they render\n // the matching deny page in-tree instead of throwing into React Flight.\n // The chain walks from the current segment outward to root, collecting\n // status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order.\n // See TIM-666.\n const denyPageChains = new Map<number, DenyPageEntry[]>();\n for (let i = 0; i < segments.length; i++) {\n const chain = await buildDenyPageChain(segments, i);\n if (chain.length > 0) {\n denyPageChains.set(i, chain);\n }\n }\n\n // Resolve metadata\n const resolvedMetadata = resolveMetadata(metadataEntries);\n const headElements = renderMetadataToElements(resolvedMetadata);\n\n // Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).\n // opengraph-image emits both og:image and twitter:image (no separate twitter-image convention).\n // Skip OG auto-linking when the user already declared images in metadata.\n // See design/16-metadata.md §\"Auto-Linking\"\n const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);\n const requestUrl = new URL(req.url);\n const requestPathname = requestUrl.pathname;\n // In dev mode, use the request origin so OG URLs resolve to localhost.\n // In production, use metadataBase (the canonical domain).\n const ogBase =\n process.env.NODE_ENV !== 'production'\n ? new URL(requestUrl.origin)\n : resolvedMetadata.metadataBase;\n\n for (let si = 0; si < segments.length; si++) {\n const segment = segments[si];\n if (!segment.metadataRoutes) continue;\n // Skip auto-linking for denied segments — same gate as metadata().\n if (si >= firstDeniedIndex) continue;\n for (const baseName of Object.keys(segment.metadataRoutes)) {\n const convention = METADATA_ROUTE_CONVENTIONS[baseName];\n if (!convention) continue;\n // Non-nestable routes only auto-link from root\n if (!convention.nestable && segment.urlPath !== '/') continue;\n // Skip auto-linking if user already declared the image in metadata\n if (convention.type === 'opengraph-image' && hasUserOgImage) continue;\n // Build the href using the actual request path (not the pattern with [param]).\n // For nestable routes, the metadata route sits under the same resolved path\n // as the page. For root-only routes, use '/'.\n const resolvedPrefix = convention.nestable\n ? requestPathname === '/'\n ? ''\n : requestPathname\n : '';\n let href = `${resolvedPrefix}/${convention.servePath}`;\n // Append cache-bust query param for image metadata routes\n if (convention.type === 'opengraph-image') {\n const metaFile = segment.metadataRoutes[baseName];\n const fileHash = metaFile?.filePath ? metadataRouteHashes?.[metaFile.filePath] : undefined;\n const cacheBust = fileHash ?? getDevNonce();\n href = `${href}?${cacheBust}`;\n }\n // Resolve to absolute URL for og:image/twitter:image (social crawlers need full URLs)\n if (ogBase && convention.type === 'opengraph-image') {\n href = new URL(href, ogBase).toString();\n }\n for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {\n if (autoLink.tag === 'link') {\n const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };\n if (autoLink.type) attrs.type = autoLink.type;\n headElements.push({ tag: 'link', attrs });\n } else {\n const attrs: Record<string, string> = { content: autoLink.content };\n if (autoLink.property) attrs.property = autoLink.property;\n if (autoLink.name) attrs.name = autoLink.name;\n headElements.push({ tag: 'meta', attrs });\n }\n }\n }\n }\n\n // Build element tree: page wrapped in layouts (innermost to outermost)\n const h = createElement as (...args: unknown[]) => React.ReactElement;\n\n // Build the page element.\n // Client references ('use client' pages) must NOT be called as functions —\n // they are proxy objects that throw when invoked. They must go through\n // createElement only, which serializes them as client references in the\n // RSC Flight stream. OTEL tracing is skipped for client components.\n // See TIM-627 for the original bug.\n // Build the page element.\n // Server component pages are wrapped in PageDenyBoundary which calls\n // them as async functions and catches DenySignal — rendering the deny\n // page in-tree instead of throwing into React Flight. This eliminates\n // the second render pass for deny pages. See TIM-666.\n //\n // Client reference pages ('use client') can't call deny() (server-only),\n // so they go through createElement normally — no wrapper needed.\n const leafIndex = segments.length - 1;\n const leafDenyPages = denyPageChains.get(leafIndex);\n let element: React.ReactElement;\n if (isClientReference(PageComponent)) {\n element = h(PageComponent, {});\n } else if (leafDenyPages && leafDenyPages.length > 0) {\n // Server component page WITH deny page chain — wrap in PageDenyBoundary\n element = h(PageDenyBoundary, {\n Page: PageComponent,\n route: match.segments[leafIndex]?.urlPath ?? '/',\n denyPages: leafDenyPages,\n });\n } else {\n // Server component page WITHOUT deny page chain — trace only\n const TracedPage = async (props: Record<string, unknown>) => {\n return withSpan(\n 'timber.page',\n { 'timber.route': match.segments[leafIndex]?.urlPath ?? '/' },\n () => (PageComponent as (props: Record<string, unknown>) => unknown)(props)\n );\n };\n element = h(TracedPage, {});\n }\n\n // Build a lookup of layout components by segment for O(1) access.\n const layoutBySegment = new Map(\n layoutComponents.map(({ component, segment }) => [segment, component])\n );\n\n // Track which segments were skipped for the X-Timber-Skipped-Segments header.\n // The client uses this to merge the partial payload with its cached segments.\n const skippedSegments: string[] = [];\n\n // Wrap from innermost (leaf) to outermost (root), processing every\n // segment in the chain. Each segment may contribute:\n // 1. Error boundaries (status files + error.tsx)\n // 2. Layout component — wraps children + parallel slots\n // 3. SegmentProvider — records position for useSelectedLayoutSegment\n //\n // When clientStateTree is provided (from X-Timber-State-Tree header on\n // client navigation), sync layouts the client already has are skipped.\n // Access.ts was pre-checked eagerly above for metadata gating (TIM-1027).\n // See design/19-client-navigation.md §\"X-Timber-State-Tree Header\"\n //\n // hasRenderedLayoutBelow tracks whether a non-skipped layout has been\n // seen below the current segment. A segment can ONLY be skipped if\n // there is a rendered layout below it — the client merger can only\n // replace inner SegmentProviders (client component boundaries), not\n // page content embedded in a layout's server-rendered output.\n // Without this guard, skipping the innermost layout causes the merger\n // to drop the layout entirely and replace it with just the page.\n let hasRenderedLayoutBelow = false;\n let outermostSegmentProvider: React.ReactElement | null = null;\n // Track whether any rendered inner layout also exists in the client's\n // state tree. This prevents cross-section skipping: e.g., navigating\n // from /(group-a) to /dashboard shouldn't skip root \"/\" because the\n // client has no mounted outlet at \"/dashboard\".\n let innerRenderedInClientTree = false;\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n const isLeaf = i === segments.length - 1;\n const layoutComponent = layoutBySegment.get(segment);\n\n // Check if this segment's layout can be skipped for partial rendering.\n // Skipped segments: no layout wrapping, no error boundaries, no slots,\n // no AccessGate in element tree (access already ran pre-render).\n //\n // Additional constraints beyond shouldSkipSegment:\n // - Must have a rendered layout below (so the merger can find an\n // inner SegmentProvider to splice the new content into)\n // - Route groups are never skipped because sibling groups share the\n // same urlPath (e.g., /(marketing) and /(app) both have \"/\"),\n // which would cause the wrong cached layout to be reused\n // - At least one inner rendered layout must exist in the client's\n // state tree, ensuring the client has a mounted outlet to receive\n // the partial payload\n const skip =\n shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&\n hasRenderedLayoutBelow &&\n segment.segmentType !== 'group' &&\n innerRenderedInClientTree;\n\n if (skip) {\n // Skip this segment's layout/error boundaries — the client uses its cached version.\n // Metadata was already resolved above (head elements are correct).\n // Record for X-Timber-Skipped-Segments header (outermost first, so prepend).\n skippedSegments.unshift(segment.urlPath);\n\n // SECURITY: Even though the layout is skipped, AccessGate MUST still\n // wrap the element tree. access.ts runs on every navigation regardless\n // of cached layouts or state tree content.\n // See design/13-security.md §\"Auth always runs\" (test #11).\n if (segment.access) {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => unknown) | undefined;\n if (accessFn) {\n // Pass verdict for denied/redirected segments so AccessGate replays\n // without re-execution. Passing segments omit verdict so AccessGate\n // re-runs access during render for React.cache population.\n const verdict = accessVerdicts.get(i);\n element = h(AccessGate, {\n accessFn,\n segmentName: segment.segmentName,\n denyPages: denyPageChains.get(i),\n ...(verdict ? { verdict } : {}),\n children: element,\n });\n }\n }\n\n continue;\n }\n\n // This segment is rendered — mark that future (outer) segments have\n // a rendered layout below them and can safely be skipped.\n if (layoutComponent) {\n hasRenderedLayoutBelow = true;\n // Use segmentId for the client state tree check. Route groups share\n // their parent's urlPath (both \"/\"), but their segmentId includes\n // the group name (e.g., \"/(group-a)\"), so the check correctly\n // fails when the client has never visited that group.\n const outletKey =\n segment.segmentType === 'group'\n ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`\n : segment.urlPath;\n if (clientStateTree?.has(outletKey)) {\n innerRenderedInClientTree = true;\n }\n }\n\n // Wrap with error boundaries from this segment (inside layout).\n // Keep ALL error boundaries (including 4xx) — they're the safety net for\n // DenySignal from nested server components that escape AccessGate/PageDenyBoundary\n // try/catch. Status-code files must be 'use client' TSX or MDX to serialize\n // as error boundary fallbacks. See TIM-666.\n element = await wrapSegmentWithErrorBoundaries(segment, element, h);\n\n // Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.\n // When AccessGate denies, the layout never renders. The deny page appears\n // at the AccessGate level, wrapped by PARENT layouts only.\n // This prevents leaking layout UI (sidebars, nav) on denied pages.\n // See design/04-authorization.md §\"Access Failure\".\n if (layoutComponent) {\n // Resolve parallel slots for this layout.\n // Compute the parent tree path so slots can build their full\n // segment path for per-slot param storage in ALS.\n const parentTreePath =\n '/' +\n segments\n .slice(0, i + 1)\n .map((s) => s.segmentName)\n .filter(Boolean)\n .join('/');\n const slotProps: Record<string, unknown> = {};\n const slotEntries = Object.entries(segment.slots ?? {});\n for (const [slotName, slotNode] of slotEntries) {\n slotProps[slotName] = await resolveSlotElement(\n slotNode as ManifestSegmentNode,\n match,\n h,\n interception,\n parentTreePath\n );\n }\n\n // '/'.split('/') → ['', ''] (length 2), but root should be [''] (length 1).\n // The extra empty string makes useSelectedLayoutSegment skip the first URL segment.\n const rawPath = segment.urlPath.split('/');\n const segmentPath = rawPath.length === 2 && rawPath[1] === '' ? [''] : rawPath;\n const parallelRouteKeys = Object.keys(segment.slots ?? {});\n\n // For route groups, urlPath is shared with the parent (both \"/\"),\n // so include the group name to distinguish them. Used for both OTEL\n // span labels and client-side element caching (segmentId).\n const segmentId =\n segment.segmentType === 'group'\n ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`\n : segment.urlPath;\n\n // Build the layout element.\n // Same client reference guard as pages — client layouts must not be\n // called as functions. OTEL tracing is skipped for client components.\n let layoutElement: React.ReactElement;\n if (isClientReference(layoutComponent)) {\n layoutElement = h(layoutComponent, {\n ...slotProps,\n children: element,\n });\n } else {\n // Server component layout — wrap with OTEL tracing AND DenySignal\n // catching. If the layout calls deny(), the signal is caught here\n // and the matching deny page renders in-tree (same pattern as\n // AccessGate and PageDenyBoundary). Without this, DenySignal\n // escapes to React Flight onError and triggers the re-render\n // fallback path. See TIM-668, design/04-authorization.md.\n const layoutComponentRef = layoutComponent;\n const layoutDenyPages = denyPageChains.get(i);\n const TracedLayout = async (props: Record<string, unknown>) => {\n try {\n return await withSpan('timber.layout', { 'timber.segment': segmentId }, () =>\n (layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)\n );\n } catch (error: unknown) {\n if (error instanceof DenySignal && layoutDenyPages) {\n const denyElement = renderMatchingDenyPage(layoutDenyPages, error.status, error.data);\n if (denyElement) {\n setDenyStatus(error.status);\n return denyElement;\n }\n }\n // Non-deny errors (RedirectSignal, runtime errors) propagate normally.\n throw error;\n }\n };\n layoutElement = h(TracedLayout, {\n ...slotProps,\n children: element,\n });\n }\n\n const segmentProviderElement = h(SegmentProvider, {\n segments: segmentPath,\n segmentId,\n parallelRouteKeys,\n children: layoutElement,\n });\n\n element = h(SegmentOutlet, {\n segmentPath: segmentId,\n children: segmentProviderElement,\n });\n\n // Track the SegmentProvider for the outermost rendered layout.\n // On partial navigation (segments skipped above), the client already\n // has a SegmentOutlet mounted at this position. Sending another one\n // in the payload causes double-wrapping and infinite context recursion.\n // We'll strip the outermost SegmentOutlet after the loop.\n outermostSegmentProvider = segmentProviderElement;\n }\n\n // Wrap in AccessGate OUTSIDE the layout.\n // If access denies, the deny page renders here — the layout above\n // never executes. Parent layouts (from outer iterations) form the shell.\n // See TIM-662, TIM-666, design/04-authorization.md §\"Access Failure\".\n if (segment.access) {\n const accessMod = await loadModule(segment.access);\n const accessFn = accessMod.default as (() => unknown) | undefined;\n if (accessFn) {\n // Pass verdict for denied/redirected segments so AccessGate replays\n // without re-execution. Passing segments omit verdict so AccessGate\n // re-runs access during render for React.cache population.\n const verdict = accessVerdicts.get(i);\n element = h(AccessGate, {\n accessFn,\n segmentName: segment.segmentName,\n denyPages: denyPageChains.get(i),\n ...(verdict ? { verdict } : {}),\n children: element,\n });\n }\n }\n }\n\n // On partial navigation (some segments skipped), the client already has a\n // SegmentOutlet mounted at the outermost non-skipped segment's position.\n // Sending another SegmentOutlet in the payload causes double-wrapping —\n // the inner outlet reads the same context update and recurses infinitely.\n // Replace the outermost SegmentOutlet with just its SegmentProvider child.\n if (skippedSegments.length > 0 && outermostSegmentProvider) {\n element = replaceOutermostSegmentOutlet(element, outermostSegmentProvider);\n }\n\n return {\n element,\n headElements: headElements as HeadElement[],\n layoutComponents,\n segments,\n deferSuspenseFor,\n skippedSegments,\n };\n}\n","/**\n * Version Skew Detection — graceful recovery when stale clients hit new deployments.\n *\n * When a new version of the app is deployed, clients with open tabs still have\n * the old JavaScript bundle. Without version skew handling, these stale clients\n * will experience:\n *\n * 1. Server action calls that crash (action IDs are content-hashed)\n * 2. Chunk load failures (old filenames gone from CDN)\n * 3. RSC payload mismatches (component references differ between builds)\n *\n * This module implements deployment ID comparison:\n * - A per-build deployment ID is generated at build time (see build-manifest.ts)\n * - The client sends it via `X-Timber-Deployment-Id` header on every RSC/action request\n * - The server compares it against the current build's ID\n * - On mismatch: signal the client to reload (not crash)\n *\n * The deployment ID is always-on in production. Dev mode skips the check\n * (HMR handles code updates without full reloads).\n *\n * See design/25-production-deployments.md, TIM-446\n */\n\n// ─── Constants ───────────────────────────────────────────────────\n\n/** Header sent by the client with every RSC/action request. */\nexport const DEPLOYMENT_ID_HEADER = 'X-Timber-Deployment-Id';\n\n/** Response header that signals the client to do a full page reload. */\nexport const RELOAD_HEADER = 'X-Timber-Reload';\n\n// ─── Deployment ID ───────────────────────────────────────────────\n\n/**\n * The current build's deployment ID. Set at startup from the manifest init\n * module (globalThis.__TIMBER_DEPLOYMENT_ID__). Null in dev mode.\n */\nlet currentDeploymentId: string | null = null;\n\n/**\n * Set the current deployment ID. Called once at server startup from the\n * manifest init module. In dev mode this is never called (deployment ID\n * checks are skipped).\n */\nexport function setDeploymentId(id: string): void {\n currentDeploymentId = id;\n}\n\n/**\n * Get the current deployment ID. Returns null in dev mode.\n */\nexport function getDeploymentId(): string | null {\n return currentDeploymentId;\n}\n\n// ─── Skew Detection ──────────────────────────────────────────────\n\n/** Result of a version skew check. */\nexport interface SkewCheckResult {\n /** Whether the client's deployment ID matches the server's. */\n ok: boolean;\n /** The client's deployment ID (null if header not sent — e.g., initial page load). */\n clientId: string | null;\n}\n\n/**\n * Check if a request's deployment ID matches the current build.\n *\n * Returns `{ ok: true }` when:\n * - Dev mode (no deployment ID set — HMR handles updates)\n * - No deployment ID header (initial page load, non-RSC request)\n * - Deployment IDs match\n *\n * Returns `{ ok: false }` when:\n * - Client sends a deployment ID that differs from the current build\n */\nexport function checkVersionSkew(req: Request): SkewCheckResult {\n // Dev mode — no deployment ID checks (HMR handles updates)\n if (!currentDeploymentId) {\n return { ok: true, clientId: null };\n }\n\n const clientId = req.headers.get(DEPLOYMENT_ID_HEADER);\n\n // No header — initial page load or non-RSC request. Always OK.\n if (!clientId) {\n return { ok: true, clientId: null };\n }\n\n // Compare deployment IDs\n if (clientId === currentDeploymentId) {\n return { ok: true, clientId };\n }\n\n return { ok: false, clientId };\n}\n\n/**\n * Apply version skew reload headers to a response.\n * Sets X-Timber-Reload: 1 to signal the client to do a full page reload.\n */\nexport function applyReloadHeaders(headers: Headers): void {\n headers.set(RELOAD_HEADER, '1');\n}\n","/**\n * Metadata route helpers for the request pipeline.\n *\n * Handles serving static metadata files and serializing sitemap responses.\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\n\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Content types that are text-based and should include charset=utf-8.\n * Binary formats (images) should not include charset.\n */\nconst TEXT_CONTENT_TYPES = new Set([\n 'application/xml',\n 'text/plain',\n 'application/json',\n 'application/manifest+json',\n 'image/svg+xml',\n]);\n\n/**\n * Serve a static metadata file by reading it from disk.\n *\n * Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)\n * are served as-is with the appropriate Content-Type header.\n * Text files include charset=utf-8; binary files do not.\n *\n * See design/16-metadata.md §\"Metadata Routes\"\n */\nexport async function serveStaticMetadataFile(\n metaMatch: import('./route-matcher.js').MetadataRouteMatch\n): Promise<Response> {\n const { contentType, file } = metaMatch;\n const isText = TEXT_CONTENT_TYPES.has(contentType);\n\n const body = await readFile(file.filePath);\n\n const headers: Record<string, string> = {\n 'Content-Type': isText ? `${contentType}; charset=utf-8` : contentType,\n 'Content-Length': String(body.byteLength),\n };\n\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serialize a sitemap array to XML.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemap(\n entries: Array<{\n url: string;\n lastModified?: string | Date;\n changeFrequency?: string;\n priority?: number;\n }>\n): string {\n const urls = entries\n .map((e) => {\n let xml = ` <url>\\n <loc>${escapeXml(e.url)}</loc>`;\n if (e.lastModified) {\n const date = e.lastModified instanceof Date ? e.lastModified.toISOString() : e.lastModified;\n xml += `\\n <lastmod>${escapeXml(date)}</lastmod>`;\n }\n if (e.changeFrequency) {\n xml += `\\n <changefreq>${escapeXml(e.changeFrequency)}</changefreq>`;\n }\n if (e.priority !== undefined) {\n xml += `\\n <priority>${e.priority}</priority>`;\n }\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`;\n}\n\n/**\n * Serialize a sitemap index (list of sub-sitemap URLs) to XML.\n * Used for pagination when the total URL count exceeds 50,000.\n * Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html\n */\nexport function serializeSitemapIndex(sitemapUrls: string[]): string {\n const sitemaps = sitemapUrls\n .map((url) => ` <sitemap>\\n <loc>${escapeXml(url)}</loc>\\n </sitemap>`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${sitemaps}\\n</sitemapindex>`;\n}\n\n/** Escape special XML characters. */\nexport function escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * Interception route matching for the request pipeline.\n *\n * Matches target URLs against interception rewrites to support the\n * modal route pattern (soft navigation intercepts).\n *\n * Extracted from pipeline.ts to keep files under 500 lines.\n *\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n\nimport { classifyUrlSegment } from '../routing/segment-classify.js';\n\n/** Result of a successful interception match. */\nexport interface InterceptionMatchResult {\n /** The pathname to re-match (the source/intercepting route's parent). */\n sourcePathname: string;\n}\n\n/**\n * Check if a pathname starts with a prefix on a segment boundary.\n *\n * Prevents /feed from matching /feed-private — the prefix must be\n * followed by '/' or be an exact match.\n */\nfunction hasSegmentPrefix(pathname: string, prefix: string): boolean {\n if (prefix === '/') return true;\n if (!pathname.startsWith(prefix)) return false;\n return pathname.length === prefix.length || pathname[prefix.length] === '/';\n}\n\n/**\n * Check if an intercepting route applies for this soft navigation.\n *\n * Matches the target pathname against interception rewrites, constrained\n * by the source URL (X-Timber-URL header — where the user navigates FROM).\n *\n * Returns the source pathname to re-match if interception applies, or null.\n */\nexport function findInterceptionMatch(\n targetPathname: string,\n sourceUrl: string,\n rewrites: import('../routing/interception.js').InterceptionRewrite[]\n): InterceptionMatchResult | null {\n for (const rewrite of rewrites) {\n // Check if the source URL starts with the intercepting prefix,\n // enforcing segment boundary to prevent /feed matching /feed-private.\n if (!hasSegmentPrefix(sourceUrl, rewrite.interceptingPrefix)) continue;\n\n // Check if the target URL matches the intercepted pattern.\n // Dynamic segments in the pattern match any single URL segment.\n if (pathnameMatchesPattern(targetPathname, rewrite.interceptedPattern)) {\n return { sourcePathname: rewrite.interceptingPrefix };\n }\n }\n return null;\n}\n\n/**\n * Check if a pathname matches a URL pattern with dynamic segments.\n *\n * Supports [param] (single segment) and [...param] (one or more segments).\n * Static segments must match exactly.\n */\nexport function pathnameMatchesPattern(pathname: string, pattern: string): boolean {\n const pathParts = pathname === '/' ? [] : pathname.slice(1).split('/');\n const patternParts = pattern === '/' ? [] : pattern.slice(1).split('/');\n\n let pi = 0;\n for (let i = 0; i < patternParts.length; i++) {\n const seg = classifyUrlSegment(patternParts[i]);\n\n switch (seg.kind) {\n case 'catch-all':\n return pi < pathParts.length;\n case 'optional-catch-all':\n return true;\n case 'dynamic':\n if (pi >= pathParts.length) return false;\n pi++;\n continue;\n case 'static':\n if (pi >= pathParts.length || pathParts[pi] !== seg.value) return false;\n pi++;\n continue;\n }\n }\n\n return pi === pathParts.length;\n}\n","/**\n * Pipeline outcome translator — converts a `PhaseOutcome` (the value\n * each phase function returns) into a final `Response`.\n *\n * Lifted out of `pipeline-phases.ts` (TIM-853) so the per-phase try /\n * catch logic and the terminal Response-building logic each live in\n * their own file. The phases produce values; this module is the single\n * source of truth for how those values become wire responses.\n *\n * See design/07-routing.md §\"Request Lifecycle\".\n */\n\nimport {\n applyCookieJar,\n buildRedirectResponse,\n cloneWithMutableHeaders,\n fireOnRequestError,\n mergeMissingHeaders,\n} from './pipeline-helpers.js';\nimport {\n logProxyError,\n logMiddlewareError,\n logMiddlewareShortCircuit,\n logRenderError,\n} from './logger.js';\nimport { markResponseFlushed } from './request-context.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { isDebug } from './debug.js';\nimport type { PipelineConfig, RouteMatch } from './pipeline.js';\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction rscErrorResponse(isRsc: boolean, status: number, headers?: Headers): Response {\n if (!isRsc) return new Response(null, { status });\n const h = headers ?? new Headers();\n h.set('X-Timber-Error', '1');\n h.set('content-type', 'application/json; charset=utf-8');\n return new Response(JSON.stringify({ error: true, status }), { status, headers: h });\n}\n\n// ─── Phase Outcome ─────────────────────────────────────────────────────────\n\nexport type PhaseName = 'proxy' | 'middleware' | 'render';\n\nexport type PhaseOutcome =\n | { kind: 'response'; phase: PhaseName; response: Response }\n | { kind: 'redirect'; phase: PhaseName; signal: RedirectSignal }\n | { kind: 'deny'; phase: PhaseName; signal: DenySignal }\n | { kind: 'error'; phase: PhaseName; error: unknown };\n\nexport interface OutcomeContext {\n req: Request;\n method: string;\n path: string;\n responseHeaders?: Headers;\n match?: RouteMatch;\n}\n\n// ─── Translator ────────────────────────────────────────────────────────────\n\n/**\n * Terminal outcome handler — converts a `PhaseOutcome` into a final\n * `Response`, applying cookies, building redirects, rendering deny pages\n * and fallback error pages, and firing instrumentation hooks.\n *\n * This is the single source of truth for how phase outputs become wire\n * responses; the per-phase try/catch blocks now produce values, not\n * Responses, so the conversion logic lives in exactly one place.\n */\nexport async function outcomeToResponse(\n config: PipelineConfig,\n outcome: PhaseOutcome,\n ctx: OutcomeContext\n): Promise<Response> {\n switch (outcome.kind) {\n case 'response': {\n // Clone unconditionally so downstream code (cookie/header merge,\n // Server-Timing in createPipeline) can write headers without paying\n // for a try/catch immutability probe per request. User middleware,\n // proxy, and route code may all return `Response.redirect()` or\n // platform-level responses with frozen header bags. See TIM-866.\n const finalResponse = cloneWithMutableHeaders(outcome.response);\n\n if (outcome.phase === 'proxy') return finalResponse;\n\n if (outcome.phase === 'middleware' && ctx.responseHeaders) {\n applyCookieJar(finalResponse.headers);\n mergeMissingHeaders(finalResponse.headers, ctx.responseHeaders);\n logMiddlewareShortCircuit({\n method: ctx.method,\n path: ctx.path,\n status: finalResponse.status,\n });\n }\n\n if (outcome.phase === 'render') {\n markResponseFlushed();\n }\n\n return finalResponse;\n }\n\n case 'redirect': {\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n return buildRedirectResponse(outcome.signal, ctx.req, headers);\n }\n\n case 'deny': {\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n if (config.renderDenyFallback) {\n try {\n // Clone user-supplied deny-page responses so downstream\n // Server-Timing writes are safe against frozen header bags\n // (e.g. user returned Response.redirect from the hook).\n return cloneWithMutableHeaders(\n await config.renderDenyFallback(outcome.signal, ctx.req, headers, ctx.match)\n );\n } catch (denyRenderError) {\n // Deny page rendering failed — log before falling through to bare response.\n // Without this, a crashing deny page produces a blank response with zero\n // server-side signal. See TIM-876.\n logRenderError({ method: ctx.method, path: ctx.path, error: denyRenderError });\n await fireOnRequestError(denyRenderError, ctx.req, 'render');\n if (config.onPipelineError && denyRenderError instanceof Error)\n config.onPipelineError(denyRenderError, 'render');\n }\n }\n if (isDebug()) {\n console.warn(\n `[timber] DenySignal(${outcome.signal.status}) from ${outcome.phase} phase — ` +\n `no renderDenyFallback configured, returning bare ${outcome.signal.status} response\\n` +\n ` Request: ${ctx.method} ${ctx.path}\\n` +\n ` Add a not-found.tsx or error.tsx to render a custom deny page.`\n );\n }\n return new Response(null, { status: outcome.signal.status, headers });\n }\n\n case 'error': {\n // RSC payload requests (client navigation) expect Flight data, not HTML.\n // Signal the error via X-Timber-Error so the client hard-navigates\n // to the server-rendered error page instead of feeding HTML to the\n // Flight decoder (which crashes with \"enqueueModel is not a function\").\n const isRsc = (ctx.req.headers.get('Accept') ?? '').includes('text/x-component');\n\n if (outcome.phase === 'proxy') {\n logProxyError({ error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'proxy');\n if (config.onPipelineError && outcome.error instanceof Error)\n config.onPipelineError(outcome.error, 'proxy');\n return rscErrorResponse(isRsc, 500);\n }\n\n if (outcome.phase === 'middleware') {\n logMiddlewareError({ method: ctx.method, path: ctx.path, error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'handler');\n if (config.onPipelineError && outcome.error instanceof Error) {\n config.onPipelineError(outcome.error, 'middleware');\n }\n return rscErrorResponse(isRsc, 500);\n }\n\n const headers = ctx.responseHeaders ?? new Headers();\n applyCookieJar(headers);\n logRenderError({ method: ctx.method, path: ctx.path, error: outcome.error });\n await fireOnRequestError(outcome.error, ctx.req, 'render');\n if (config.onPipelineError && outcome.error instanceof Error)\n config.onPipelineError(outcome.error, 'render');\n\n if (isRsc) {\n return rscErrorResponse(true, 500, headers);\n }\n\n if (config.renderFallbackError) {\n try {\n // Clone user-supplied fallback error responses so downstream\n // Server-Timing writes are safe against frozen header bags.\n return cloneWithMutableHeaders(\n await config.renderFallbackError(outcome.error, ctx.req, headers)\n );\n } catch (fallbackRenderError) {\n // Fallback rendering itself failed — log the secondary error before\n // falling through to bare 500. The original render error was already\n // logged above; this captures the fallback renderer's own crash so it\n // doesn't vanish silently. See TIM-876.\n logRenderError({ method: ctx.method, path: ctx.path, error: fallbackRenderError });\n await fireOnRequestError(fallbackRenderError, ctx.req, 'render');\n if (config.onPipelineError && fallbackRenderError instanceof Error)\n config.onPipelineError(fallbackRenderError, 'render');\n }\n }\n return new Response(null, { status: 500 });\n }\n }\n}\n","/**\n * Pipeline phase functions — module-level free functions that take their\n * dependencies as explicit parameters. Each phase returns a `PhaseOutcome`\n * (a discriminated union over response / redirect / deny / error) defined\n * in `pipeline-outcome.ts`. The terminal `outcomeToResponse` (also in\n * `pipeline-outcome.ts`) translates outcomes into Responses.\n *\n * Lifted out of `createPipeline` so each phase can be unit-tested in\n * isolation. The lift is mechanical — these functions used to be closures\n * over `config`; they now take `config` as an explicit parameter.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\".\n */\n\nimport { canonicalize } from './canonicalize.js';\nimport { runProxy } from './proxy.js';\nimport { runMiddlewareChain, shouldBypassMiddleware } from './middleware-runner.js';\nimport { withTiming } from './server-timing.js';\nimport {\n applyRequestHeaderOverlay,\n setMutableCookieContext,\n setSegmentParams,\n setMatchedSegmentPath,\n} from './request-context.js';\nimport { withSpan } from './tracing.js';\nimport { logRenderError } from './logger.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\nimport { ParamCoercionError } from './route-element-builder.js';\nimport { checkVersionSkew, applyReloadHeaders } from './version-skew.js';\nimport { serveStaticMetadataFile, serializeSitemap } from './pipeline-metadata.js';\nimport { loadModule } from './safe-load.js';\nimport { findInterceptionMatch } from './pipeline-interception.js';\nimport { applyCookieJar, cloneWithMutableHeaders, type ProxyResolver } from './pipeline-helpers.js';\nimport { coerceSegmentParams } from './param-coercion.js';\nimport { outcomeToResponse, type PhaseOutcome } from './pipeline-outcome.js';\nimport type { InterceptionContext, PipelineConfig, RouteMatch } from './pipeline.js';\nimport type { MetadataHandler, MetadataRoute, MiddlewareContext } from './types.js';\nimport { swallow } from './logger.js';\nimport { isDebug } from './debug.js';\n\ninterface RenderContext {\n canonicalPathname: string;\n interception?: InterceptionContext;\n}\n\n/**\n * Validate and canonicalize the X-Timber-URL header value.\n *\n * Returns the canonical pathname if valid, or null if rejected:\n * - Must start with '/' (relative pathname, no scheme/authority)\n * - Must not contain control characters\n * - Must pass canonicalization (no encoded separators, null bytes, etc.)\n */\nfunction validateInterceptionHeader(raw: string, stripTrailingSlash: boolean): string | null {\n if (!raw.startsWith('/')) return null;\n if (raw.startsWith('//')) return null;\n for (let i = 0; i < raw.length; i++) {\n const code = raw.charCodeAt(i);\n if (code <= 0x1f || code === 0x7f) return null;\n }\n const result = canonicalize(raw, stripTrailingSlash);\n if (!result.ok) return null;\n return result.pathname;\n}\n\n// ─── Phase: Proxy ──────────────────────────────────────────────────────────\n\n/**\n * Run the proxy.ts phase. Calls user proxy code and uses `handleRequest` as\n * the inner `next()` continuation. The proxy resolver was picked at pipeline\n * construction time so the hot path sees no per-request branching on the\n * `ProxyConfig` discriminant.\n */\nexport async function runProxyPhase(\n config: PipelineConfig,\n getProxy: ProxyResolver,\n req: Request,\n method: string,\n path: string\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n try {\n const proxyExport = await getProxy();\n const proxyFn = () =>\n runProxy(proxyExport, req, () => handleRequest(config, req, method, path, true));\n const response = await withSpan('timber.proxy', {}, () =>\n detailed ? withTiming('proxy', 'proxy.ts', proxyFn) : proxyFn()\n );\n return { kind: 'response', phase: 'proxy', response };\n } catch (error) {\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'proxy', signal: error };\n }\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'proxy', signal: error };\n }\n return { kind: 'error', phase: 'proxy', error };\n }\n}\n\n// ─── Phase: Middleware ─────────────────────────────────────────────────────\n\n/**\n * Run the middleware chain phase. If the chain short-circuits with a Response,\n * returns it as a 'response' outcome. Otherwise applies the request header\n * overlay and falls through to the render phase.\n */\nexport async function runMiddlewarePhase(\n config: PipelineConfig,\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n renderContext: RenderContext\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n const ctx: MiddlewareContext = {\n req,\n requestHeaders: requestHeaderOverlay,\n headers: responseHeaders,\n segmentParams: match.segmentParams,\n earlyHints: (hints) => {\n for (const hint of hints) {\n // Match Cloudflare's cached Early Hints attribute order: `as` before `rel`.\n // Cloudflare caches Link headers and re-emits them on subsequent 200s.\n // If our order differs, the browser sees duplicate preloads and warns.\n let value: string;\n if (hint.as !== undefined) {\n value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;\n } else {\n value = `<${hint.href}>; rel=${hint.rel}`;\n }\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n responseHeaders.append('Link', value);\n }\n },\n };\n\n try {\n const chainFn = () => runMiddlewareChain(match.middlewareChain, ctx);\n // Enable cookie mutation during middleware (design/29-cookies.md §\"Context Tracking\")\n const middlewareResponse = await (async () => {\n setMutableCookieContext(true);\n try {\n return await withSpan('timber.middleware', {}, () =>\n detailed ? withTiming('mw', 'middleware.ts', chainFn) : chainFn()\n );\n } finally {\n setMutableCookieContext(false);\n }\n })();\n if (middlewareResponse) {\n return { kind: 'response', phase: 'middleware', response: middlewareResponse };\n }\n // Middleware chain completed without short-circuiting — apply any\n // injected request headers so getHeaders() returns them downstream.\n applyRequestHeaderOverlay(requestHeaderOverlay);\n\n // Apply cookie jar to response headers before render commits them.\n // This preserves the historical ordering where middleware cookie writes\n // are visible to route-handler header merging, while handler Set-Cookie\n // values still come after middleware cookies and therefore take precedence.\n applyCookieJar(responseHeaders);\n\n return runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, renderContext);\n } catch (error) {\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'middleware', signal: error };\n }\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'middleware', signal: error };\n }\n return { kind: 'error', phase: 'middleware', error };\n }\n}\n\n// ─── Phase: Render ─────────────────────────────────────────────────────────\n\n/**\n * Run the render phase. Wraps the configured renderer in a span and a\n * timing scope, and translates thrown signals into outcome variants.\n */\nexport async function runRenderPhase(\n config: PipelineConfig,\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n { canonicalPathname, interception }: RenderContext\n): Promise<PhaseOutcome> {\n const detailed = config.serverTiming === 'detailed';\n try {\n const renderFn = () =>\n config.render(req, match, responseHeaders, requestHeaderOverlay, interception);\n const response = await withSpan('timber.render', { 'http.route': canonicalPathname }, () =>\n detailed ? withTiming('render', 'RSC + SSR render', renderFn) : renderFn()\n );\n return { kind: 'response', phase: 'render', response };\n } catch (error) {\n if (error instanceof DenySignal) {\n return { kind: 'deny', phase: 'render', signal: error };\n }\n if (error instanceof RedirectSignal) {\n return { kind: 'redirect', phase: 'render', signal: error };\n }\n return { kind: 'error', phase: 'render', error };\n }\n}\n\n// ─── Request Handler ───────────────────────────────────────────────────────\n\n/**\n * Process a single request from canonicalization through phase dispatch.\n *\n * Stages: canonicalize → metadata routes → auto-sitemap → version skew →\n * route match → interception → early hints → param coercion → middleware →\n * render → outcome translation. Pre-routing short-circuits return Responses\n * directly; post-match dispatch goes through `outcomeToResponse`.\n *\n * Used both as the top-level entry (when no proxy.ts is configured) and as\n * the `next()` continuation passed to `runProxy()`.\n *\n * @param pathIsCanonical When true, `path` has already been canonicalized by\n * `createPipeline` — skip re-canonicalization to prevent double-decode.\n * When false (default), runs canonicalize as a safety net for direct callers.\n */\nexport async function handleRequest(\n config: PipelineConfig,\n req: Request,\n method: string,\n path: string,\n pathIsCanonical?: boolean\n): Promise<Response> {\n const stripTrailingSlash = config.stripTrailingSlash ?? true;\n\n // Stage 1: URL canonicalization.\n // When pathIsCanonical is true, createPipeline has already run canonicalize()\n // and passed the result as `path`. Re-canonicalizing would double-decode\n // percent-encoded characters (e.g., %61dmin → admin). See TIM-1004.\n //\n // When pathIsCanonical is false, this is a safety net for direct callers\n // (tests, non-pipeline usage) that haven't pre-canonicalized.\n let canonicalPathname: string;\n if (pathIsCanonical) {\n canonicalPathname = path;\n } else {\n const result = canonicalize(path, stripTrailingSlash);\n if (!result.ok) {\n if (isDebug()) {\n console.warn(\n `[timber] URL canonicalization rejected ${method} ${path} — responding with ${result.status}\\n` +\n ` This usually means the URL contains encoded separators (%2f, %5c),\\n` +\n ` null bytes (%00), path traversal (..), or malformed percent-encoding.`\n );\n }\n return new Response(null, { status: result.status });\n }\n canonicalPathname = result.pathname;\n }\n\n // Stage 1b: Metadata route matching — runs before regular route matching.\n // Metadata routes skip middleware.ts and access.ts (public endpoints for crawlers).\n // See design/16-metadata.md §\"Pipeline Integration\"\n if (config.matchMetadataRoute) {\n const metaMatch = config.matchMetadataRoute(canonicalPathname);\n if (metaMatch) {\n try {\n // Static metadata files (.xml, .txt, .png, .ico, etc.) are served\n // directly from disk. Dynamic metadata routes (.ts, .tsx) export a\n // handler function that generates the response.\n if (metaMatch.isStatic) {\n return await serveStaticMetadataFile(metaMatch);\n }\n\n setSegmentParams(metaMatch.segmentParams);\n const mod = await loadModule<{ default?: MetadataHandler }>(metaMatch.file);\n if (typeof mod.default !== 'function') {\n if (isDebug()) {\n console.warn(\n `[timber] Metadata route ${metaMatch.file} does not export a default function — responding with 500\\n` +\n ` Metadata routes must export a default function that returns the metadata content.`\n );\n }\n return new Response('Metadata route must export a default function', { status: 500 });\n }\n const handlerResult = await mod.default();\n // If the handler returns a Response, normalize headers so the\n // outer Server-Timing writer can append without hitting an\n // immutable header bag (e.g. user returns Response.redirect()).\n if (handlerResult instanceof Response) {\n if (method === 'HEAD') {\n return new Response(null, {\n status: handlerResult.status,\n statusText: handlerResult.statusText,\n headers: new Headers(handlerResult.headers),\n });\n }\n return cloneWithMutableHeaders(handlerResult);\n }\n // Otherwise, serialize based on content type. The type discriminator\n // here is the metadata route's declared content type (from the file\n // convention), not the shape of `handlerResult` — TS can't narrow the\n // `MetadataResult` union on that, so we assert against the expected\n // shape at each branch.\n const contentType = metaMatch.contentType;\n let body: string;\n if (typeof handlerResult === 'string') {\n body = handlerResult;\n } else if (contentType === 'application/xml') {\n body = serializeSitemap(handlerResult as MetadataRoute.Sitemap);\n } else if (contentType === 'application/manifest+json') {\n body = JSON.stringify(handlerResult, null, 2);\n } else {\n body = String(handlerResult);\n }\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': `${contentType}; charset=utf-8` },\n });\n } catch (error) {\n // Control-flow signals from the metadata handler (e.g. a sitemap that\n // calls deny(404) for an unknown tenant, or a dynamic icon that\n // redirects to a CDN-hosted asset) translate to proper HTTP responses\n // instead of being logged as 500s.\n if (error instanceof RedirectSignal) {\n return new Response(null, {\n status: error.status,\n headers: { Location: error.location },\n });\n }\n if (error instanceof DenySignal) {\n return new Response(null, { status: error.status });\n }\n logRenderError({ method, path, error });\n if (config.onPipelineError && error instanceof Error)\n config.onPipelineError(error, 'metadata-route');\n return new Response(null, { status: 500 });\n }\n }\n }\n\n // Stage 1b.2: Auto-generated sitemap — serves /sitemap.xml and /sitemap/N.xml\n // when sitemap generation is enabled and no user-authored sitemap exists.\n // Runs after metadata route matching so user sitemaps always take precedence.\n // See design/16-metadata.md §\"Auto-generated Sitemap\"\n if (config.autoSitemapHandler) {\n try {\n const sitemapResponse = await config.autoSitemapHandler(canonicalPathname);\n if (sitemapResponse) return cloneWithMutableHeaders(sitemapResponse);\n } catch (error) {\n logRenderError({ method, path, error });\n if (config.onPipelineError && error instanceof Error)\n config.onPipelineError(error, 'auto-sitemap');\n return new Response(null, { status: 500 });\n }\n }\n\n // Stage 1c: Version skew detection (TIM-446).\n // For RSC payload requests (client navigation), check if the client's\n // deployment ID matches the current build. On mismatch, signal the\n // client to do a full page reload instead of returning an RSC payload\n // that references mismatched module IDs.\n const isRscRequest = (req.headers.get('Accept') ?? '').includes('text/x-component');\n if (isRscRequest) {\n const skewCheck = checkVersionSkew(req);\n if (!skewCheck.ok) {\n const reloadHeaders = new Headers();\n applyReloadHeaders(reloadHeaders);\n return new Response(null, { status: 204, headers: reloadHeaders });\n }\n }\n\n // Stage 2: Route matching\n let match = config.matchRoute(canonicalPathname);\n let interception: InterceptionContext | undefined;\n\n // Stage 2a: Intercepting route resolution (modal pattern).\n // Only honored on RSC client-navigation requests (Accept: text/x-component)\n // to prevent spoofing from plain HTML navigations or raw HTTP clients.\n // The header value must be a valid relative pathname — reject schemes,\n // authority, and control characters. Canonicalize before matching.\n if (isRscRequest && config.interceptionRewrites?.length) {\n const rawSourceUrl = req.headers.get('X-Timber-URL');\n const validatedSourceUrl = rawSourceUrl\n ? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash)\n : null;\n if (validatedSourceUrl) {\n const intercepted = findInterceptionMatch(\n canonicalPathname,\n validatedSourceUrl,\n config.interceptionRewrites\n );\n if (intercepted) {\n const sourceMatch = config.matchRoute(intercepted.sourcePathname);\n if (sourceMatch) {\n match = sourceMatch;\n interception = { targetPathname: canonicalPathname };\n }\n }\n }\n }\n\n if (!match) {\n // Dev-time diagnostic: log the unmatched pathname so developers can\n // see why a request 404'd without needing to add breakpoints.\n if (isDebug()) {\n console.warn(\n `[timber] No route matched for pathname: ${canonicalPathname}\\n` +\n ` Input path: ${path}\\n` +\n ` Method: ${method}`\n );\n }\n // No route matched — render 404.tsx in root layout if available,\n // otherwise fall back to a bare 404 Response.\n if (config.renderNoMatch) {\n const responseHeaders = new Headers();\n return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));\n }\n return new Response(null, { status: 404 });\n }\n\n // Response and request header containers — created before early hints so\n // the emitter can append Link headers (e.g. for Cloudflare CDN → 103).\n const responseHeaders = new Headers();\n const requestHeaderOverlay = new Headers();\n\n // Set Cache-Control for dynamic HTML responses. Without this header,\n // CDNs (particularly Cloudflare) may attempt to buffer/process the\n // response differently, causing intermittent multi-second delays.\n // This matches Next.js's default behavior.\n responseHeaders.set('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate');\n\n // Stage 2b: 103 Early Hints (before middleware, after match)\n // Fires before middleware so the browser can begin fetching critical\n // assets while middleware runs. Non-fatal — a failing emitter never\n // blocks the request.\n if (config.earlyHints) {\n try {\n await config.earlyHints(match, req, responseHeaders);\n } catch (err) {\n swallow(err, 'early hints hook threw');\n }\n }\n\n // Stage 2c: Param coercion (before middleware)\n // Load params.ts modules from matched segments and coerce raw string\n // params through defineSegmentParams codecs. Coercion failure → 404\n // (middleware never runs). See design/07-routing.md §\"Where Coercion Runs\"\n //\n // Snapshot raw params before coercion — slot resolution needs the\n // original string values to reconstruct URL parts for tree matching.\n // Coerced params may have been transformed by codecs.\n match.rawSegmentParams = { ...match.segmentParams };\n try {\n await coerceSegmentParams(match);\n } catch (error) {\n if (error instanceof ParamCoercionError) {\n // Dev-time diagnostic: log param coercion failures so developers can\n // see why a matched route 404'd. Silent coercion 404s are brutal to debug.\n if (isDebug()) {\n const segmentChain = match.segments.map((s) => s.segmentName || '/').join(' → ');\n console.warn(\n `[timber] Param coercion failed for ${method} ${canonicalPathname} — responding with 404\\n` +\n ` Matched segments: ${segmentChain}\\n` +\n ` Error: ${error.message}\\n` +\n ` This usually means a params.ts codec rejected the URL params.\\n` +\n ` Check that all fields in defineSegmentParams() are optional for params\\n` +\n ` that don't appear at every route depth (e.g. year, month, day).`\n );\n }\n // For API routes (route.ts), return a bare 404 — not an HTML page.\n // API consumers expect JSON/empty responses, not rendered HTML.\n const leafSegment = match.segments[match.segments.length - 1];\n if ((leafSegment as { route?: unknown }).route && !(leafSegment as { page?: unknown }).page) {\n return new Response(null, { status: 404 });\n }\n // Route through the app's 404 page (404.tsx in root layout) instead of\n // returning a bare empty 404 Response. Falls back to bare 404 only if\n // no renderNoMatch renderer is configured.\n if (config.renderNoMatch) {\n return cloneWithMutableHeaders(await config.renderNoMatch(req, responseHeaders));\n }\n return new Response(null, { status: 404 });\n }\n throw error;\n }\n\n // Store coerced segment params in ALS so components can access them\n // via getSegmentParams() instead of receiving them as a prop.\n // See design/07-routing.md §\"params.ts — Convention File for Typed Params\"\n setSegmentParams(match.segmentParams);\n\n // Store the matched segment path for dev-mode validation in getSegmentParams().\n // Build the tree path from the segment chain (includes groups and slots).\n const segmentPath =\n match.segments\n .map((s) => s.segmentName)\n .filter(Boolean)\n .join('/') || '/';\n setMatchedSegmentPath(segmentPath.startsWith('/') ? segmentPath : `/${segmentPath}`);\n\n // Bypass middleware for synthetic re-render requests built by the\n // action-dispatch wrapper after a no-JS form validation failure.\n // The wrapper has already executed middleware once on the inbound POST;\n // running it again on the rerender GET would double-execute auth, rate\n // limiting, and request-header injection. See TIM-871.\n const skipMiddleware = shouldBypassMiddleware(req);\n const outcome =\n !skipMiddleware && match.middlewareChain.length > 0\n ? await runMiddlewarePhase(config, req, match, responseHeaders, requestHeaderOverlay, {\n canonicalPathname,\n interception,\n })\n : await runRenderPhase(config, req, match, responseHeaders, requestHeaderOverlay, {\n canonicalPathname,\n interception,\n });\n\n return outcomeToResponse(config, outcome, {\n req,\n method,\n path,\n responseHeaders,\n match,\n });\n}\n","/**\n * Request pipeline — the central dispatch for all timber.js requests.\n *\n * Pipeline stages (in order):\n * proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render\n *\n * The phase functions live in `pipeline-phases.ts` so each phase can be\n * tested in isolation. The terminal `outcomeToResponse` translator and\n * stateless helpers live in `pipeline-phases.ts` and `pipeline-helpers.ts`\n * respectively. This file owns only the public type surface and the\n * `createPipeline` entry point: trace ID setup, request-context ALS,\n * Server-Timing wrapping, and the activeRequests counter.\n *\n * See design/07-routing.md §\"Request Lifecycle\", design/02-rendering-pipeline.md §\"Request Flow\",\n * and design/17-logging.md §\"Production Logging\"\n */\n\nimport type { ProxyExport } from './proxy.js';\nimport type { MiddlewareFn } from './middleware-runner.js';\nimport { runWithTimingCollector, getServerTimingHeader } from './server-timing.js';\nimport { runWithRequestContext } from './request-context.js';\nimport {\n generateTraceId,\n runWithTraceId,\n getOtelTraceId,\n replaceTraceId,\n withSpan,\n setSpanAttribute,\n} from './tracing.js';\nimport { logRequestReceived, logRequestCompleted, logSlowRequest } from './logger.js';\nimport { DenySignal } from './primitives.js';\nimport type { ManifestSegmentNode } from './route-matcher.js';\nimport { makeProxyResolver } from './pipeline-helpers.js';\nimport { handleRequest, runProxyPhase } from './pipeline-phases.js';\nimport { outcomeToResponse } from './pipeline-outcome.js';\nimport { canonicalize } from './canonicalize.js';\nimport { isDebug } from './debug.js';\n\n// ─── Route Match Result ────────────────────────────────────────────────────\n\n/**\n * Result of matching a canonical pathname against the route tree.\n *\n * `segments` is the runtime (`ManifestFile`-specialized) shape — the same\n * nodes carried in the virtual route manifest. TIM-863 unified this: the\n * matcher produces `ManifestSegmentNode[]` directly and every consumer\n * (render, slots, params coercion, early hints, deny fallback) sees the\n * same structural type with no `as unknown as` laundering.\n */\nexport interface RouteMatch {\n /** The matched segment chain from root to leaf. */\n segments: ManifestSegmentNode[];\n /** Extracted segment params (catch-all segments produce string[]). */\n segmentParams: Record<string, string | string[]>;\n /**\n * Raw segment params before codec coercion. Always string or string[].\n * Used by slot resolution to reconstruct URL parts — coerced params may\n * have been transformed by codecs and are unsuitable for URL matching.\n * Set by the pipeline before coercion runs.\n */\n rawSegmentParams?: Record<string, string | string[]>;\n /** Middleware chain from the segment tree, ordered root-to-leaf. */\n middlewareChain: MiddlewareFn[];\n}\n\n/** Function that matches a canonical pathname to a route. */\nexport type RouteMatcher = (pathname: string) => RouteMatch | null;\n\n/** Function that matches a canonical pathname to a metadata route. */\nexport type MetadataRouteMatcher = (\n pathname: string\n) => import('./route-matcher.js').MetadataRouteMatch | null;\n\n/** Context for intercepting route resolution (modal pattern). */\nexport interface InterceptionContext {\n /** The URL the user is navigating TO (the intercepted route). */\n targetPathname: string;\n}\n\n/** Function that renders a matched route into a Response. */\nexport type RouteRenderer = (\n req: Request,\n match: RouteMatch,\n responseHeaders: Headers,\n requestHeaderOverlay: Headers,\n interception?: InterceptionContext\n) => Response | Promise<Response>;\n\n/** Function that sends 103 Early Hints for a matched route. */\nexport type EarlyHintsEmitter = (\n match: RouteMatch,\n req: Request,\n responseHeaders: Headers\n) => void | Promise<void>;\n\n// ─── Pipeline Configuration ────────────────────────────────────────────────\n\n/**\n * Proxy source — a tagged union so the choice between \"already-resolved\n * export\" and \"lazy HMR-friendly loader\" is encoded in the type, not\n * inferred per-request.\n *\n * - `static` — the proxy export is already resolved (production, tests).\n * - `lazy` — a loader is called per-request for HMR freshness (dev).\n *\n * `PipelineConfig.proxy` also accepts a bare `ProxyExport` (a function or\n * function array) as shorthand for the static variant — convenient for tests\n * that construct a `createPipeline` config inline. Omit the field entirely\n * when the app has no `proxy.ts`.\n *\n * See design/07-routing.md §\"proxy.ts — Global Middleware\".\n */\nexport type ProxyConfig =\n | { kind: 'static'; export: ProxyExport }\n | { kind: 'lazy'; loader: () => Promise<{ default: ProxyExport }> };\n\nexport interface PipelineConfig {\n /**\n * proxy.ts source. Undefined if the app has no proxy.ts. Accepts either a\n * tagged `ProxyConfig` (canonical) or a bare `ProxyExport` as sugar for the\n * static variant.\n */\n proxy?: ProxyConfig | ProxyExport;\n /** Route matcher — resolves a canonical pathname to a RouteMatch. */\n matchRoute: RouteMatcher;\n /** Metadata route matcher — resolves metadata route pathnames (sitemap.xml, robots.txt, etc.) */\n matchMetadataRoute?: MetadataRouteMatcher;\n /** Renderer — produces the final Response for a matched route. */\n render: RouteRenderer;\n /** Renderer for no-match 404 — renders 404.tsx in root layout. */\n renderNoMatch?: (req: Request, responseHeaders: Headers) => Response | Promise<Response>;\n /** Early hints emitter — fires 103 hints after route match, before middleware. */\n earlyHints?: EarlyHintsEmitter;\n /** Whether to strip trailing slashes during canonicalization. Default: true. */\n stripTrailingSlash?: boolean;\n /** Slow request threshold in ms. Requests exceeding this emit a warning. 0 to disable. Default: 3000. */\n slowRequestMs?: number;\n /**\n * Interception rewrites — conditional routes for the modal pattern.\n * Generated at build time from intercepting route directories.\n * See design/07-routing.md §\"Intercepting Routes\"\n */\n interceptionRewrites?: import('../routing/interception.js').InterceptionRewrite[];\n /**\n * Control Server-Timing header output.\n *\n * - `'detailed'` — per-phase breakdown (proxy, middleware, render).\n * - `'total'` — single `total;dur=N` entry (production-safe).\n * - `false` — no Server-Timing header at all.\n *\n * Default: `'total'`.\n */\n serverTiming?: 'detailed' | 'total' | false;\n /**\n * Auto-generated sitemap handler. When provided, the pipeline intercepts\n * `/sitemap.xml` and `/sitemap/N.xml` requests and delegates to this\n * function. Returns a Response or null (pass-through to regular routing).\n *\n * See design/16-metadata.md §\"Auto-generated Sitemap\"\n */\n autoSitemapHandler?: (pathname: string) => Promise<Response | null>;\n /**\n * Dev pipeline error callback — called when a pipeline phase (proxy,\n * middleware, render) catches an unhandled error. Used to wire the error\n * into the Vite browser error overlay in dev mode.\n *\n * Undefined in production — zero overhead.\n */\n onPipelineError?: (error: Error, phase: string) => void;\n\n /**\n * Dev error handler with RSC debug context — set by the dev server after\n * the RSC entry module is imported. `onPipelineError` resolves debug\n * components from ALS and delegates to this handler.\n *\n * This is a mutable property (not a constructor argument) because the dev\n * server imports the RSC entry module first (which constructs the config),\n * then wires in the overlay handler. Moving the state here (from a\n * module-level `let`) keeps it co-located with the config it belongs to\n * and makes it visible in tests without module-level mutation.\n *\n * Undefined in production — zero overhead.\n */\n devPipelineErrorHandler?: (\n error: Error,\n phase: string,\n debugComponents?: Array<{ name: string; env: string | null; stack: unknown[] | null }>\n ) => void;\n\n /**\n * HMR connection options for dev-only HTML pages (dev 404 page, fallback\n * error page) — set by the dev server after the RSC entry module is\n * imported, like `devPipelineErrorHandler`. The RSC environment can't\n * read the Vite server config (separate module graph), so the dev server\n * plumbs the resolved HMR endpoint (protocol/host/port/path/token) here\n * for the pages' auto-reload WebSocket. See TIM-1067.\n *\n * Undefined in production — zero overhead.\n */\n devHmrOptions?: import('../dev-tools/dev-page-shell.js').DevErrorHmrOptions;\n\n /**\n * Fallback error renderer — called when a catastrophic error escapes the\n * render phase. Produces an HTML Response instead of a bare empty 500.\n *\n * In dev mode, this renders a styled error page with the error message\n * and stack trace. In production, this attempts to render the app's\n * error.tsx / 5xx.tsx / 500.tsx from the root segment.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: 500 })`.\n */\n renderFallbackError?: (\n error: unknown,\n req: Request,\n responseHeaders: Headers\n ) => Response | Promise<Response>;\n /**\n * Fallback deny page renderer — called when a DenySignal escapes from\n * middleware or the render phase. Renders the appropriate status-code\n * page (403.tsx, 404.tsx, etc.) instead of returning a bare empty response.\n *\n * If this function throws, the pipeline falls back to a bare\n * `new Response(null, { status: denyStatus })`.\n */\n renderDenyFallback?: (\n deny: DenySignal,\n req: Request,\n responseHeaders: Headers,\n /**\n * The matched route, if available. Provided by both the middleware-stage\n * and render-stage catch blocks (matching runs before middleware). When\n * present, the renderer should resolve the deny status file against the\n * matched chain so colocated `403.tsx`/`4xx.tsx`/`401.json` files are\n * picked up. Falls back to the root-only chain when omitted (e.g. for\n * deny()s thrown before route matching could complete). See TIM-822.\n */\n match?: RouteMatch\n ) => Response | Promise<Response>;\n}\n\n// ─── Pipeline ──────────────────────────────────────────────────────────────\n\n/**\n * Create the request handler from a pipeline configuration.\n *\n * Returns a function that processes an incoming Request through all pipeline\n * stages and produces a Response. This is the top-level entry point for the\n * server. The body is intentionally small — phase logic lives in\n * `pipeline-phases.ts`. This function only owns the per-request setup that\n * has to wrap the entire dispatch: trace ID, request context ALS, span\n * scope, Server-Timing header emission, and the active-request counter.\n */\nexport function createPipeline(config: PipelineConfig): (req: Request) => Promise<Response> {\n // Resolve the proxy source once. The request hot path calls this closure\n // directly with no discriminant check — the branch is taken here during\n // setup. For the lazy variant, `loader()` still runs per-request so HMR\n // continues to re-import the user's proxy.ts.\n const proxyResolver = makeProxyResolver(config.proxy);\n const slowRequestMs = config.slowRequestMs ?? 3000;\n const serverTiming = config.serverTiming ?? 'total';\n\n // Concurrent request counter — tracks how many requests are in-flight.\n // Logged with each request for diagnosing resource contention.\n let activeRequests = 0;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const method = req.method;\n const path = url.pathname;\n const startTime = performance.now();\n activeRequests++;\n\n // Establish per-request trace ID scope (design/17-logging.md §\"trace_id is Always Set\").\n // This runs before runWithRequestContext so traceId() is available from the\n // very first line of proxy.ts, middleware.ts, and all server code.\n const traceIdValue = generateTraceId();\n\n return runWithTraceId(traceIdValue, async () => {\n // Establish request context ALS scope so getHeaders() and getCookies() work\n // throughout the entire request lifecycle (proxy, middleware, render).\n return runWithRequestContext(req, async () => {\n // In dev mode, wrap with timing collector for Server-Timing header.\n // The collector uses ALS so timing entries are per-request.\n const runRequest = async () => {\n logRequestReceived({ method, path });\n\n const response = await withSpan(\n 'http.server.request',\n { 'http.request.method': method, 'url.path': path },\n async () => {\n // If OTEL is active, the root span now exists — replace the UUID\n // fallback with the real OTEL trace ID for log–trace correlation.\n const otelIds = await getOtelTraceId();\n if (otelIds) {\n replaceTraceId(otelIds.traceId, otelIds.spanId);\n }\n\n // Stage 0: Canonicalize URL at the outer boundary — before\n // proxy.ts sees the request. Every layer (proxy, middleware,\n // access, render) must see the same canonical path.\n // See design/07-routing.md §\"URL Canonicalization & Security\".\n const stripTrailingSlash = config.stripTrailingSlash ?? true;\n const canonResult = canonicalize(url.pathname, stripTrailingSlash);\n if (!canonResult.ok) {\n if (isDebug()) {\n console.warn(\n `[timber] URL canonicalization rejected ${method} ${url.pathname} — responding with ${canonResult.status}\\n` +\n ` This usually means the URL contains encoded separators (%2f, %5c),\\n` +\n ` null bytes (%00), path traversal (..), or malformed percent-encoding.`\n );\n }\n return new Response(null, { status: canonResult.status });\n }\n const canonicalPath = canonResult.pathname;\n\n // Construct a Request with the canonical URL so proxy.ts sees\n // the same path that route matching will use. This prevents\n // auth bypass via /admin/, /%61dmin, etc.\n let canonicalReq = req;\n if (url.pathname !== canonicalPath) {\n const canonicalUrl = new URL(req.url);\n canonicalUrl.pathname = canonicalPath;\n canonicalReq = new Request(canonicalUrl.toString(), req);\n }\n\n let result: Response;\n if (proxyResolver) {\n const outcome = await runProxyPhase(\n config,\n proxyResolver,\n canonicalReq,\n method,\n canonicalPath\n );\n result = await outcomeToResponse(config, outcome, {\n req: canonicalReq,\n method,\n path: canonicalPath,\n });\n } else {\n result = await handleRequest(config, canonicalReq, method, canonicalPath, true);\n }\n\n // Set response status on the root span before it ends —\n // DevSpanProcessor reads this for tree/summary output.\n await setSpanAttribute('http.response.status_code', result.status);\n\n // Vary: Accept — CDNs must cache HTML and RSC payload responses\n // separately for the same URL. Without this, a shared cache may\n // serve an RSC Flight payload to a browser expecting HTML (or\n // vice versa). See GHSA-wfc6-r584-vfw7, design/13-security.md.\n //\n // Vary: X-Timber-URL — intercepting route responses depend on\n // the source URL, so caches must store them separately.\n const varyTokens = ['Accept'];\n if (config.interceptionRewrites?.length) {\n varyTokens.push('X-Timber-URL');\n }\n const existingVary = result.headers.get('Vary');\n const existingTokens = existingVary\n ? existingVary\n .toLowerCase()\n .split(',')\n .map((t) => t.trim())\n : [];\n const newTokens = varyTokens.filter((t) => !existingTokens.includes(t.toLowerCase()));\n if (newTokens.length > 0) {\n result.headers.set(\n 'Vary',\n existingVary ? `${existingVary}, ${newTokens.join(', ')}` : newTokens.join(', ')\n );\n }\n\n // Append Server-Timing header based on configured mode.\n // Header mutability is guaranteed by the producer-side clone\n // in `outcomeToResponse` and the metadata-route / auto-sitemap\n // user-handler clones in `handleRequest`, so we can write\n // directly without a runtime probe. See TIM-866.\n if (serverTiming === 'detailed') {\n // Detailed: per-phase breakdown (proxy, middleware, render).\n const timingHeader = getServerTimingHeader();\n if (timingHeader) {\n result.headers.set('Server-Timing', timingHeader);\n }\n } else if (serverTiming === 'total') {\n // Total only: single `total;dur=N` — no phase names.\n // Prevents information disclosure while giving browser\n // DevTools useful timing data.\n const totalMs = Math.round(performance.now() - startTime);\n result.headers.set('Server-Timing', `total;dur=${totalMs}`);\n }\n // serverTiming === false: no header at all\n\n return result;\n }\n );\n\n // Post-span: structured production logging\n const durationMs = Math.round(performance.now() - startTime);\n const status = response.status;\n const concurrency = activeRequests;\n activeRequests--;\n logRequestCompleted({ method, path, status, durationMs, concurrency });\n\n if (slowRequestMs > 0 && durationMs > slowRequestMs) {\n logSlowRequest({ method, path, durationMs, threshold: slowRequestMs, concurrency });\n }\n\n return response;\n };\n\n return serverTiming === 'detailed' ? runWithTimingCollector(runRequest) : runRequest();\n });\n });\n };\n}\n","/**\n * Build manifest types and utilities for CSS and JS asset tracking.\n *\n * The build manifest maps route segment file paths to their output\n * chunks from Vite's client build. This enables:\n * - <link rel=\"stylesheet\"> injection in HTML <head>\n * - <script type=\"module\"> with hashed URLs in production\n * - <link rel=\"modulepreload\"> for client chunk dependencies\n * - Link preload headers for Early Hints (103)\n *\n * In dev mode, Vite's HMR client handles CSS/JS injection, so the build\n * manifest is empty. In production, it's populated from Vite's\n * .vite/manifest.json after the client build.\n *\n * Design docs: 18-build-system.md §\"Build Manifest\", 02-rendering-pipeline.md §\"Early Hints\"\n */\n\n/** A font asset entry in the build manifest. */\nexport interface ManifestFontEntry {\n /** URL path to the font file (e.g. `/_timber/fonts/inter-latin-400-abc123.woff2`). */\n href: string;\n /** Font format (e.g. `woff2`). */\n format: string;\n /** Crossorigin attribute — always `anonymous` for fonts. */\n crossOrigin: string;\n}\n\n/** Build manifest mapping input file paths to output asset URLs. */\nexport interface BuildManifest {\n /** Map from input file path (relative to project root) to output CSS URLs. */\n css: Record<string, string[]>;\n /** Map from input file path to output JS chunk URL (hashed filename). */\n js: Record<string, string>;\n /** Map from input file path to transitive JS dependency URLs for modulepreload. */\n modulepreload: Record<string, string[]>;\n /** Map from input file path to font assets used by that module. */\n fonts: Record<string, ManifestFontEntry[]>;\n /**\n * Cache-busting hashes for metadata route files (opengraph-image, etc.).\n * Map from file path (relative to project root) to short content hash.\n * In dev mode this is empty — a startup nonce is used instead.\n */\n metadataRouteHashes?: Record<string, string>;\n}\n\n/** Empty build manifest used in dev mode. */\nexport const EMPTY_BUILD_MANIFEST: BuildManifest = {\n css: {},\n js: {},\n modulepreload: {},\n fonts: {},\n metadataRouteHashes: {},\n};\n\n/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n/**\n * Collect all CSS files needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting CSS for each layout and page.\n * Deduplicates while preserving order (root layout CSS first).\n */\nexport function collectRouteCss(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const cssFiles = manifest.css[file.filePath];\n if (!cssFiles) continue;\n for (const url of cssFiles) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"stylesheet\"> tags for CSS URLs.\n *\n * Returns an HTML string to prepend to headHtml for injection\n * via injectHead() before </head>.\n */\nexport function buildCssLinkTags(cssUrls: string[]): string {\n // Emit only <link rel=\"stylesheet\"> — no preload tags. CSS preloading\n // is handled by 103 Early Hints (Link header) which fires before the\n // HTML stream. Float also emits `<link rel=\"stylesheet\" data-precedence>`\n // at the top of <head> during Fizz rendering — the browser discovers\n // CSS from that tag. A <link rel=\"preload\"> here would arrive *after*\n // Float's stylesheet tag and trigger \"Preload was ignored\" warnings.\n return cssUrls.map((url) => `<link rel=\"stylesheet\" href=\"${url}\">`).join('');\n}\n\n// ─── Font utilities ──────────────────────────────────────────────────────\n\n/**\n * Collect all font entries needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting fonts for each layout and page.\n * Deduplicates by href while preserving order.\n */\nexport function collectRouteFonts(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): ManifestFontEntry[] {\n const seen = new Set<string>();\n const result: ManifestFontEntry[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const fonts = manifest.fonts[file.filePath];\n if (!fonts) continue;\n for (const entry of fonts) {\n if (!seen.has(entry.href)) {\n seen.add(entry.href);\n result.push(entry);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"preload\"> tags for font assets.\n *\n * Font preloads use `as=font` and always include `crossorigin` (required\n * for font preloads even for same-origin resources per the spec).\n */\nexport function buildFontPreloadTags(fonts: ManifestFontEntry[]): string {\n return fonts\n .map(\n (f) =>\n `<link rel=\"preload\" href=\"${f.href}\" as=\"font\" type=\"font/${f.format}\" crossorigin=\"${f.crossOrigin}\">`\n )\n .join('');\n}\n\n// ─── JS chunk utilities ──────────────────────────────────────────────────\n\n/**\n * Collect modulepreload URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting transitive JS dependencies\n * for each layout and page. Deduplicates across segments.\n */\nexport function collectRouteModulepreloads(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const preloads = manifest.modulepreload[file.filePath];\n if (!preloads) continue;\n for (const url of preloads) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"modulepreload\"> tags for JS dependency URLs.\n *\n * Modulepreload hints tell the browser to fetch and parse JS modules\n * before they're needed, reducing waterfall latency for dynamic imports.\n */\nexport function buildModulepreloadTags(urls: string[]): string {\n return urls.map((url) => `<link rel=\"modulepreload\" href=\"${url}\">`).join('');\n}\n","/**\n * 103 Early Hints utilities.\n *\n * Early Hints are sent before the final response to let the browser\n * start fetching critical resources (CSS, fonts, JS) while the server\n * is still rendering.\n *\n * The framework collects hints from two sources:\n * 1. Build manifest — CSS, fonts, and JS chunks known at route-match time\n * 2. ctx.earlyHints() — explicit hints added by middleware or route handlers\n *\n * Both are emitted as Link headers. Cloudflare CDN automatically converts\n * Link headers into 103 Early Hints responses.\n *\n * Design docs: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport {\n collectRouteCss,\n collectRouteFonts,\n collectRouteModulepreloads,\n} from './build-manifest.js';\nimport type { BuildManifest } from './build-manifest.js';\n\n/** Minimal segment shape needed for early hint collection. */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n// ─── EarlyHint type ───────────────────────────────────────────────────────\n\n/**\n * A single Link header hint for 103 Early Hints.\n *\n * ```ts\n * ctx.earlyHints([\n * { href: '/styles/critical.css', rel: 'preload', as: 'style' },\n * { href: 'https://fonts.googleapis.com', rel: 'preconnect' },\n * ])\n * ```\n */\nexport interface EarlyHint {\n /** The resource URL (absolute or root-relative). */\n href: string;\n /** Link relation — `preload`, `modulepreload`, or `preconnect`. */\n rel: 'preload' | 'modulepreload' | 'preconnect';\n /** Resource type for `preload` hints (omit for `modulepreload` / `preconnect`). */\n as?: 'style' | 'script' | 'font' | 'image' | 'fetch' | 'document';\n /** Crossorigin attribute — required for font preloads per spec. */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /** Fetch priority hint — `high`, `low`, or `auto`. */\n fetchPriority?: 'high' | 'low' | 'auto';\n}\n\n// ─── formatLinkHeader ─────────────────────────────────────────────────────\n\n/**\n * Format a single EarlyHint as a Link header value.\n *\n * Attribute order: `as` before `rel` to match Cloudflare CDN's cached\n * Early Hints format. Cloudflare caches Link headers from 200 responses\n * and re-emits them as 103 Early Hints on subsequent requests. If our\n * attribute order differs from Cloudflare's cached copy, the browser\n * sees two preload headers for the same URL (different attribute order)\n * and warns \"Preload was ignored.\" Matching the order ensures the\n * browser deduplicates them correctly.\n *\n * Examples:\n * `</styles/root.css>; as=style; rel=preload`\n * `</fonts/inter.woff2>; as=font; rel=preload; crossorigin=anonymous`\n * `</_timber/client.js>; rel=modulepreload`\n * `<https://fonts.googleapis.com>; rel=preconnect`\n */\nexport function formatLinkHeader(hint: EarlyHint): string {\n // For preload hints, emit `as` before `rel` to match Cloudflare's\n // cached header format and avoid duplicate preload warnings.\n if (hint.as !== undefined) {\n let value = `<${hint.href}>; as=${hint.as}; rel=${hint.rel}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n }\n // For modulepreload / preconnect (no `as`), emit rel first.\n let value = `<${hint.href}>; rel=${hint.rel}`;\n if (hint.crossOrigin !== undefined) value += `; crossorigin=${hint.crossOrigin}`;\n if (hint.fetchPriority !== undefined) value += `; fetchpriority=${hint.fetchPriority}`;\n return value;\n}\n\n// ─── collectEarlyHintHeaders ──────────────────────────────────────────────\n\n/** Options for early hint collection. */\nexport interface EarlyHintOptions {\n /** Skip JS modulepreload hints (e.g. when client JavaScript is disabled). */\n skipJs?: boolean;\n}\n\n/**\n * Collect all Link header strings for a matched route's segment chain.\n *\n * Walks the build manifest to emit hints for:\n * - CSS stylesheets (as=style; rel=preload)\n * - Font assets (as=font; rel=preload; crossorigin)\n * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set\n *\n * Returns formatted Link header strings, deduplicated by URL, root → leaf order.\n * Returns an empty array in dev mode (manifest is empty).\n */\nexport function collectEarlyHintHeaders(\n segments: SegmentWithFiles[],\n manifest: BuildManifest,\n options?: EarlyHintOptions\n): string[] {\n const result: string[] = [];\n // Dedup by URL (href), not by full formatted header string.\n // Different code paths can produce the same URL with different attribute\n // ordering, which would bypass a full-string dedup and produce duplicate\n // Link headers that trigger browser \"preload was ignored\" warnings.\n const seenUrls = new Set<string>();\n\n const add = (url: string, header: string) => {\n if (!seenUrls.has(url)) {\n seenUrls.add(url);\n result.push(header);\n }\n };\n\n // Per-route CSS — as=style; rel=preload\n // The HTML <head> also contains a matching <link rel=\"preload\" as=\"style\">\n // tag so browsers can deduplicate the 103 hint against the HTML tag.\n for (const url of collectRouteCss(segments, manifest)) {\n add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));\n }\n\n // Fonts — as=font; rel=preload; crossorigin (crossorigin required per spec)\n for (const font of collectRouteFonts(segments, manifest)) {\n add(\n font.href,\n formatLinkHeader({ href: font.href, rel: 'preload', as: 'font', crossOrigin: 'anonymous' })\n );\n }\n\n // JS chunks — rel=modulepreload (skip when client JS is disabled)\n if (!options?.skipJs) {\n for (const url of collectRouteModulepreloads(segments, manifest)) {\n add(url, formatLinkHeader({ href: url, rel: 'modulepreload' }));\n }\n }\n\n return result;\n}\n","/**\n * Per-request 103 Early Hints sender — ALS bridge for platform adapters.\n *\n * The pipeline collects Link headers for CSS, fonts, and JS chunks at\n * route-match time. On platforms that support it (Node.js v18.11+, Bun),\n * the adapter can send these as a 103 Early Hints interim response before\n * the final response is ready.\n *\n * This module provides an ALS-based bridge: the generated entry point\n * (e.g., the Nitro entry) wraps the handler with `runWithEarlyHintsSender`,\n * binding a per-request sender function. The pipeline calls\n * `sendEarlyHints103()` to fire the 103 if a sender is available.\n *\n * On platforms where 103 is handled at the CDN level (e.g., Cloudflare\n * converts Link headers into 103 automatically), no sender is installed\n * and `sendEarlyHints103()` is a no-op.\n *\n * Design doc: 02-rendering-pipeline.md §\"Early Hints (103)\"\n */\n\nimport { earlyHintsSenderAls } from './als-registry.js';\nimport { swallow } from './logger.js';\n\n/** Function that sends Link header values as a 103 Early Hints response. */\nexport type EarlyHintsSenderFn = (links: string[]) => void;\n\n/**\n * Run a function with a per-request early hints sender installed.\n *\n * Called by generated entry points (e.g., Nitro node-server/bun) to\n * bind the platform's writeEarlyHints capability for the request duration.\n */\nexport function runWithEarlyHintsSender<T>(sender: EarlyHintsSenderFn, fn: () => T): T {\n return earlyHintsSenderAls.run(sender, fn);\n}\n\n/**\n * Send collected Link headers as a 103 Early Hints response.\n *\n * No-op if no sender is installed for the current request (e.g., on\n * Cloudflare where the CDN handles 103 automatically, or in dev mode).\n *\n * Non-fatal: errors from the sender are caught and silently ignored.\n */\nexport function sendEarlyHints103(links: string[]): void {\n if (!links.length) return;\n const sender = earlyHintsSenderAls.getStore();\n if (!sender) return;\n try {\n sender(links);\n } catch (err) {\n swallow(err, 'early hints 103 send failed');\n }\n}\n","/**\n * Element tree construction for timber.js rendering.\n *\n * Builds a unified React element tree from a matched segment chain, bottom-up:\n * page → status-code error boundaries → access gates → layout → repeat up segment chain\n *\n * The tree is rendered via a single `renderToReadableStream` call,\n * giving one `React.cache` scope for the entire route.\n *\n * See design/02-rendering-pipeline.md §\"Element Tree Construction\"\n */\n\nimport type { ReactNode } from 'react';\nimport type { RouteFile, SegmentNode } from '../routing/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A loaded module for a route file convention. */\nexport interface LoadedModule {\n /** The default export (component, access function, etc.) */\n default?: unknown;\n /** Named exports (for route.ts method handlers, metadata, etc.) */\n [key: string]: unknown;\n}\n\n/** Function that loads a route file's module. */\nexport type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;\n\n/**\n * A React component reference loaded from a route module's default export.\n *\n * Loaded modules' `default` is typed as `unknown` (modules are dynamic), so\n * call sites narrow it through `isValidElementType` (below) before treating\n * it as a component. The signature is a callable returning `ReactNode` —\n * TypeScript's view of every valid React component shape, including exotic\n * components (`memo`, `forwardRef`, `lazy`) which the type system treats as\n * callable even though their runtime values are objects with `$$typeof`\n * markers rather than functions.\n */\nexport type LoadedComponent = (...args: unknown[]) => ReactNode;\n\n// Marker symbols React stamps onto exotic component types. Mirrors the\n// internal `isValidElementType` check in `react.development.js` — React\n// doesn't export it, and we don't want to add `react-is` just for this one\n// validation. Inlined the same way `isClientReference` in\n// `route-element-builder.ts` inlines the client-reference marker.\nconst REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');\nconst REACT_MEMO_TYPE = Symbol.for('react.memo');\nconst REACT_LAZY_TYPE = Symbol.for('react.lazy');\nconst REACT_PROVIDER_TYPE = Symbol.for('react.provider');\nconst REACT_CONTEXT_TYPE = Symbol.for('react.context');\nconst REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');\nconst REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');\nconst REACT_CLIENT_REFERENCE_TYPE = Symbol.for('react.client.reference');\n\nconst REACT_COMPONENT_TYPE_MARKERS: ReadonlySet<symbol> = new Set([\n REACT_FORWARD_REF_TYPE,\n REACT_MEMO_TYPE,\n REACT_LAZY_TYPE,\n REACT_PROVIDER_TYPE,\n REACT_CONTEXT_TYPE,\n REACT_SUSPENSE_TYPE,\n REACT_SUSPENSE_LIST_TYPE,\n REACT_CLIENT_REFERENCE_TYPE,\n]);\n\n/**\n * Validate that a loaded module's `default` export is something React\n * accepts as the first argument to `createElement` — i.e. a valid component\n * type. React doesn't export `isValidElementType` (only `isValidElement`,\n * which checks for *elements*, not *component types*), so this mirrors\n * React's internal check:\n *\n * - functions → function or class components\n * - objects with a `$$typeof` matching one of React's known component\n * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,\n * suspense, client references via `@vitejs/plugin-rsc`)\n *\n * Strings (HTML tag names) are valid for `createElement` but never appear\n * as a route module's default export, so they're not recognized here.\n *\n * Anything else (numbers, plain config objects, JSON, etc.) is rejected so\n * the boundary wrapper is skipped rather than crashing inside React.\n */\nfunction isValidElementType(value: unknown): value is LoadedComponent {\n if (typeof value === 'function') return true;\n if (typeof value !== 'object' || value === null) return false;\n const marker = (value as { $$typeof?: unknown }).$$typeof;\n return typeof marker === 'symbol' && REACT_COMPONENT_TYPE_MARKERS.has(marker);\n}\n\n/**\n * Function that creates a React element. Matches React.createElement signature.\n *\n * `props` is typed as `object | null` rather than `Record<string, unknown>` so\n * that interface types with known keys (e.g. `AccessGateProps`,\n * `ErrorBoundaryProps`) flow through without an explicit index-signature cast.\n */\nexport type CreateElement = (\n type: unknown,\n props: object | null,\n ...children: unknown[]\n) => ReactNode;\n\n/**\n * Resolved slot content for a layout.\n * Key is slot name (without @), value is the element tree for that slot.\n */\nexport type SlotElements = Map<string, ReactNode>;\n\n/** Configuration for the tree builder. */\nexport interface TreeBuilderConfig {\n /** The matched segment chain from root to leaf. */\n segments: SegmentNode[];\n /** Loads a route file's module. */\n loadModule: ModuleLoader;\n /** React.createElement or equivalent. */\n createElement: CreateElement;\n /**\n * Error boundary component for wrapping segments.\n *\n * This is injected by the caller rather than imported directly to avoid\n * pulling 'use client' code into the server barrel (@timber-js/app/server).\n * In the RSC environment, the RSC plugin transforms this import to a\n * client reference proxy — the caller handles the import so the server\n * barrel stays free of client dependencies.\n */\n errorBoundaryComponent?: unknown;\n}\n\n// ─── Component wrappers ──────────────────────────────────────────────────────\n\n/**\n * Framework-injected access gate component.\n *\n * When `verdict` is provided (from the pre-render pass), AccessGate replays\n * the stored result synchronously — no re-execution, no async, immune to\n * Suspense timing. When `verdict` is absent, falls back to calling `accessFn`\n * (backward compat for tree-builder.ts which doesn't run a pre-render pass).\n */\nexport interface AccessGateProps {\n accessFn: () => unknown;\n /** Segment name for dev logging (e.g. \"authenticated\", \"dashboard\"). */\n segmentName?: string;\n /**\n * Pre-computed verdict from the pre-render pass. When set, AccessGate\n * replays this verdict synchronously instead of calling accessFn.\n * - 'pass': render children\n * - DenySignal/RedirectSignal: throw synchronously\n */\n verdict?:\n | 'pass'\n | import('./primitives.js').DenySignal\n | import('./primitives.js').RedirectSignal;\n /**\n * Deny page fallback chain. When provided and a DenySignal is caught,\n * AccessGate renders the matching deny page in-tree instead of throwing.\n * This prevents the error from reaching React Flight, eliminating the\n * second render pass. See TIM-666.\n */\n denyPages?: import('./deny-boundary.js').DenyPageEntry[];\n children: ReactNode;\n}\n\n/**\n * Framework-injected slot access gate component.\n * On denial, renders denied.tsx → default.tsx → null instead of failing the page.\n *\n * DeniedComponent is passed instead of a pre-built element so that\n * SlotAccessGate can forward DenySignal.data as dangerouslyPassData\n * and slotName as the slot prop after catching the signal.\n */\nexport interface SlotAccessGateProps {\n accessFn: () => unknown;\n /** The denied.tsx component (not a pre-built element). null if no denied.tsx exists. */\n DeniedComponent: LoadedComponent | null;\n /** Slot directory name without @ prefix (e.g. \"admin\", \"sidebar\"). */\n slotName: string;\n /** createElement function for building elements dynamically. */\n createElement: CreateElement;\n defaultFallback: ReactNode;\n children: ReactNode;\n}\n\n/**\n * Framework-injected error boundary wrapper.\n * Wraps content with status-code error boundary handling.\n *\n * Field types must agree with `TimberErrorBoundaryProps` in\n * `client/error-boundary.tsx`. The two are kept structurally compatible by\n * convention rather than by direct type import — tree-builder.ts is the\n * server-side construction site and may not import types from a 'use client'\n * module to keep the server barrel free of client coupling.\n */\nexport interface ErrorBoundaryProps {\n /** The component to render when an error is caught (TSX status files). */\n fallbackComponent?: LoadedComponent;\n /** Pre-rendered fallback element (MDX status files — see TIM-503). */\n fallbackElement?: ReactNode;\n /** Status code filter: 400 = any 4xx, 500 = any 5xx, specific number = exact match. */\n status?: number;\n children: ReactNode;\n}\n\n// ─── Tree Builder ────────────────────────────────────────────────────────────\n\n/**\n * Result of building the element tree.\n */\nexport interface TreeBuildResult {\n /**\n * The root React element tree ready for renderToReadableStream.\n * `null` for API routes (route.ts), which don't render a React tree.\n */\n tree: ReactNode;\n /** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */\n isApiRoute: boolean;\n}\n\n/**\n * Build the unified element tree from a matched segment chain.\n *\n * Construction is bottom-up:\n * 1. Start with the page component (leaf segment)\n * 2. Wrap in status-code error boundaries (fallback chain)\n * 3. Wrap in AccessGate (if segment has access.ts)\n * 4. Pass as children to the segment's layout\n * 5. Repeat up the segment chain to root\n *\n * Parallel slots are resolved at each layout level and composed as named props.\n */\nexport async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {\n const { segments, loadModule, createElement, errorBoundaryComponent } = config;\n\n if (segments.length === 0) {\n throw new Error('[timber] buildElementTree: empty segment chain');\n }\n\n const leaf = segments[segments.length - 1];\n\n // API routes (route.ts) don't build a React tree\n if (leaf.route && !leaf.page) {\n return { tree: null, isApiRoute: true };\n }\n\n // Start with the page component\n const pageModule = leaf.page ? await loadModule(leaf.page) : null;\n const PageComponent = pageModule?.default as LoadedComponent | undefined;\n\n if (!PageComponent) {\n throw new Error(\n `[timber] No page component found for route at ${leaf.urlPath}. ` +\n 'Each route must have a page.tsx or route.ts.'\n );\n }\n\n // Build the page element — params are accessed via getSegmentParams() from ALS\n let element: ReactNode = createElement(PageComponent, {});\n\n // Build tree bottom-up: wrap page, then walk segments from leaf to root\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i];\n\n // Wrap in error boundaries (status-code files + error.tsx)\n element = await wrapWithErrorBoundaries(\n segment,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in AccessGate if segment has access.ts\n if (segment.access) {\n const accessModule = await loadModule(segment.access);\n const accessFn = accessModule.default as AccessGateProps['accessFn'];\n element = createElement('timber:access-gate', {\n accessFn,\n segmentName: segment.segmentName,\n children: element,\n } satisfies AccessGateProps);\n }\n\n // Wrap in layout (if exists and not the leaf's page-level wrapping)\n if (segment.layout) {\n const layoutModule = await loadModule(segment.layout);\n const LayoutComponent = layoutModule.default as LoadedComponent | undefined;\n\n if (LayoutComponent) {\n // Resolve parallel slots for this layout\n const slotProps: Record<string, ReactNode> = {};\n const slotNames = Object.keys(segment.slots);\n if (slotNames.length > 0) {\n for (const slotName of slotNames) {\n const slotNode = segment.slots[slotName]!;\n slotProps[slotName] = await buildSlotElement(\n slotNode,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n }\n }\n\n /* eslint-disable react/no-children-prop -- createElement API */\n element = createElement(LayoutComponent, {\n ...slotProps,\n children: element,\n });\n /* eslint-enable react/no-children-prop */\n }\n }\n }\n\n return { tree: element, isApiRoute: false };\n}\n\n// ─── Slot Element Builder ────────────────────────────────────────────────────\n\n/**\n * Build the element tree for a parallel slot.\n *\n * Slots have their own access.ts (SlotAccessGate) and error boundaries.\n * On access denial: denied.tsx → default.tsx → null (graceful degradation).\n */\nasync function buildSlotElement(\n slotNode: SegmentNode,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactNode> {\n // Load slot page\n const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;\n const PageComponent = pageModule?.default as LoadedComponent | undefined;\n\n // Load default.tsx fallback\n const defaultModule = slotNode.default ? await loadModule(slotNode.default) : null;\n const DefaultComponent = defaultModule?.default as LoadedComponent | undefined;\n\n // If no page, render default.tsx or null\n if (!PageComponent) {\n return DefaultComponent ? createElement(DefaultComponent, {}) : null;\n }\n\n let element: ReactNode = createElement(PageComponent, {});\n\n // Wrap in error boundaries\n element = await wrapWithErrorBoundaries(\n slotNode,\n element,\n loadModule,\n createElement,\n errorBoundaryComponent\n );\n\n // Wrap in SlotAccessGate if slot has access.ts\n if (slotNode.access) {\n const accessModule = await loadModule(slotNode.access);\n const accessFn = accessModule.default as SlotAccessGateProps['accessFn'];\n\n // Load denied.tsx — pass component (not pre-built element) so\n // SlotAccessGate can forward DenySignal.data dynamically. See TIM-488.\n const deniedModule = slotNode.denied ? await loadModule(slotNode.denied) : null;\n const DeniedComponent = (deniedModule?.default as LoadedComponent | undefined) ?? null;\n\n const defaultFallback = DefaultComponent ? createElement(DefaultComponent, {}) : null;\n\n element = createElement('timber:slot-access-gate', {\n accessFn,\n DeniedComponent,\n slotName: slotNode.segmentName.replace(/^@/, ''),\n createElement,\n defaultFallback,\n children: element,\n } satisfies SlotAccessGateProps);\n }\n\n return element;\n}\n\n// ─── Error Boundary Wrapping ─────────────────────────────────────────────────\n\n/** MDX/markdown extensions — these are server components that cannot be passed as function props. */\nconst MDX_EXTENSIONS = new Set(['mdx', 'md']);\n\n/**\n * Check if a route file is an MDX/markdown file based on its extension.\n * MDX components are server components by default and cannot cross the\n * RSC→client boundary as function props. They must be pre-rendered as\n * elements and passed as fallbackElement instead of fallbackComponent.\n */\nfunction isMdxFile(file: RouteFile): boolean {\n return MDX_EXTENSIONS.has(file.extension);\n}\n\n/**\n * Wrap an element with error boundaries from a segment's status-code files.\n *\n * Wrapping order (innermost to outermost):\n * 1. Specific status files (503.tsx, 429.tsx, etc.)\n * 2. Category catch-alls (4xx.tsx, 5xx.tsx)\n * 3. error.tsx (general error boundary)\n *\n * This creates the fallback chain described in design/10-error-handling.md.\n *\n * MDX status files are server components and cannot be passed as function\n * props to TimberErrorBoundary (a 'use client' component). Instead, they\n * are pre-rendered as elements and passed as fallbackElement. The error\n * boundary renders the element directly when an error is caught.\n * See TIM-503.\n */\nasync function wrapWithErrorBoundaries(\n segment: SegmentNode,\n element: ReactNode,\n loadModule: ModuleLoader,\n createElement: CreateElement,\n errorBoundaryComponent: unknown\n): Promise<ReactNode> {\n // Wrapping is applied inside-out. The last wrap call produces the outermost boundary.\n // Order: specific status → category → error.tsx (outermost)\n\n if (segment.statusFiles) {\n // Wrap with specific status files (innermost — highest priority at runtime)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key !== '4xx' && key !== '5xx') {\n const status = parseInt(key, 10);\n if (!isNaN(status)) {\n const mod = await loadModule(file);\n // mod.default is `unknown` — narrow to a component reference.\n // `isValidElementType` accepts memo/forwardRef objects in addition to\n // bare functions; non-component values fall through.\n const Component = isValidElementType(mod.default) ? mod.default : null;\n if (Component) {\n const boundaryProps: ErrorBoundaryProps = isMdxFile(file)\n ? {\n fallbackElement: createElement(Component, { status }),\n status,\n children: element,\n }\n : {\n fallbackComponent: Component,\n status,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n }\n }\n\n // Wrap with category catch-alls (4xx.tsx, 5xx.tsx)\n for (const [key, file] of Object.entries(segment.statusFiles)) {\n if (key === '4xx' || key === '5xx') {\n const mod = await loadModule(file);\n const Component = isValidElementType(mod.default) ? mod.default : null;\n if (Component) {\n const categoryStatus = key === '4xx' ? 400 : 500;\n const boundaryProps: ErrorBoundaryProps = isMdxFile(file)\n ? {\n fallbackElement: createElement(Component, {}),\n status: categoryStatus,\n children: element,\n }\n : {\n fallbackComponent: Component,\n status: categoryStatus,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n }\n }\n\n // Wrap with error.tsx (outermost — catches anything not matched by status files)\n // Note: error.tsx/error.mdx receives { error, digest, reset } props.\n // MDX error files are pre-rendered without those props (they're static content).\n if (segment.error) {\n const errorModule = await loadModule(segment.error);\n const ErrorComponent = isValidElementType(errorModule.default) ? errorModule.default : null;\n if (ErrorComponent) {\n const boundaryProps: ErrorBoundaryProps = isMdxFile(segment.error)\n ? {\n fallbackElement: createElement(ErrorComponent, {}),\n children: element,\n }\n : {\n fallbackComponent: ErrorComponent,\n children: element,\n };\n element = createElement(errorBoundaryComponent, boundaryProps);\n }\n }\n\n return element;\n}\n","/**\n * CSRF protection — Origin header validation.\n *\n * Auto-derived from the Host header for single-origin deployments.\n * Configurable via allowedOrigins for multi-origin setups.\n * Disable with csrf: false (not recommended outside local dev).\n *\n * See design/08-forms-and-actions.md §\"CSRF Protection\"\n * See design/13-security.md §\"Security Testing Checklist\" #6\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface CsrfConfig {\n /** Explicit list of allowed origins. Replaces Host-based auto-derivation. */\n allowedOrigins?: string[];\n /** Set to false to disable CSRF validation entirely. */\n csrf?: boolean;\n}\n\nexport type CsrfResult = { ok: true } | { ok: false; status: 403 };\n\n// ─── Constants ────────────────────────────────────────────────────────────\n\n/** HTTP methods that are considered safe (no mutation). */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n// ─── Implementation ───────────────────────────────────────────────────────\n\n/**\n * Derive the request's scheme from trusted sources.\n *\n * Priority:\n * 1. X-Forwarded-Proto (set by reverse proxies / load balancers)\n * 2. Request URL protocol\n *\n * Falls back to 'https' (fail closed) if neither is available.\n */\nfunction deriveRequestScheme(req: Request): string {\n const forwarded = req.headers.get('X-Forwarded-Proto');\n if (forwarded) {\n const proto = forwarded.split(',')[0].trim().toLowerCase();\n if (proto === 'http' || proto === 'https') return proto;\n }\n\n try {\n return new URL(req.url).protocol.replace(':', '');\n } catch {\n return 'https';\n }\n}\n\n/**\n * Validate the Origin header against the request's full origin\n * (scheme + host + port).\n *\n * For mutation methods (POST, PUT, PATCH, DELETE):\n * - If `csrf: false`, skip validation.\n * - If `allowedOrigins` is set, Origin must match one exactly (no wildcards).\n * - Otherwise, Origin must match the derived request origin.\n *\n * Safe methods (GET, HEAD, OPTIONS) always pass.\n */\nexport function validateCsrf(req: Request, config: CsrfConfig): CsrfResult {\n // Safe methods don't need CSRF protection\n if (SAFE_METHODS.has(req.method)) {\n return { ok: true };\n }\n\n // Explicitly disabled\n if (config.csrf === false) {\n return { ok: true };\n }\n\n const origin = req.headers.get('Origin');\n\n // No Origin header on a mutation → reject\n if (!origin) {\n return { ok: false, status: 403 };\n }\n\n // If allowedOrigins is configured, use that instead of Host-based derivation\n if (config.allowedOrigins) {\n const allowed = config.allowedOrigins.includes(origin);\n return allowed ? { ok: true } : { ok: false, status: 403 };\n }\n\n // Auto-derive from Host header\n const host = req.headers.get('Host');\n if (!host) {\n return { ok: false, status: 403 };\n }\n\n // Compare full origins (scheme + host + port) using URL.origin for\n // canonical port normalization (e.g. https://x:443 → https://x).\n let originOrigin: string;\n try {\n originOrigin = new URL(origin).origin;\n } catch {\n return { ok: false, status: 403 };\n }\n\n const scheme = deriveRequestScheme(req);\n let expectedOrigin: string;\n try {\n expectedOrigin = new URL(`${scheme}://${host}`).origin;\n } catch {\n return { ok: false, status: 403 };\n }\n\n return originOrigin === expectedOrigin ? { ok: true } : { ok: false, status: 403 };\n}\n","/**\n * Request body size limits — returns 413 when exceeded.\n * See design/08-forms-and-actions.md §\"FormData Limits\"\n */\n\nexport interface BodyLimitsConfig {\n limits?: {\n actionBodySize?: string;\n uploadBodySize?: string;\n maxFields?: number;\n };\n}\n\nexport type BodyLimitResult = { ok: true } | { ok: false; status: 411 | 413 };\n\nexport type BodyKind = 'action' | 'upload';\n\nconst KB = 1024;\nconst MB = 1024 * KB;\nconst GB = 1024 * MB;\n\nexport const DEFAULT_LIMITS = {\n actionBodySize: 1 * MB,\n uploadBodySize: 10 * MB,\n maxFields: 100,\n} as const;\n\nconst SIZE_PATTERN = /^(\\d+(?:\\.\\d+)?)\\s*(kb|mb|gb)?$/i;\n\n/** Parse a human-readable size string (\"1mb\", \"512kb\", \"1024\") into bytes. */\nexport function parseBodySize(size: string): number {\n const match = SIZE_PATTERN.exec(size.trim());\n if (!match) {\n throw new Error(\n `Invalid body size format: \"${size}\". Expected format like \"1mb\", \"512kb\", or \"1024\".`\n );\n }\n\n const value = Number.parseFloat(match[1]);\n const unit = (match[2] ?? '').toLowerCase();\n\n switch (unit) {\n case 'kb':\n return Math.floor(value * KB);\n case 'mb':\n return Math.floor(value * MB);\n case 'gb':\n return Math.floor(value * GB);\n case '':\n return Math.floor(value);\n default:\n throw new Error(`Unknown size unit: \"${unit}\"`);\n }\n}\n\n/** Strict integer pattern: one or more digits, nothing else. */\nconst STRICT_INTEGER_RE = /^\\d+$/;\n\n/** Check whether a request body exceeds the configured size limit (stateless, no ALS). */\nexport function enforceBodyLimits(\n req: Request,\n kind: BodyKind,\n config: BodyLimitsConfig\n): BodyLimitResult {\n const contentLength = req.headers.get('Content-Length');\n if (!contentLength) {\n // Reject requests without Content-Length — prevents body limit bypass via\n // chunked transfer-encoding. Browsers always send Content-Length for form POSTs.\n return { ok: false, status: 411 };\n }\n\n // Reject malformed values: multiple values (\"100, 999999\"), negative numbers,\n // floats (\"100.5\"), or non-numeric strings. parseInt would silently parse\n // the first number from \"100, 999999\" as 100, letting the real body through.\n const trimmed = contentLength.trim();\n if (!STRICT_INTEGER_RE.test(trimmed)) {\n return { ok: false, status: 411 };\n }\n\n const bodySize = Number.parseInt(trimmed, 10);\n\n const limit = resolveLimit(kind, config);\n return bodySize <= limit ? { ok: true } : { ok: false, status: 413 };\n}\n\n/** Check whether a FormData payload exceeds the configured field count limit. */\nexport function enforceFieldLimit(formData: FormData, config: BodyLimitsConfig): BodyLimitResult {\n const maxFields = config.limits?.maxFields ?? DEFAULT_LIMITS.maxFields;\n // Count unique keys — FormData.keys() yields duplicates for multi-value fields,\n // so we use a Set to count distinct field names.\n const fieldCount = new Set(formData.keys()).size;\n return fieldCount <= maxFields ? { ok: true } : { ok: false, status: 413 };\n}\n\n/**\n * Resolve the byte limit for a given body kind, using config overrides or defaults.\n */\nfunction resolveLimit(kind: BodyKind, config: BodyLimitsConfig): number {\n const userLimits = config.limits;\n\n if (kind === 'action') {\n return userLimits?.actionBodySize\n ? parseBodySize(userLimits.actionBodySize)\n : DEFAULT_LIMITS.actionBodySize;\n }\n\n return userLimits?.uploadBodySize\n ? parseBodySize(userLimits.uploadBodySize)\n : DEFAULT_LIMITS.uploadBodySize;\n}\n","/**\n * Route handler for route.ts API endpoints.\n *\n * route.ts files export named HTTP method handlers (GET, POST, etc.).\n * They share the same pipeline (proxy → match → middleware → access → handler)\n * but don't render React trees.\n *\n * See design/07-routing.md §\"route.ts — API Endpoints\"\n */\n\nimport type { RouteContext } from './types.js';\nimport { logRouteError } from './logger.js';\nimport { DenySignal, RedirectSignal } from './primitives.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/** HTTP methods that route.ts can export as named handlers. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n/** A single route handler function — one-arg signature. */\nexport type RouteHandler = (ctx: RouteContext) => Response | Promise<Response>;\n\n/** A route.ts module — named exports for each supported HTTP method. */\nexport type RouteModule = {\n [K in HttpMethod]?: RouteHandler;\n};\n\n/** All recognized HTTP method export names. */\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\n\n// ─── Allowed Methods ─────────────────────────────────────────────────────\n\n/**\n * Resolve the full list of allowed methods for a route module.\n *\n * Includes:\n * - All explicitly exported methods\n * - HEAD (implicit when GET is exported)\n * - OPTIONS (always implicit)\n */\nexport function resolveAllowedMethods(mod: RouteModule): HttpMethod[] {\n const methods: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (method === 'HEAD' || method === 'OPTIONS') continue;\n if (mod[method]) {\n methods.push(method);\n }\n }\n\n // HEAD is implicit when GET is exported\n if (mod.GET && !mod.HEAD) {\n methods.push('HEAD');\n } else if (mod.HEAD) {\n methods.push('HEAD');\n }\n\n // OPTIONS is always implicit\n if (!mod.OPTIONS) {\n methods.push('OPTIONS');\n } else {\n methods.push('OPTIONS');\n }\n\n return methods;\n}\n\n// ─── Route Request Handler ───────────────────────────────────────────────\n\n/**\n * Handle an incoming request against a route.ts module.\n *\n * Dispatches to the named method handler, auto-generates 405/OPTIONS,\n * and merges response headers from ctx.headers.\n */\nexport async function handleRouteRequest(mod: RouteModule, ctx: RouteContext): Promise<Response> {\n const method = ctx.req.method.toUpperCase() as HttpMethod;\n const allowed = resolveAllowedMethods(mod);\n const allowHeader = allowed.join(', ');\n\n // Auto OPTIONS — 204 with Allow header\n if (method === 'OPTIONS') {\n if (mod.OPTIONS) {\n return runHandler(mod.OPTIONS, ctx);\n }\n return new Response(null, {\n status: 204,\n headers: { Allow: allowHeader },\n });\n }\n\n // HEAD fallback — run GET, strip body\n if (method === 'HEAD') {\n if (mod.HEAD) {\n return runHandler(mod.HEAD, ctx);\n }\n if (mod.GET) {\n const res = await runHandler(mod.GET, ctx);\n // Return headers + status but no body\n return new Response(null, {\n status: res.status,\n headers: res.headers,\n });\n }\n }\n\n // Dispatch to the named handler\n const handler = mod[method];\n if (!handler) {\n return new Response(null, {\n status: 405,\n headers: { Allow: allowHeader },\n });\n }\n\n return runHandler(handler, ctx);\n}\n\n/**\n * Run a handler, merge ctx.headers into the response, and catch errors.\n */\nasync function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Response> {\n try {\n const res = await handler(ctx);\n return mergeResponseHeaders(res, ctx.headers);\n } catch (error) {\n // Control-flow signals — propagate to the API route dispatcher so deny()\n // gets the JSON status-file chain and redirect() gets a proper 3xx with\n // Location. Not errors; don't log them.\n if (error instanceof DenySignal || error instanceof RedirectSignal) {\n throw error;\n }\n logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });\n return new Response(null, { status: 500 });\n }\n}\n\n/**\n * Merge response headers from ctx.headers into the handler's response.\n * ctx.headers (set by middleware or the handler) are applied to the final response.\n * Handler-set headers take precedence over ctx.headers.\n */\nfunction mergeResponseHeaders(res: Response, ctxHeaders: Headers): Response {\n // If no ctx headers to merge, return as-is\n let hasCtxHeaders = false;\n ctxHeaders.forEach(() => {\n hasCtxHeaders = true;\n });\n if (!hasCtxHeaders) return res;\n\n // Merge: ctx.headers first, then handler response headers override.\n // Set-Cookie needs special handling: Headers.set() replaces all values\n // for a key, but each Set-Cookie must be its own header per RFC 6265 §4.1.\n // Use append for Set-Cookie, set for everything else.\n const merged = new Headers();\n ctxHeaders.forEach((value, key) => {\n if (key.toLowerCase() === 'set-cookie') {\n merged.append(key, value);\n } else {\n merged.set(key, value);\n }\n });\n // Response Set-Cookie headers: use getSetCookie() to preserve individual\n // cookies (forEach joins them with \", \" into one entry).\n const resCookies = res.headers.getSetCookie();\n for (const cookie of resCookies) {\n merged.append('Set-Cookie', cookie);\n }\n res.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'set-cookie') {\n merged.set(key, value);\n }\n });\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: merged,\n });\n}\n","/**\n * Render timeout utilities for SSR streaming pipeline.\n *\n * Provides a RenderTimeoutError class and a helper to create\n * timeout-guarded AbortSignals. Used to defend against hung RSC\n * streams and infinite SSR renders.\n *\n * Design doc: 02-rendering-pipeline.md §\"Streaming Constraints\"\n */\n\n/**\n * Error thrown when an SSR render or RSC stream read exceeds the\n * configured timeout. Callers can check `instanceof RenderTimeoutError`\n * to distinguish timeout from other errors and return a 504 or close\n * the connection cleanly.\n */\nexport class RenderTimeoutError extends Error {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number, context?: string) {\n const message = context\n ? `Render timeout after ${timeoutMs}ms: ${context}`\n : `Render timeout after ${timeoutMs}ms`;\n super(message);\n this.name = 'RenderTimeoutError';\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Result of createRenderTimeout — an AbortSignal that fires after\n * the given duration, plus a cancel function to clear the timer\n * when the render completes normally.\n */\nexport interface RenderTimeout {\n /** AbortSignal that aborts after timeoutMs. */\n signal: AbortSignal;\n /** Cancel the timeout timer. Call this when the render completes. */\n cancel: () => void;\n}\n\n/**\n * Create a render timeout that aborts after the given duration.\n *\n * Returns an AbortSignal and a cancel function. The signal fires\n * with a RenderTimeoutError as the abort reason after `timeoutMs`.\n * Call `cancel()` when the render completes to prevent the timeout\n * from firing.\n *\n * If an existing `parentSignal` is provided, the returned signal\n * aborts when either the parent signal or the timeout fires —\n * whichever comes first.\n */\nexport function createRenderTimeout(timeoutMs: number, parentSignal?: AbortSignal): RenderTimeout {\n const controller = new AbortController();\n const reason = new RenderTimeoutError(timeoutMs, 'RSC stream read timed out');\n\n const timer = setTimeout(() => controller.abort(reason), timeoutMs);\n\n let onParentAbort: (() => void) | null = null;\n\n if (parentSignal) {\n if (parentSignal.aborted) {\n clearTimeout(timer);\n controller.abort(parentSignal.reason);\n } else {\n onParentAbort = () => {\n clearTimeout(timer);\n controller.abort(parentSignal.reason);\n };\n parentSignal.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (onParentAbort && parentSignal) {\n parentSignal.removeEventListener('abort', onParentAbort);\n onParentAbort = null;\n }\n },\n };\n}\n\n/**\n * Race a promise against a timeout. Rejects with RenderTimeoutError\n * if the promise does not resolve within `timeoutMs`.\n *\n * Used to guard individual `rscReader.read()` calls inside pullLoop.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n context?: string\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n const timeoutPromise = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(new RenderTimeoutError(timeoutMs, context));\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timer!);\n });\n}\n"],"mappings":"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"}