@timber-js/app 0.1.47 → 0.1.49

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.
Files changed (70) hide show
  1. package/dist/_chunks/{als-registry-c0AGnbqS.js → als-registry-k-AtAQ9R.js} +4 -2
  2. package/dist/_chunks/{als-registry-c0AGnbqS.js.map → als-registry-k-AtAQ9R.js.map} +1 -1
  3. package/dist/_chunks/interception-DGDIjDbR.js.map +1 -1
  4. package/dist/_chunks/{request-context-C69VW4xS.js → request-context-CRj2Zh1E.js} +2 -2
  5. package/dist/_chunks/request-context-CRj2Zh1E.js.map +1 -0
  6. package/dist/_chunks/{tracing-tIvqStk8.js → tracing-DF0G3FB7.js} +2 -2
  7. package/dist/_chunks/{tracing-tIvqStk8.js.map → tracing-DF0G3FB7.js.map} +1 -1
  8. package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -1
  9. package/dist/adapters/nitro.d.ts.map +1 -1
  10. package/dist/adapters/nitro.js +34 -7
  11. package/dist/adapters/nitro.js.map +1 -1
  12. package/dist/cache/index.js +2 -2
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/navigation-context.d.ts +1 -1
  15. package/dist/client/navigation-context.d.ts.map +1 -1
  16. package/dist/client/router.d.ts.map +1 -1
  17. package/dist/client/transition-root.d.ts.map +1 -1
  18. package/dist/client/use-query-states.d.ts.map +1 -1
  19. package/dist/client/use-router.d.ts.map +1 -1
  20. package/dist/cookies/index.js +2 -2
  21. package/dist/index.d.ts +8 -12
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +53 -10
  24. package/dist/index.js.map +1 -1
  25. package/dist/plugins/chunks.d.ts.map +1 -1
  26. package/dist/plugins/entries.d.ts +10 -0
  27. package/dist/plugins/entries.d.ts.map +1 -1
  28. package/dist/routing/scanner.d.ts.map +1 -1
  29. package/dist/server/als-registry.d.ts +4 -0
  30. package/dist/server/als-registry.d.ts.map +1 -1
  31. package/dist/server/index.js +39 -7
  32. package/dist/server/index.js.map +1 -1
  33. package/dist/server/metadata-platform.d.ts.map +1 -1
  34. package/dist/server/metadata-social.d.ts.map +1 -1
  35. package/dist/server/pipeline.d.ts.map +1 -1
  36. package/dist/server/primitives.d.ts +8 -4
  37. package/dist/server/primitives.d.ts.map +1 -1
  38. package/dist/server/request-context.d.ts.map +1 -1
  39. package/dist/server/rsc-entry/index.d.ts +1 -0
  40. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  41. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  42. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  43. package/dist/server/rsc-prop-warnings.d.ts.map +1 -1
  44. package/dist/server/waituntil-bridge.d.ts +27 -0
  45. package/dist/server/waituntil-bridge.d.ts.map +1 -0
  46. package/package.json +2 -2
  47. package/src/adapters/nitro.ts +55 -10
  48. package/src/client/browser-entry.ts +80 -12
  49. package/src/client/navigation-context.ts +4 -1
  50. package/src/client/router.ts +5 -7
  51. package/src/client/transition-root.tsx +5 -11
  52. package/src/client/use-query-states.ts +4 -1
  53. package/src/client/use-router.ts +3 -1
  54. package/src/index.ts +8 -25
  55. package/src/plugins/chunks.ts +2 -1
  56. package/src/plugins/entries.ts +66 -2
  57. package/src/routing/scanner.ts +1 -4
  58. package/src/server/als-registry.ts +10 -0
  59. package/src/server/compress.ts +0 -1
  60. package/src/server/metadata-platform.ts +4 -1
  61. package/src/server/metadata-social.ts +4 -1
  62. package/src/server/pipeline.ts +6 -23
  63. package/src/server/primitives.ts +19 -9
  64. package/src/server/request-context.ts +1 -5
  65. package/src/server/rsc-entry/index.ts +16 -0
  66. package/src/server/rsc-entry/rsc-stream.ts +1 -4
  67. package/src/server/rsc-entry/ssr-renderer.ts +1 -3
  68. package/src/server/rsc-prop-warnings.ts +7 -17
  69. package/src/server/waituntil-bridge.ts +34 -0
  70. package/dist/_chunks/request-context-C69VW4xS.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/client/link-navigate-interceptor.tsx","../../src/client/use-link-status.ts","../../src/client/navigation-context.ts","../../src/client/link-status-provider.tsx","../../src/client/link.tsx","../../src/client/segment-cache.ts","../../src/client/history.ts","../../src/client/use-params.ts","../../src/client/router.ts","../../src/client/use-navigation-pending.ts","../../src/client/router-ref.ts","../../src/client/use-router.ts","../../src/client/use-pathname.ts","../../src/client/use-search-params.ts","../../src/client/segment-context.ts","../../src/client/use-selected-layout-segment.ts","../../src/client/form.tsx"],"sourcesContent":["'use client';\n\n// LinkNavigateInterceptor — client component that stores an onNavigate callback\n// on the parent <a> element so the delegated click handler in browser-entry.ts\n// can invoke it before triggering SPA navigation.\n//\n// See design/19-client-navigation.md, TIM-167\n\nimport { useRef, useEffect, type ReactNode } from 'react';\n\n/** Symbol used to store the onNavigate callback on anchor elements. */\nexport const ON_NAVIGATE_KEY = '__timberOnNavigate' as const;\n\nexport type OnNavigateEvent = {\n preventDefault: () => void;\n};\n\nexport type OnNavigateHandler = (e: OnNavigateEvent) => void;\n\n/**\n * Augment HTMLAnchorElement with the optional onNavigate property.\n * Used by browser-entry.ts handleLinkClick to check for the callback.\n */\ndeclare global {\n interface HTMLAnchorElement {\n [ON_NAVIGATE_KEY]?: OnNavigateHandler;\n }\n}\n\n/**\n * Client component rendered inside <Link> that attaches the onNavigate\n * callback to the closest <a> ancestor via a DOM property. The callback\n * is cleaned up on unmount.\n *\n * Renders no extra DOM — just a transparent wrapper.\n */\nexport function LinkNavigateInterceptor({\n onNavigate,\n children,\n}: {\n onNavigate: OnNavigateHandler;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const anchor = ref.current?.closest('a');\n if (!anchor) return;\n anchor[ON_NAVIGATE_KEY] = onNavigate;\n return () => {\n delete anchor[ON_NAVIGATE_KEY];\n };\n }, [onNavigate]);\n\n // Use a <span> with display:contents to avoid affecting layout.\n // The ref lets us walk up to the parent <a> in the effect.\n return (\n <span ref={ref} style={{ display: 'contents' }}>\n {children}\n </span>\n );\n}\n","'use client';\n\n// useLinkStatus — returns { pending: true } while the nearest parent <Link>'s\n// navigation is in flight. No arguments — scoped via React context.\n// See design/19-client-navigation.md §\"useLinkStatus()\"\n\nimport { useContext, createContext } from 'react';\n\nexport interface LinkStatus {\n pending: boolean;\n}\n\n/**\n * React context provided by <Link>. Holds the pending status\n * for that specific link's navigation.\n */\nexport const LinkStatusContext = createContext<LinkStatus>({ pending: false });\n\n/**\n * Returns `{ pending: true }` while the nearest parent `<Link>` component's\n * navigation is in flight. Must be used inside a `<Link>` component's children.\n *\n * Unlike `useNavigationPending()` which is global, this hook is scoped to\n * the nearest parent `<Link>` — only the link the user clicked shows pending.\n *\n * ```tsx\n * 'use client'\n * import { Link, useLinkStatus } from '@timber-js/app/client'\n *\n * function Hint() {\n * const { pending } = useLinkStatus()\n * return <span className={pending ? 'opacity-50' : ''} />\n * }\n *\n * export function NavLink({ href, children }) {\n * return (\n * <Link href={href}>\n * {children} <Hint />\n * </Link>\n * )\n * }\n * ```\n */\nexport function useLinkStatus(): LinkStatus {\n return useContext(LinkStatusContext);\n}\n","'use client';\n\n/**\n * NavigationContext — React context for navigation state.\n *\n * Holds the current route params and pathname, updated atomically\n * with the RSC tree on each navigation. This replaces the previous\n * useSyncExternalStore approach for useParams() and usePathname(),\n * which suffered from a timing gap: the new tree could commit before\n * the external store re-renders fired, causing a frame where both\n * old and new active states were visible simultaneously.\n *\n * By wrapping the RSC payload element in NavigationProvider inside\n * renderRoot(), the context value and the element tree are passed to\n * reactRoot.render() in the same call — atomic by construction.\n * All consumers (useParams, usePathname) see the new values in the\n * same render pass as the new tree.\n *\n * During SSR, no NavigationProvider is mounted. Hooks fall back to\n * the ALS-backed getSsrData() for per-request isolation.\n *\n * IMPORTANT: createContext and useContext are NOT available in the RSC\n * environment (React Server Components use a stripped-down React).\n * The context is lazily initialized on first access, and all functions\n * that depend on these APIs are safe to call from any environment —\n * they return null or no-op when the APIs aren't available.\n *\n * See design/19-client-navigation.md §\"NavigationContext\"\n */\n\nimport React, { createElement, type ReactNode } from 'react';\n\n// ---------------------------------------------------------------------------\n// Context type\n// ---------------------------------------------------------------------------\n\nexport interface NavigationState {\n params: Record<string, string | string[]>;\n pathname: string;\n}\n\n// ---------------------------------------------------------------------------\n// Lazy context initialization\n// ---------------------------------------------------------------------------\n\n/**\n * The context is created lazily to avoid calling createContext at module\n * level. In the RSC environment, React.createContext doesn't exist —\n * calling it at import time would crash the server.\n *\n * IMPORTANT: Context instances are stored on globalThis, NOT in module-\n * level variables. The RSC client bundler duplicates this module across\n * the browser-entry chunk (index) and client-reference chunk (shared-app)\n * because both entry graphs import it. Module-level variables would create\n * separate singleton instances per chunk — the provider in TransitionRoot\n * (index chunk) would use context A while the consumer in LinkStatusProvider\n * (shared-app chunk) reads from context B. globalThis guarantees a single\n * instance regardless of how many times the module is duplicated.\n *\n * See design/19-client-navigation.md §\"Singleton Context Guarantee\"\n */\n\n// Symbol keys for globalThis storage — prevents collisions with user code\nconst NAV_CTX_KEY = Symbol.for('__timber_nav_ctx');\nconst PENDING_CTX_KEY = Symbol.for('__timber_pending_nav_ctx');\n\nfunction getOrCreateContext(): React.Context<NavigationState | null> | undefined {\n const existing = (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] as\n | React.Context<NavigationState | null>\n | undefined;\n if (existing !== undefined) return existing;\n // createContext may not exist in the RSC environment\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<NavigationState | null>(null);\n (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] = ctx;\n return ctx;\n }\n return undefined;\n}\n\n/**\n * Read the navigation context. Returns null during SSR (no provider)\n * or in the RSC environment (no context available).\n * Internal — used by useParams() and usePathname().\n */\nexport function useNavigationContext(): NavigationState | null {\n const ctx = getOrCreateContext();\n if (!ctx) return null;\n // useContext may not exist in the RSC environment — caller wraps in try/catch\n if (typeof React.useContext !== 'function') return null;\n return React.useContext(ctx);\n}\n\n// ---------------------------------------------------------------------------\n// Provider component\n// ---------------------------------------------------------------------------\n\nexport interface NavigationProviderProps {\n value: NavigationState;\n children?: ReactNode;\n}\n\n/**\n * Wraps children with NavigationContext.Provider.\n *\n * Used in browser-entry.ts renderRoot to wrap the RSC payload element\n * so that navigation state updates atomically with the tree render.\n */\nexport function NavigationProvider({ value, children }: NavigationProviderProps): React.ReactElement {\n const ctx = getOrCreateContext();\n if (!ctx) {\n // RSC environment — no context available. Return children as-is.\n return children as React.ReactElement;\n }\n return createElement(ctx.Provider, { value }, children);\n}\n\n// ---------------------------------------------------------------------------\n// Module-level state for renderRoot to read\n// ---------------------------------------------------------------------------\n\n/**\n * Navigation state communicated between the router and renderRoot.\n *\n * The router calls setNavigationState() before renderRoot(). The\n * renderRoot callback reads via getNavigationState() to create the\n * NavigationProvider with the correct params/pathname.\n *\n * This is NOT used by hooks directly — hooks read from React context.\n *\n * Stored on globalThis (like the context instances above) because the\n * router lives in the shared-app chunk while renderRoot lives in the\n * index chunk. Module-level variables would be separate per chunk.\n */\nconst NAV_STATE_KEY = Symbol.for('__timber_nav_state');\n\nfunction _getNavStateStore(): { current: NavigationState } {\n const g = globalThis as Record<symbol, unknown>;\n if (!g[NAV_STATE_KEY]) {\n g[NAV_STATE_KEY] = { current: { params: {}, pathname: '/' } };\n }\n return g[NAV_STATE_KEY] as { current: NavigationState };\n}\n\nexport function setNavigationState(state: NavigationState): void {\n _getNavStateStore().current = state;\n}\n\nexport function getNavigationState(): NavigationState {\n return _getNavStateStore().current;\n}\n\n// ---------------------------------------------------------------------------\n// Pending Navigation Context (same module for singleton guarantee)\n// ---------------------------------------------------------------------------\n\n/**\n * Separate context for the in-flight navigation URL. Provided by\n * TransitionRoot (urgent useState), consumed by LinkStatusProvider\n * and useNavigationPending.\n *\n * Uses globalThis via Symbol.for for the same reason as NavigationContext\n * above — the bundler duplicates this module across chunks, and module-\n * level variables would create separate context instances.\n */\n\nfunction getOrCreatePendingContext(): React.Context<string | null> | undefined {\n const existing = (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] as\n | React.Context<string | null>\n | undefined;\n if (existing !== undefined) return existing;\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<string | null>(null);\n (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] = ctx;\n return ctx;\n }\n return undefined;\n}\n\n/**\n * Read the pending navigation URL from context.\n * Returns null during SSR (no provider) or in the RSC environment.\n */\nexport function usePendingNavigationUrl(): string | null {\n const ctx = getOrCreatePendingContext();\n if (!ctx) return null;\n if (typeof React.useContext !== 'function') return null;\n return React.useContext(ctx);\n}\n\n/**\n * Provider for the pending navigation URL. Wraps children with\n * the pending context Provider.\n */\nexport function PendingNavigationProvider({\n value,\n children,\n}: {\n value: string | null;\n children?: ReactNode;\n}): React.ReactElement {\n const ctx = getOrCreatePendingContext();\n if (!ctx) {\n return children as React.ReactElement;\n }\n return createElement(ctx.Provider, { value }, children);\n}\n","'use client';\n\n// LinkStatusProvider — client component that provides per-link pending status\n// via React context. Used inside <Link> to power useLinkStatus().\n//\n// Reads pendingUrl from PendingNavigationContext (provided by TransitionRoot).\n// The pending URL is set as an URGENT update at navigation start (shows\n// immediately) and cleared inside startTransition when the new tree commits\n// (atomic with params/pathname). This eliminates both:\n// 1. The delay before showing the spinner (urgent update, not deferred)\n// 2. The gap between spinner disappearing and active state updating (same commit)\n\nimport type { ReactNode } from 'react';\nimport { LinkStatusContext, type LinkStatus } from './use-link-status.js';\nimport { usePendingNavigationUrl } from './navigation-context.js';\n\nconst NOT_PENDING: LinkStatus = { pending: false };\nconst IS_PENDING: LinkStatus = { pending: true };\n\n/**\n * Client component that reads the pending URL from PendingNavigationContext\n * and provides a scoped LinkStatusContext to children. Renders no extra DOM —\n * just a context provider around children.\n */\nexport function LinkStatusProvider({ href, children }: { href: string; children?: ReactNode }) {\n const pendingUrl = usePendingNavigationUrl();\n const status = pendingUrl === href ? IS_PENDING : NOT_PENDING;\n\n return <LinkStatusContext.Provider value={status}>{children}</LinkStatusContext.Provider>;\n}\n","'use client';\n\n// Link component — client-side navigation with progressive enhancement\n// See design/19-client-navigation.md § Progressive Enhancement\n//\n// Without JavaScript, <Link> renders as a plain <a> tag — standard browser\n// navigation. With JavaScript, the client runtime intercepts clicks on links\n// marked with data-timber-link, fetches RSC payloads, and reconciles the DOM.\n//\n// Typed Link: design/09-typescript.md §\"Typed Link\"\n// - href validated against known routes (via codegen overloads, not runtime)\n// - params prop typed per-route, URL interpolated at runtime\n// - searchParams prop serialized via SearchParamsDefinition\n// - params and fully-resolved string href are mutually exclusive\n// - searchParams and inline query string are mutually exclusive\n\nimport type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport type { SearchParamsDefinition } from '#/search-params/create.js';\nimport type { OnNavigateHandler } from './link-navigate-interceptor.js';\nimport { LinkNavigateInterceptor } from './link-navigate-interceptor.js';\nimport { LinkStatusProvider } from './link-status-provider.js';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/**\n * Base props shared by all Link variants.\n */\ninterface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** Prefetch the RSC payload on hover */\n prefetch?: boolean;\n /**\n * Scroll to top on navigation. Defaults to true.\n * Set to false for tabbed interfaces where content changes within a fixed layout.\n */\n scroll?: boolean;\n /**\n * Called before client-side navigation commits. Call `e.preventDefault()`\n * to cancel the default navigation — the caller is then responsible for\n * navigating (e.g. via `router.push()`).\n *\n * Only fires for client-side SPA navigations, not full page loads.\n * Has no effect during SSR.\n */\n onNavigate?: OnNavigateHandler;\n children?: ReactNode;\n}\n\n/**\n * Link with a fully-resolved string href.\n * When using a string href with params already interpolated,\n * the params prop is not available.\n */\nexport interface LinkPropsWithHref extends LinkBaseProps {\n href: string;\n params?: never;\n /**\n * Typed search params — serialized via the route's SearchParamsDefinition.\n * Mutually exclusive with an inline query string in href.\n */\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n}\n\n/**\n * Link with a route pattern + params for interpolation.\n * e.g. <Link href=\"/products/[id]\" params={{ id: \"123\" }}>\n * <Link href=\"/products/[id]\" params={{ id: 123 }}>\n */\nexport interface LinkPropsWithParams extends LinkBaseProps {\n /** Route pattern with dynamic segments (e.g. \"/products/[id]\") */\n href: string;\n /**\n * Dynamic segment values to interpolate into the href.\n * Single dynamic segments accept string | number (numbers are stringified).\n * Catch-all segments accept string[].\n */\n params: Record<string, string | number | string[]>;\n /**\n * Typed search params — serialized via the route's SearchParamsDefinition.\n */\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n}\n\nexport type LinkProps = LinkPropsWithHref | LinkPropsWithParams;\n\n// ─── Dangerous URL Scheme Detection ──────────────────────────────\n\n/**\n * Reject dangerous URL schemes that could execute script.\n * Security: design/13-security.md § Link scheme injection (test #9)\n */\nconst DANGEROUS_SCHEMES = /^\\s*(javascript|data|vbscript):/i;\n\nexport function validateLinkHref(href: string): void {\n if (DANGEROUS_SCHEMES.test(href)) {\n throw new Error(\n `<Link> received a dangerous href: \"${href}\". ` +\n 'javascript:, data:, and vbscript: URLs are not allowed.'\n );\n }\n}\n\n// ─── Internal Link Detection ─────────────────────────────────────\n\n/** Returns true if the href is an internal path (not an external URL) */\nfunction isInternalHref(href: string): boolean {\n // Relative paths, root-relative paths, and hash links are internal\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return true;\n }\n // Anything with a protocol scheme is external\n if (/^[a-z][a-z0-9+.-]*:/i.test(href)) {\n return false;\n }\n // Bare relative paths (e.g., \"dashboard\") are internal\n return true;\n}\n\n// ─── URL Interpolation ──────────────────────────────────────────\n\n/**\n * Interpolate dynamic segments in a route pattern with actual values.\n * e.g. interpolateParams(\"/products/[id]\", { id: \"123\" }) → \"/products/123\"\n *\n * Supports:\n * - [param] → single segment\n * - [...param] → catch-all (joined with /)\n * - [[...param]] → optional catch-all (omitted if undefined/empty)\n */\nexport function interpolateParams(\n pattern: string,\n params: Record<string, string | number | string[]>\n): string {\n return (\n pattern\n .replace(\n /\\[\\[\\.\\.\\.(\\w+)\\]\\]|\\[\\.\\.\\.(\\w+)\\]|\\[(\\w+)\\]/g,\n (_match, optionalCatchAll, catchAll, single) => {\n if (optionalCatchAll) {\n const value = params[optionalCatchAll];\n if (value === undefined || (Array.isArray(value) && value.length === 0)) {\n return '';\n }\n const segments = Array.isArray(value) ? value : [value];\n return segments.map(encodeURIComponent).join('/');\n }\n\n if (catchAll) {\n const value = params[catchAll];\n if (value === undefined) {\n throw new Error(\n `<Link> missing required catch-all param \"${catchAll}\" for pattern \"${pattern}\".`\n );\n }\n const segments = Array.isArray(value) ? value : [value];\n if (segments.length === 0) {\n throw new Error(\n `<Link> catch-all param \"${catchAll}\" must have at least one segment for pattern \"${pattern}\".`\n );\n }\n return segments.map(encodeURIComponent).join('/');\n }\n\n // single dynamic segment\n const value = params[single];\n if (value === undefined) {\n throw new Error(`<Link> missing required param \"${single}\" for pattern \"${pattern}\".`);\n }\n if (Array.isArray(value)) {\n throw new Error(\n `<Link> param \"${single}\" expected a string but received an array for pattern \"${pattern}\".`\n );\n }\n // Accept numbers — coerce to string for URL interpolation\n return encodeURIComponent(String(value));\n }\n )\n // Clean up trailing slash from empty optional catch-all\n .replace(/\\/+$/, '') || '/'\n );\n}\n\n// ─── Resolve Href ───────────────────────────────────────────────\n\n/**\n * Resolve the final href string from Link props.\n *\n * Handles:\n * - params interpolation into route patterns\n * - searchParams serialization via SearchParamsDefinition\n * - Validation that searchParams and inline query strings are exclusive\n */\nexport function resolveHref(\n href: string,\n params?: Record<string, string | number | string[]>,\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n }\n): string {\n let resolvedPath = href;\n\n // Interpolate params if provided\n if (params) {\n resolvedPath = interpolateParams(href, params);\n }\n\n // Serialize searchParams if provided\n if (searchParams) {\n // Validate: searchParams prop and inline query string are mutually exclusive\n if (resolvedPath.includes('?')) {\n throw new Error(\n '<Link> received both a searchParams prop and a query string in href. ' +\n 'These are mutually exclusive — use one or the other.'\n );\n }\n\n const qs = searchParams.definition.serialize(searchParams.values);\n if (qs) {\n resolvedPath = `${resolvedPath}?${qs}`;\n }\n }\n\n return resolvedPath;\n}\n\n// ─── Build Props ─────────────────────────────────────────────────\n\ninterface LinkOutputProps {\n 'href': string;\n 'data-timber-link'?: boolean;\n 'data-timber-prefetch'?: boolean;\n 'data-timber-scroll'?: string;\n}\n\n/**\n * Build the HTML attributes for a Link. Separated from the component\n * for testability — the component just spreads these onto an <a>.\n */\nexport function buildLinkProps(\n props: Pick<LinkPropsWithHref, 'href' | 'prefetch' | 'scroll'> & {\n params?: Record<string, string | number | string[]>;\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n }\n): LinkOutputProps {\n const resolvedHref = resolveHref(props.href, props.params, props.searchParams);\n\n validateLinkHref(resolvedHref);\n\n const output: LinkOutputProps = { href: resolvedHref };\n const internal = isInternalHref(resolvedHref);\n\n if (internal) {\n output['data-timber-link'] = true;\n\n if (props.prefetch) {\n output['data-timber-prefetch'] = true;\n }\n\n if (props.scroll === false) {\n output['data-timber-scroll'] = 'false';\n }\n }\n\n return output;\n}\n\n// ─── Link Component ──────────────────────────────────────────────\n\n/**\n * Navigation link with progressive enhancement.\n *\n * Renders as a plain `<a>` tag — works without JavaScript. When the client\n * runtime is active, it intercepts clicks on links marked with\n * `data-timber-link` to perform RSC-based client navigation.\n *\n * Supports typed routes via codegen overloads. At runtime:\n * - `params` prop interpolates dynamic segments in the href pattern\n * - `searchParams` prop serializes query parameters via a SearchParamsDefinition\n */\nexport function Link({\n href,\n prefetch,\n scroll,\n params,\n searchParams,\n onNavigate,\n children,\n ...rest\n}: LinkProps) {\n const linkProps = buildLinkProps({ href, prefetch, scroll, params, searchParams });\n\n const inner = <LinkStatusProvider href={linkProps.href}>{children}</LinkStatusProvider>;\n\n return (\n <a {...rest} {...linkProps}>\n {onNavigate ? (\n <LinkNavigateInterceptor onNavigate={onNavigate}>{inner}</LinkNavigateInterceptor>\n ) : (\n inner\n )}\n </a>\n );\n}\n","// Segment Cache — stores the mounted segment tree and prefetched payloads\n// See design/19-client-navigation.md for architecture details.\n\nimport type { HeadElement } from './head';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/** A prefetched RSC result with optional head elements and segment metadata. */\nexport interface PrefetchResult {\n payload: unknown;\n headElements: HeadElement[] | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo?: SegmentInfo[] | null;\n /** Route params from X-Timber-Params header for populating useParams(). */\n params?: Record<string, string | string[]> | null;\n}\n\n/**\n * A node in the client-side segment tree. Each node represents a mounted\n * layout or page segment with its RSC flight payload.\n */\nexport interface SegmentNode {\n /** The segment's URL pattern (e.g., \"/\", \"/dashboard\", \"/projects/[id]\") */\n segment: string;\n /** The RSC flight payload for this segment (opaque to the cache) */\n payload: unknown;\n /** Whether the segment is async (async layouts always re-render on navigation) */\n isAsync: boolean;\n /** Child segments keyed by segment path */\n children: Map<string, SegmentNode>;\n}\n\n/**\n * Serialized state tree sent via X-Timber-State-Tree header.\n * Only sync segments are included — async segments always re-render.\n */\nexport interface StateTree {\n segments: string[];\n}\n\n// ─── Segment Cache ───────────────────────────────────────────────\n\n/**\n * Maintains the client-side segment tree representing currently mounted\n * layouts and pages. Used for navigation reconciliation — the router diffs\n * new routes against this tree to determine which segments to re-fetch.\n */\nexport class SegmentCache {\n private root: SegmentNode | undefined;\n\n get(segment: string): SegmentNode | undefined {\n if (segment === '/' || segment === this.root?.segment) {\n return this.root;\n }\n return undefined;\n }\n\n set(segment: string, node: SegmentNode): void {\n if (segment === '/' || !this.root) {\n this.root = node;\n }\n }\n\n clear(): void {\n this.root = undefined;\n }\n\n /**\n * Serialize the mounted segment tree for the X-Timber-State-Tree header.\n * Only includes sync segments — async segments are excluded because the\n * server must always re-render them (they may depend on request context).\n *\n * This is a performance optimization only, NOT a security boundary.\n * The server always runs all access.ts files regardless of the state tree.\n */\n serializeStateTree(): StateTree {\n const segments: string[] = [];\n if (this.root) {\n collectSyncSegments(this.root, segments);\n }\n return { segments };\n }\n}\n\n/** Recursively collect sync segment paths from the tree */\nfunction collectSyncSegments(node: SegmentNode, out: string[]): void {\n if (!node.isAsync) {\n out.push(node.segment);\n }\n for (const child of node.children.values()) {\n collectSyncSegments(child, out);\n }\n}\n\n// ─── Segment Tree Builder ────────────────────────────────────────\n\n/**\n * Segment metadata from the server, sent via X-Timber-Segments header.\n * Describes a rendered segment's path and whether it's async.\n */\nexport interface SegmentInfo {\n path: string;\n isAsync: boolean;\n}\n\n/**\n * Build a SegmentNode tree from flat segment metadata.\n *\n * Takes an ordered list of segment descriptors (root → leaf) from the\n * server's X-Timber-Segments header and constructs the hierarchical\n * tree structure that SegmentCache expects.\n *\n * Each segment is nested as a child of the previous one, forming a\n * linear chain from root to leaf. The leaf segment (page) is excluded\n * from the tree — pages are never cached across navigations.\n */\nexport function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefined {\n // Need at least a root segment to build a tree\n if (segments.length === 0) return undefined;\n\n // Exclude the leaf (page) — pages always re-render on navigation.\n // Only layouts are cached in the segment tree.\n const layouts = segments.length > 1 ? segments.slice(0, -1) : segments;\n\n let root: SegmentNode | undefined;\n let parent: SegmentNode | undefined;\n\n for (const info of layouts) {\n const node: SegmentNode = {\n segment: info.path,\n payload: null,\n isAsync: info.isAsync,\n children: new Map(),\n };\n\n if (!root) {\n root = node;\n }\n\n if (parent) {\n parent.children.set(info.path, node);\n }\n\n parent = node;\n }\n\n return root;\n}\n\n// ─── Prefetch Cache ──────────────────────────────────────────────\n\ninterface PrefetchEntry {\n result: PrefetchResult;\n expiresAt: number;\n}\n\n/**\n * Short-lived cache for hover-triggered prefetches. Entries expire after\n * 30 seconds. When a link is clicked, the prefetched payload is consumed\n * (moved to the history stack) and removed from this cache.\n *\n * timber.js does NOT prefetch on viewport intersection — only explicit\n * hover on <Link prefetch> triggers a prefetch.\n */\nexport class PrefetchCache {\n private static readonly TTL_MS = 30_000;\n private entries = new Map<string, PrefetchEntry>();\n\n set(url: string, result: PrefetchResult): void {\n this.entries.set(url, {\n result,\n expiresAt: Date.now() + PrefetchCache.TTL_MS,\n });\n }\n\n get(url: string): PrefetchResult | undefined {\n const entry = this.entries.get(url);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.entries.delete(url);\n return undefined;\n }\n return entry.result;\n }\n\n /** Get and remove the entry (used when navigation consumes a prefetch) */\n consume(url: string): PrefetchResult | undefined {\n const result = this.get(url);\n if (result !== undefined) {\n this.entries.delete(url);\n }\n return result;\n }\n}\n","// History Stack — stores RSC payloads by URL for instant back/forward navigation\n// See design/19-client-navigation.md § History Stack\n\nimport type { HeadElement } from './head';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface HistoryEntry {\n /** The complete segment tree payload at the time of navigation */\n payload: unknown;\n /** Resolved head elements for this page (title, meta tags). Null for SSR'd initial page. */\n headElements?: HeadElement[] | null;\n /** Route params for this page (for useParams). Null for SSR'd initial page. */\n params?: Record<string, string | string[]> | null;\n}\n\n// ─── History Stack ───────────────────────────────────────────────\n\n/**\n * Session-lived history stack keyed by URL. Enables instant back/forward\n * navigation without a server roundtrip.\n *\n * On forward navigation, the new page's payload is pushed onto the stack.\n * On popstate, the cached payload is replayed instantly.\n *\n * Scroll positions are stored in history.state (browser History API),\n * not in this stack — see design/19-client-navigation.md §Scroll Restoration.\n *\n * Entries persist for the session duration (no expiry) and are cleared\n * when the tab is closed — matching browser back-button behavior.\n */\nexport class HistoryStack {\n private entries = new Map<string, HistoryEntry>();\n\n push(url: string, entry: HistoryEntry): void {\n this.entries.set(url, entry);\n }\n\n get(url: string): HistoryEntry | undefined {\n return this.entries.get(url);\n }\n\n has(url: string): boolean {\n return this.entries.has(url);\n }\n}\n","/**\n * useParams() — client-side hook for accessing route params.\n *\n * Returns the dynamic route parameters for the current URL.\n * When called with a route pattern argument, TypeScript narrows\n * the return type to the exact params shape for that route.\n *\n * Two layers of type narrowing work together:\n * 1. The generic overload here uses the Routes interface directly —\n * `useParams<R>()` returns `Routes[R]['params']`.\n * 2. Build-time codegen generates per-route string-literal overloads\n * in the .d.ts file for IDE autocomplete (see routing/codegen.ts).\n *\n * When the Routes interface is empty (no codegen yet), the generic\n * overload has `keyof Routes = never`, so only the fallback matches.\n *\n * During SSR, params are read from the ALS-backed SSR data context\n * (populated by ssr-entry.ts) to ensure correct per-request isolation\n * across concurrent requests with streaming Suspense.\n *\n * Reactivity: On the client, useParams() reads from NavigationContext\n * which is updated atomically with the RSC tree render. This replaces\n * the previous useSyncExternalStore approach that suffered from a\n * timing gap between tree render and store notification — causing\n * preserved layout components to briefly show stale active state.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n *\n * Design doc: design/09-typescript.md §\"Typed Routes\"\n */\n\nimport type { Routes } from '#/index.js';\nimport { getSsrData } from './ssr-data.js';\nimport { currentParams, _setCurrentParams, paramsListeners } from './state.js';\nimport { useNavigationContext } from './navigation-context.js';\n\n// ---------------------------------------------------------------------------\n// Module-level subscribe/notify pattern — kept for backward compat and tests\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to params changes.\n * Retained for backward compatibility with tests that verify the\n * subscribe/notify contract. On the client, useParams() reads from\n * NavigationContext instead.\n */\nexport function subscribe(callback: () => void): () => void {\n paramsListeners.add(callback);\n return () => paramsListeners.delete(callback);\n}\n\n/**\n * Get the current params snapshot (module-level fallback).\n * Used by tests and by the hook when called outside a React component.\n */\nexport function getSnapshot(): Record<string, string | string[]> {\n return currentParams;\n}\n\n// ---------------------------------------------------------------------------\n// Framework API — called by the segment router on each navigation\n// ---------------------------------------------------------------------------\n\n/**\n * Set the current route params in the module-level store.\n *\n * Called by the router on each navigation. This updates the fallback\n * snapshot used by tests and by the hook when called outside a React\n * component (no NavigationContext available).\n *\n * On the client, the primary reactivity path is NavigationContext —\n * the router calls setNavigationState() then renderRoot() which wraps\n * the element in NavigationProvider. setCurrentParams is still called\n * for the module-level fallback.\n *\n * During SSR, params are also available via getSsrData().params\n * (ALS-backed).\n */\nexport function setCurrentParams(params: Record<string, string | string[]>): void {\n _setCurrentParams(params);\n}\n\n/**\n * Notify all legacy subscribers that params have changed.\n *\n * Retained for backward compatibility with tests. On the client,\n * the NavigationContext + renderRoot pattern replaces this — params\n * update atomically with the tree render, so explicit notification\n * is no longer needed.\n */\nexport function notifyParamsListeners(): void {\n for (const listener of paramsListeners) {\n listener();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public hook\n// ---------------------------------------------------------------------------\n\n/**\n * Read the current route's dynamic params.\n *\n * The optional `_route` argument exists only for TypeScript narrowing —\n * it does not affect the runtime return value.\n *\n * On the client, reads from NavigationContext (provided by\n * NavigationProvider in renderRoot). This ensures params update\n * atomically with the RSC tree — no timing gap.\n *\n * During SSR, reads from the ALS-backed SSR data context to ensure\n * per-request isolation across concurrent requests with streaming Suspense.\n *\n * When called outside a React component (e.g., in test assertions),\n * falls back to the module-level snapshot.\n *\n * @overload Typed — when a known route path is passed, returns the\n * exact params shape from the generated Routes interface.\n * @overload Fallback — returns the generic params record.\n */\nexport function useParams<R extends keyof Routes>(route: R): Routes[R]['params'];\nexport function useParams(route?: string): Record<string, string | string[]>;\nexport function useParams(_route?: string): Record<string, string | string[]> {\n // Try reading from NavigationContext (client-side, inside React tree).\n // During SSR, no NavigationProvider is mounted, so this returns null.\n // When called outside a React component, useContext throws — caught below.\n try {\n const navContext = useNavigationContext();\n if (navContext !== null) {\n return navContext.params;\n }\n } catch {\n // No React dispatcher available (called outside a component).\n // Fall through to module-level snapshot below.\n }\n\n // SSR path: read from ALS-backed SSR data context.\n // Falls back to module-level currentParams for tests.\n return getSsrData()?.params ?? currentParams;\n}\n","// Segment Router — manages client-side navigation and RSC payload fetching\n// See design/19-client-navigation.md for the full architecture.\n\nimport { SegmentCache, PrefetchCache, buildSegmentTree } from './segment-cache';\nimport type { SegmentInfo } from './segment-cache';\nimport { HistoryStack } from './history';\nimport type { HeadElement } from './head';\nimport { setCurrentParams } from './use-params.js';\nimport { setNavigationState } from './navigation-context.js';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface NavigationOptions {\n /** Set to false to prevent scroll-to-top on forward navigation */\n scroll?: boolean;\n /** Use replaceState instead of pushState (replaces current history entry) */\n replace?: boolean;\n}\n\n/**\n * Function that decodes an RSC Flight stream into a React element tree.\n * In production: createFromFetch from @vitejs/plugin-rsc/browser.\n * In tests: a mock that returns the raw payload.\n */\nexport type RscDecoder = (fetchPromise: Promise<Response>) => unknown;\n\n/**\n * Function that renders a decoded RSC element tree into the DOM.\n * In production: reactRoot.render(element).\n * In tests: a no-op or mock.\n */\nexport type RootRenderer = (element: unknown) => void;\n\n/**\n * Platform dependencies injected for testability. In production these\n * map to browser APIs; in tests they're replaced with mocks.\n */\nexport interface RouterDeps {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n pushState: (data: unknown, unused: string, url: string) => void;\n replaceState: (data: unknown, unused: string, url: string) => void;\n scrollTo: (x: number, y: number) => void;\n getCurrentUrl: () => string;\n getScrollY: () => number;\n /** Decode RSC Flight stream into React elements. If not provided, raw response text is stored. */\n decodeRsc?: RscDecoder;\n /** Render decoded RSC tree into the DOM. If not provided, rendering is a no-op. */\n renderRoot?: RootRenderer;\n /**\n * Schedule a callback after the next paint. In the browser, this is\n * requestAnimationFrame + setTimeout(0) to run after React commits.\n * In tests, this runs the callback synchronously.\n */\n afterPaint?: (callback: () => void) => void;\n /** Apply resolved head elements (title, meta tags) to the DOM after navigation. */\n applyHead?: (elements: HeadElement[]) => void;\n /**\n * Run a navigation inside a React transition with optimistic pending URL.\n * The pending URL shows immediately (useOptimistic urgent update) and\n * reverts when the transition commits (atomic with the new tree).\n *\n * The `perform` callback receives a `wrapPayload` function to wrap the\n * decoded RSC payload with NavigationProvider + NuqsAdapter before\n * TransitionRoot sets it as the new element.\n *\n * If not provided (tests), the router falls back to renderRoot.\n */\n navigateTransition?: (\n pendingUrl: string,\n perform: (wrapPayload: (payload: unknown) => unknown) => Promise<unknown>,\n ) => Promise<void>;\n}\n\n/** Result of fetching an RSC payload — includes head elements and segment metadata. */\ninterface FetchResult {\n payload: unknown;\n headElements: HeadElement[] | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo: SegmentInfo[] | null;\n /** Route params from X-Timber-Params header for populating useParams(). */\n params: Record<string, string | string[]> | null;\n}\n\nexport interface RouterInstance {\n /** Navigate to a new URL (forward navigation) */\n navigate(url: string, options?: NavigationOptions): Promise<void>;\n /** Full re-render of the current URL — no state tree sent */\n refresh(): Promise<void>;\n /** Handle a popstate event (back/forward button). scrollY is read from history.state. */\n handlePopState(url: string, scrollY?: number): Promise<void>;\n /** Whether a navigation is currently in flight */\n isPending(): boolean;\n /** The URL currently being navigated to, or null if idle */\n getPendingUrl(): string | null;\n /** Subscribe to pending state changes */\n onPendingChange(listener: (pending: boolean) => void): () => void;\n /** Prefetch an RSC payload for a URL (used by Link hover) */\n prefetch(url: string): void;\n /**\n * Apply a piggybacked revalidation payload from a server action response.\n * Renders the element tree and updates head elements without a server fetch.\n * See design/08-forms-and-actions.md §\"Single-Roundtrip Revalidation\".\n */\n applyRevalidation(element: unknown, headElements: HeadElement[] | null): void;\n /**\n * Populate the segment cache from server-provided segment metadata.\n * Called on initial hydration with segment info embedded in the HTML.\n */\n initSegmentCache(segments: SegmentInfo[]): void;\n /** The segment cache (exposed for tests and <Link> prefetch) */\n segmentCache: SegmentCache;\n /** The prefetch cache (exposed for tests and <Link> prefetch) */\n prefetchCache: PrefetchCache;\n /** The history stack (exposed for tests) */\n historyStack: HistoryStack;\n}\n\n/**\n * Thrown when an RSC payload response contains X-Timber-Redirect header.\n * Caught in navigate() to trigger a soft router navigation to the redirect target.\n */\nclass RedirectError extends Error {\n readonly redirectUrl: string;\n constructor(url: string) {\n super(`Server redirect to ${url}`);\n this.redirectUrl = url;\n }\n}\n\n/**\n * Check if an error is an abort error (connection closed / fetch aborted).\n * Browsers throw DOMException with name 'AbortError' when a fetch is aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n if (error instanceof Error && error.name === 'AbortError') return true;\n return false;\n}\n\n// ─── RSC Fetch ───────────────────────────────────────────────────\n\nconst RSC_CONTENT_TYPE = 'text/x-component';\n\n/**\n * Generate a short random cache-busting ID (5 chars, a-z0-9).\n * Matches the format Next.js uses for _rsc params.\n */\nfunction generateCacheBustId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let id = '';\n for (let i = 0; i < 5; i++) {\n id += chars[(Math.random() * 36) | 0];\n }\n return id;\n}\n\n/**\n * Append a `_rsc=<id>` query parameter to the URL.\n * Follows Next.js's pattern — prevents CDN/browser from serving cached HTML\n * for RSC navigation requests and signals that this is an RSC fetch.\n */\nfunction appendRscParam(url: string): string {\n const separator = url.includes('?') ? '&' : '?';\n return `${url}${separator}_rsc=${generateCacheBustId()}`;\n}\n\nfunction buildRscHeaders(\n stateTree: { segments: string[] } | undefined,\n currentUrl?: string\n): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: RSC_CONTENT_TYPE,\n };\n if (stateTree) {\n headers['X-Timber-State-Tree'] = JSON.stringify(stateTree);\n }\n // Send current URL for intercepting route resolution.\n // The server uses this to determine if an intercepting route should\n // render instead of the actual target route (modal pattern).\n // See design/07-routing.md §\"Intercepting Routes\"\n if (currentUrl) {\n headers['X-Timber-URL'] = currentUrl;\n }\n return headers;\n}\n\n/**\n * Extract head elements from the X-Timber-Head response header.\n * Returns null if the header is missing or malformed.\n */\nfunction extractHeadElements(response: Response): HeadElement[] | null {\n const header = response.headers.get('X-Timber-Head');\n if (!header) return null;\n try {\n return JSON.parse(decodeURIComponent(header));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract segment metadata from the X-Timber-Segments response header.\n * Returns null if the header is missing or malformed.\n *\n * Format: JSON array of {path, isAsync} objects describing the rendered\n * segment chain from root to leaf. Used to populate the client-side\n * segment cache for state tree diffing on subsequent navigations.\n */\nfunction extractSegmentInfo(response: Response): SegmentInfo[] | null {\n const header = response.headers.get('X-Timber-Segments');\n if (!header) return null;\n try {\n return JSON.parse(header);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract route params from the X-Timber-Params response header.\n * Returns null if the header is missing or malformed.\n *\n * Used to populate useParams() after client-side navigation.\n */\nfunction extractParams(response: Response): Record<string, string | string[]> | null {\n const header = response.headers.get('X-Timber-Params');\n if (!header) return null;\n try {\n return JSON.parse(header);\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch an RSC payload from the server. If a decodeRsc function is provided,\n * the response is decoded into a React element tree via createFromFetch.\n * Otherwise, the raw response text is returned (test mode).\n *\n * Also extracts head elements from the X-Timber-Head response header\n * so the client can update document.title and <meta> tags after navigation.\n */\nasync function fetchRscPayload(\n url: string,\n deps: RouterDeps,\n stateTree?: { segments: string[] },\n currentUrl?: string\n): Promise<FetchResult> {\n const rscUrl = appendRscParam(url);\n const headers = buildRscHeaders(stateTree, currentUrl);\n if (deps.decodeRsc) {\n // Production path: use createFromFetch for streaming RSC decoding.\n // createFromFetch takes a Promise<Response> and progressively parses\n // the RSC Flight stream as chunks arrive.\n //\n // Intercept the response to read X-Timber-Head before createFromFetch\n // consumes the body. Reading headers does NOT consume the body stream.\n const fetchPromise = deps.fetch(rscUrl, { headers, redirect: 'manual' });\n let headElements: HeadElement[] | null = null;\n let segmentInfo: SegmentInfo[] | null = null;\n let params: Record<string, string | string[]> | null = null;\n const wrappedPromise = fetchPromise.then((response) => {\n // Detect server-side redirects. The server returns 204 + X-Timber-Redirect\n // for RSC payload requests instead of a raw 302, because fetch with\n // redirect: \"manual\" turns 302s into opaque redirects (status 0, null body)\n // which crashes createFromFetch when it tries to read the body stream.\n const redirectLocation =\n response.headers.get('X-Timber-Redirect') ||\n (response.status >= 300 && response.status < 400 ? response.headers.get('Location') : null);\n if (redirectLocation) {\n throw new RedirectError(redirectLocation);\n }\n headElements = extractHeadElements(response);\n segmentInfo = extractSegmentInfo(response);\n params = extractParams(response);\n return response;\n });\n // Await so headElements/segmentInfo/params are populated before we return.\n // Also await the decoded payload — createFromFetch returns a thenable\n // that resolves to the React element tree.\n await wrappedPromise;\n const payload = await deps.decodeRsc(wrappedPromise);\n return { payload, headElements, segmentInfo, params };\n }\n // Test/fallback path: return raw text\n const response = await deps.fetch(rscUrl, { headers, redirect: 'manual' });\n // Check for redirect in test path too\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('Location');\n if (location) {\n throw new RedirectError(location);\n }\n }\n return {\n payload: await response.text(),\n headElements: extractHeadElements(response),\n segmentInfo: extractSegmentInfo(response),\n params: extractParams(response),\n };\n}\n\n// ─── Router Factory ──────────────────────────────────────────────\n\n/**\n * Create a router instance. In production, called once at app hydration\n * with real browser APIs. In tests, called with mock dependencies.\n */\nexport function createRouter(deps: RouterDeps): RouterInstance {\n const segmentCache = new SegmentCache();\n const prefetchCache = new PrefetchCache();\n const historyStack = new HistoryStack();\n\n let pending = false;\n let pendingUrl: string | null = null;\n const pendingListeners = new Set<(pending: boolean) => void>();\n\n function setPending(value: boolean, url?: string): void {\n const newPendingUrl = value && url ? url : null;\n if (pending === value && pendingUrl === newPendingUrl) return;\n pending = value;\n pendingUrl = newPendingUrl;\n // Notify external store listeners (non-React consumers).\n // React-facing pending state is handled by useOptimistic in\n // TransitionRoot via navigateTransition — not this function.\n for (const listener of pendingListeners) {\n listener(value);\n }\n }\n\n /** Update the segment cache from server-provided segment metadata. */\n function updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void {\n if (!segmentInfo || segmentInfo.length === 0) return;\n const tree = buildSegmentTree(segmentInfo);\n if (tree) {\n segmentCache.set('/', tree);\n }\n }\n\n /** Render a decoded RSC payload into the DOM if a renderer is available. */\n function renderPayload(payload: unknown): void {\n if (deps.renderRoot) {\n deps.renderRoot(payload);\n }\n }\n\n /**\n * Update navigation state (params + pathname) for the next render.\n *\n * Sets both the module-level fallback (for tests and SSR) and the\n * navigation context state (read by renderRoot to wrap the element\n * in NavigationProvider). The context update is atomic with the tree\n * render — both are passed to reactRoot.render() in the same call.\n */\n function updateNavigationState(\n params: Record<string, string | string[]> | null | undefined,\n url: string\n ): void {\n const resolvedParams = params ?? {};\n // Module-level fallback for tests (no NavigationProvider) and SSR\n setCurrentParams(resolvedParams);\n // Navigation context — read by renderRoot to wrap the RSC element\n const pathname = url.startsWith('http')\n ? new URL(url).pathname\n : url.split('?')[0] || '/';\n setNavigationState({ params: resolvedParams, pathname });\n }\n\n /**\n * Render a payload via navigateTransition (production) or renderRoot (tests).\n * The perform callback should fetch data, update state, and return the payload.\n * In production, the entire callback runs inside a React transition with\n * useOptimistic for the pending URL. In tests, the payload is rendered directly.\n */\n async function renderViaTransition(\n pendingUrl: string,\n perform: () => Promise<FetchResult>,\n ): Promise<HeadElement[] | null> {\n if (deps.navigateTransition) {\n let headElements: HeadElement[] | null = null;\n await deps.navigateTransition(pendingUrl, async (wrapPayload) => {\n const result = await perform();\n headElements = result.headElements;\n return wrapPayload(result.payload);\n });\n return headElements;\n }\n // Fallback: no transition (tests, no React tree)\n const result = await perform();\n renderPayload(result.payload);\n return result.headElements;\n }\n\n /** Apply head elements (title, meta tags) to the DOM if available. */\n function applyHead(elements: HeadElement[] | null | undefined): void {\n if (elements && deps.applyHead) {\n deps.applyHead(elements);\n }\n }\n\n /** Run a callback after the next paint (after React commit). */\n function afterPaint(callback: () => void): void {\n if (deps.afterPaint) {\n deps.afterPaint(callback);\n } else {\n callback();\n }\n }\n\n /**\n * Core navigation logic shared between the transition and fallback paths.\n * Fetches the RSC payload, updates all state, and returns the result.\n */\n async function performNavigationFetch(\n url: string,\n options: { replace: boolean },\n ): Promise<FetchResult> {\n // Check prefetch cache first. PrefetchResult has optional segmentInfo/params\n // fields — normalize to null for FetchResult compatibility.\n const prefetched = prefetchCache.consume(url);\n let result: FetchResult | undefined = prefetched\n ? {\n payload: prefetched.payload,\n headElements: prefetched.headElements,\n segmentInfo: prefetched.segmentInfo ?? null,\n params: prefetched.params ?? null,\n }\n : undefined;\n\n if (result === undefined) {\n // Fetch RSC payload with state tree for partial rendering.\n // Send current URL for intercepting route resolution (modal pattern).\n const stateTree = segmentCache.serializeStateTree();\n const rawCurrentUrl = deps.getCurrentUrl();\n const currentUrl = rawCurrentUrl.startsWith('http')\n ? new URL(rawCurrentUrl).pathname\n : new URL(rawCurrentUrl, 'http://localhost').pathname;\n result = await fetchRscPayload(url, deps, stateTree, currentUrl);\n }\n\n // Update the browser history — replace mode overwrites the current entry\n if (options.replace) {\n deps.replaceState({ timber: true, scrollY: 0 }, '', url);\n } else {\n deps.pushState({ timber: true, scrollY: 0 }, '', url);\n }\n\n // Store the payload in the history stack\n historyStack.push(url, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n\n // Update the segment cache with the new route's segment tree.\n updateSegmentCache(result.segmentInfo);\n\n // Update navigation state (params + pathname) before rendering.\n updateNavigationState(result.params, url);\n\n return result;\n }\n\n async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {\n const scroll = options.scroll !== false;\n const replace = options.replace === true;\n\n // Capture the departing page's scroll position for scroll={false} preservation.\n const currentScrollY = deps.getScrollY();\n\n // Save the departing page's scroll position in history.state before\n // pushing a new entry. This ensures back/forward navigation can restore\n // the correct scroll position from the browser's per-entry state.\n deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());\n\n setPending(true, url);\n\n try {\n const headElements = await renderViaTransition(url, () =>\n performNavigationFetch(url, { replace }),\n );\n\n // Update document.title and <meta> tags with the new page's metadata\n applyHead(headElements);\n\n // Notify nuqs adapter (and any other listeners) that navigation completed.\n window.dispatchEvent(new Event('timber:navigation-end'));\n\n // Scroll-to-top on forward navigation, or restore captured position\n // for scroll={false}. React's render() on the document root can reset\n // scroll during DOM reconciliation, so all scroll must be actively managed.\n afterPaint(() => {\n if (scroll) {\n deps.scrollTo(0, 0);\n } else {\n deps.scrollTo(0, currentScrollY);\n }\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } catch (error) {\n // Server-side redirect during RSC fetch → soft router navigation.\n if (error instanceof RedirectError) {\n setPending(false);\n await navigate(error.redirectUrl, { replace: true });\n return;\n }\n // Abort errors are not application errors — swallow silently.\n if (isAbortError(error)) return;\n throw error;\n } finally {\n setPending(false);\n }\n }\n\n async function refresh(): Promise<void> {\n const currentUrl = deps.getCurrentUrl();\n\n setPending(true, currentUrl);\n\n try {\n const headElements = await renderViaTransition(currentUrl, async () => {\n // No state tree sent — server renders the complete RSC payload\n const result = await fetchRscPayload(currentUrl, deps);\n historyStack.push(currentUrl, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n updateSegmentCache(result.segmentInfo);\n updateNavigationState(result.params, currentUrl);\n return result;\n });\n\n applyHead(headElements);\n } finally {\n setPending(false);\n }\n }\n\n async function handlePopState(url: string, scrollY: number = 0): Promise<void> {\n // Scroll position is read from history.state by the caller (browser-entry.ts)\n // and passed in. This is more reliable than tracking scroll per-URL in memory\n // because the browser maintains per-entry state even with duplicate URLs.\n const entry = historyStack.get(url);\n\n if (entry && entry.payload !== null) {\n // Replay cached payload — no server roundtrip\n updateNavigationState(entry.params, url);\n renderPayload(entry.payload);\n applyHead(entry.headElements);\n afterPaint(() => {\n deps.scrollTo(0, scrollY);\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } else {\n // No cached payload — fetch from server.\n // This happens when navigating back to the initial SSR'd page\n // (its payload is null since it was rendered via SSR, not RSC fetch)\n // or when the entry doesn't exist at all.\n setPending(true, url);\n try {\n const headElements = await renderViaTransition(url, async () => {\n const stateTree = segmentCache.serializeStateTree();\n const result = await fetchRscPayload(url, deps, stateTree);\n updateSegmentCache(result.segmentInfo);\n updateNavigationState(result.params, url);\n historyStack.push(url, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n return result;\n });\n\n applyHead(headElements);\n afterPaint(() => {\n deps.scrollTo(0, scrollY);\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } finally {\n setPending(false);\n }\n }\n }\n\n /**\n * Prefetch an RSC payload for a URL and store it in the prefetch cache.\n * Called on hover of <Link prefetch> elements.\n */\n function prefetch(url: string): void {\n // Don't prefetch if already cached\n if (prefetchCache.get(url) !== undefined) return;\n if (historyStack.has(url)) return;\n\n // Fire-and-forget fetch\n const stateTree = segmentCache.serializeStateTree();\n void fetchRscPayload(url, deps, stateTree).then(\n (result) => {\n prefetchCache.set(url, result);\n },\n () => {\n // Prefetch failure is non-fatal — navigation will fetch fresh\n }\n );\n }\n\n return {\n navigate,\n refresh,\n handlePopState,\n isPending: () => pending,\n getPendingUrl: () => pendingUrl,\n onPendingChange(listener) {\n pendingListeners.add(listener);\n return () => pendingListeners.delete(listener);\n },\n prefetch,\n applyRevalidation(element: unknown, headElements: HeadElement[] | null): void {\n // Render the piggybacked element tree from a server action response.\n // Updates the current history entry with the fresh payload and applies\n // head elements — same as refresh() but without a server fetch.\n const currentUrl = deps.getCurrentUrl();\n historyStack.push(currentUrl, {\n payload: element,\n headElements,\n });\n renderPayload(element);\n applyHead(headElements);\n },\n initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),\n segmentCache,\n prefetchCache,\n historyStack,\n };\n}\n","// useNavigationPending — returns true while an RSC navigation is in flight.\n// See design/19-client-navigation.md §\"useNavigationPending()\"\n//\n// Reads from PendingNavigationContext (provided by TransitionRoot) so the\n// pending state shows immediately (urgent update) and clears atomically\n// with the new tree (same startTransition commit).\n\nimport { usePendingNavigationUrl } from './navigation-context.js';\n\n/**\n * Returns true while an RSC navigation is in flight.\n *\n * The pending state is true from the moment the RSC fetch starts until\n * React reconciliation completes. This includes the fetch itself,\n * RSC stream parsing, and React tree reconciliation.\n *\n * It does NOT include Suspense streaming after the shell — only the\n * initial shell reconciliation.\n *\n * ```tsx\n * 'use client'\n * import { useNavigationPending } from '@timber-js/app/client'\n *\n * export function NavBar() {\n * const isPending = useNavigationPending()\n * return (\n * <nav className={isPending ? 'opacity-50' : ''}>\n * <Link href=\"/dashboard\">Dashboard</Link>\n * </nav>\n * )\n * }\n * ```\n */\nexport function useNavigationPending(): boolean {\n const pendingUrl = usePendingNavigationUrl();\n // During SSR or outside PendingNavigationProvider, no navigation is pending\n return pendingUrl !== null;\n}\n","// Global router reference — shared between browser-entry and client hooks.\n//\n// Delegates to client/state.ts for the actual module-level variable.\n// This ensures singleton semantics regardless of import path — all\n// callers converge on the same state.ts instance via the barrel.\n//\n// See design/18-build-system.md §\"Module Singleton Strategy\"\n\nimport type { RouterInstance } from './router.js';\nimport { globalRouter, _setGlobalRouter } from './state.js';\n\n/**\n * Set the global router instance. Called once during bootstrap.\n */\nexport function setGlobalRouter(router: RouterInstance): void {\n _setGlobalRouter(router);\n}\n\n/**\n * Get the global router instance. Throws if called before bootstrap.\n * Used by client-side hooks (useNavigationPending, etc.)\n */\nexport function getRouter(): RouterInstance {\n if (!globalRouter) {\n throw new Error('[timber] Router not initialized. getRouter() was called before bootstrap().');\n }\n return globalRouter;\n}\n\n/**\n * Get the global router instance or null if not yet initialized.\n * Used by useRouter() methods to avoid silent failures — callers\n * can log a meaningful warning instead of silently no-oping.\n */\nexport function getRouterOrNull(): RouterInstance | null {\n return globalRouter;\n}\n\n/**\n * Reset the global router to null. Used only in tests to isolate\n * module-level state between test cases.\n * @internal\n */\nexport function resetGlobalRouter(): void {\n _setGlobalRouter(null);\n}\n","/**\n * useRouter() — client-side hook for programmatic navigation.\n *\n * Returns a router instance with push, replace, refresh, back, forward,\n * and prefetch methods. Compatible with Next.js's `useRouter()` from\n * `next/navigation` (App Router).\n *\n * This wraps timber's internal RouterInstance in the Next.js-compatible\n * AppRouterInstance shape that ecosystem libraries expect.\n *\n * NOTE: Unlike Next.js, these methods do NOT wrap navigation in\n * startTransition. In Next.js, router state is React state (useReducer)\n * so startTransition defers the update and provides isPending tracking.\n * In timber, navigation calls reactRoot.render() which is a root-level\n * render — startTransition has no effect on root renders.\n *\n * Navigation state (params, pathname) is delivered atomically via\n * NavigationContext embedded in the element tree passed to\n * reactRoot.render(). See design/19-client-navigation.md §\"NavigationContext\".\n *\n * For loading UI during navigation, use:\n * - useLinkStatus() — per-link pending indicator (inside <Link>)\n * - useNavigationPending() — global navigation pending state\n */\n\nimport { getRouterOrNull } from './router-ref.js';\n\nexport interface AppRouterInstance {\n /** Navigate to a URL, pushing a new history entry */\n push(href: string, options?: { scroll?: boolean }): void;\n /** Navigate to a URL, replacing the current history entry */\n replace(href: string, options?: { scroll?: boolean }): void;\n /** Refresh the current page (re-fetch RSC payload) */\n refresh(): void;\n /** Navigate back in history */\n back(): void;\n /** Navigate forward in history */\n forward(): void;\n /** Prefetch an RSC payload for a URL */\n prefetch(href: string): void;\n}\n\n/**\n * Get a router instance for programmatic navigation.\n *\n * Compatible with Next.js's `useRouter()` from `next/navigation`.\n *\n * Methods lazily resolve the global router when invoked (during user\n * interaction) rather than capturing it at render time. This is critical\n * because during hydration, React synchronously executes component render\n * functions *before* the router is bootstrapped in browser-entry.ts.\n * If we eagerly captured the router during render, components would get\n * a null reference and be stuck with silent no-ops forever.\n *\n * Returns safe no-ops during SSR or before bootstrap. The `typeof window`\n * check is insufficient because Vite's client SSR environment defines\n * `window`, so we use a try/catch on getRouter() — but only at method\n * invocation time, not at render time.\n */\nexport function useRouter(): AppRouterInstance {\n return {\n push(href: string, options?: { scroll?: boolean }) {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error('[timber] useRouter().push() called but router is not initialized. This is a bug — please report it.');\n }\n return;\n }\n void router.navigate(href, { scroll: options?.scroll });\n },\n replace(href: string, options?: { scroll?: boolean }) {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error('[timber] useRouter().replace() called but router is not initialized.');\n }\n return;\n }\n void router.navigate(href, { scroll: options?.scroll, replace: true });\n },\n refresh() {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error('[timber] useRouter().refresh() called but router is not initialized.');\n }\n return;\n }\n void router.refresh();\n },\n back() {\n if (typeof window !== 'undefined') window.history.back();\n },\n forward() {\n if (typeof window !== 'undefined') window.history.forward();\n },\n prefetch(href: string) {\n const router = getRouterOrNull();\n if (!router) return; // Silent — prefetch failure is non-fatal\n router.prefetch(href);\n },\n };\n}\n","/**\n * usePathname() — client-side hook for reading the current pathname.\n *\n * Returns the pathname portion of the current URL (e.g. '/dashboard/settings').\n * Updates when client-side navigation changes the URL.\n *\n * On the client, reads from NavigationContext which is updated atomically\n * with the RSC tree render. This replaces the previous useSyncExternalStore\n * approach which only subscribed to popstate events — meaning usePathname()\n * did NOT re-render on forward navigation (pushState). The context approach\n * fixes this: pathname updates in the same render pass as the new tree.\n *\n * During SSR, reads the request pathname from the SSR ALS context\n * (populated by ssr-entry.ts) instead of window.location.\n *\n * Compatible with Next.js's `usePathname()` from `next/navigation`.\n */\n\nimport { getSsrData } from './ssr-data.js';\nimport { useNavigationContext } from './navigation-context.js';\n\n/**\n * Read the current URL pathname.\n *\n * On the client, reads from NavigationContext (provided by\n * NavigationProvider in renderRoot). During SSR, reads from the\n * ALS-backed SSR data context. Falls back to window.location.pathname\n * when called outside a React component (e.g., in tests).\n */\nexport function usePathname(): string {\n // Try reading from NavigationContext (client-side, inside React tree).\n // During SSR, no NavigationProvider is mounted, so this returns null.\n try {\n const navContext = useNavigationContext();\n if (navContext !== null) {\n return navContext.pathname;\n }\n } catch {\n // No React dispatcher available (called outside a component).\n // Fall through to SSR/fallback below.\n }\n\n // SSR path: read from ALS-backed SSR data context.\n const ssrData = getSsrData();\n if (ssrData) return ssrData.pathname ?? '/';\n\n // Final fallback: window.location (tests, edge cases).\n if (typeof window !== 'undefined') return window.location.pathname;\n return '/';\n}\n","/**\n * useSearchParams() — client-side hook for reading URL search params.\n *\n * Returns a read-only URLSearchParams instance reflecting the current\n * URL's query string. Updates when client-side navigation changes the URL.\n *\n * This is a thin wrapper over window.location.search, provided for\n * Next.js API compatibility (libraries like nuqs import useSearchParams\n * from next/navigation).\n *\n * Unlike Next.js's ReadonlyURLSearchParams, this returns a standard\n * URLSearchParams. Mutation methods (set, delete, append) work on the\n * local copy but do NOT affect the URL — use the router or nuqs for that.\n *\n * During SSR, reads the request search params from the SSR ALS context\n * (populated by ssr-entry.ts) instead of window.location.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n */\n\nimport { useSyncExternalStore } from 'react';\nimport { getSsrData } from './ssr-data.js';\nimport { cachedSearch, cachedSearchParams, _setCachedSearch } from './state.js';\n\nfunction getSearch(): string {\n if (typeof window !== 'undefined') return window.location.search;\n const data = getSsrData();\n if (!data) return '';\n const sp = new URLSearchParams(data.searchParams);\n const str = sp.toString();\n return str ? `?${str}` : '';\n}\n\nfunction getServerSearch(): string {\n const data = getSsrData();\n if (!data) return '';\n const sp = new URLSearchParams(data.searchParams);\n const str = sp.toString();\n return str ? `?${str}` : '';\n}\n\nfunction subscribe(callback: () => void): () => void {\n window.addEventListener('popstate', callback);\n return () => window.removeEventListener('popstate', callback);\n}\n\n// Cache the last search string and its parsed URLSearchParams to avoid\n// creating a new object on every render when the URL hasn't changed.\n// State lives in client/state.ts for singleton guarantees.\n\nfunction getSearchParams(): URLSearchParams {\n const search = getSearch();\n if (search !== cachedSearch) {\n const params = new URLSearchParams(search);\n _setCachedSearch(search, params);\n return params;\n }\n return cachedSearchParams;\n}\n\nfunction getServerSearchParams(): URLSearchParams {\n const data = getSsrData();\n return data ? new URLSearchParams(data.searchParams) : new URLSearchParams();\n}\n\n/**\n * Read the current URL search params.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\nexport function useSearchParams(): URLSearchParams {\n // useSyncExternalStore needs a primitive snapshot for comparison.\n // We use the raw search string as the snapshot, then return the\n // parsed URLSearchParams.\n useSyncExternalStore(subscribe, getSearch, getServerSearch);\n return typeof window !== 'undefined' ? getSearchParams() : getServerSearchParams();\n}\n","/**\n * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.\n *\n * Each layout in the segment tree is wrapped with a SegmentProvider that stores\n * the URL segments from root to the current layout level. The hooks read this\n * context to determine which child segments are active below the calling layout.\n *\n * The context value is intentionally minimal: just the segment path array and\n * parallel route keys. No internal cache details are exposed.\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { createContext, useContext, createElement, useMemo } from 'react';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface SegmentContextValue {\n /** URL segments from root to this layout (e.g. ['', 'dashboard', 'settings']) */\n segments: string[];\n /** Parallel route slot keys available at this layout level (e.g. ['sidebar', 'modal']) */\n parallelRouteKeys: string[];\n}\n\n// ─── Context ─────────────────────────────────────────────────────\n\nconst SegmentContext = createContext<SegmentContextValue | null>(null);\n\n/** Read the segment context. Returns null if no provider is above this component. */\nexport function useSegmentContext(): SegmentContextValue | null {\n return useContext(SegmentContext);\n}\n\n// ─── Provider ────────────────────────────────────────────────────\n\ninterface SegmentProviderProps {\n segments: string[];\n parallelRouteKeys: string[];\n children: React.ReactNode;\n}\n\n/**\n * Wraps each layout to provide segment position context.\n * Injected by rsc-entry.ts during element tree construction.\n */\nexport function SegmentProvider({ segments, parallelRouteKeys, children }: SegmentProviderProps) {\n const value = useMemo(\n () => ({ segments, parallelRouteKeys }),\n // segments and parallelRouteKeys are static per layout — they don't change\n // across navigations. The layout's position in the tree is fixed.\n // Intentionally using derived keys — segments/parallelRouteKeys are static per layout\n [segments.join('/'), parallelRouteKeys.join(',')]\n );\n return createElement(SegmentContext.Provider, { value }, children);\n}\n","/**\n * useSelectedLayoutSegment / useSelectedLayoutSegments — client-side hooks\n * for reading the active segment(s) below the current layout.\n *\n * These hooks are used by navigation UIs to highlight active sections.\n * They match Next.js's API from next/navigation.\n *\n * How they work:\n * 1. Each layout is wrapped with a SegmentProvider that records its depth\n * (the URL segments from root to that layout level).\n * 2. The hooks read the current URL pathname via usePathname().\n * 3. They compare the layout's segment depth against the full URL segments\n * to determine which child segments are \"selected\" below.\n *\n * Example: For URL \"/dashboard/settings/profile\"\n * - Root layout (depth 0, segments: ['']): selected segment = \"dashboard\"\n * - Dashboard layout (depth 1, segments: ['', 'dashboard']): selected = \"settings\"\n * - Settings layout (depth 2, segments: ['', 'dashboard', 'settings']): selected = \"profile\"\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { useSegmentContext } from './segment-context.js';\nimport { usePathname } from './use-pathname.js';\n\n/**\n * Split a pathname into URL segments.\n * \"/\" → [\"\"]\n * \"/dashboard\" → [\"\", \"dashboard\"]\n * \"/dashboard/settings\" → [\"\", \"dashboard\", \"settings\"]\n */\nexport function pathnameToSegments(pathname: string): string[] {\n return pathname.split('/');\n}\n\n/**\n * Pure function: compute the selected child segment given a layout's segment\n * depth and the current URL pathname.\n *\n * @param contextSegments — segments from root to the calling layout, or null if no context\n * @param pathname — current URL pathname\n * @returns the active child segment one level below, or null if at the leaf\n */\nexport function getSelectedSegment(\n contextSegments: string[] | null,\n pathname: string\n): string | null {\n const urlSegments = pathnameToSegments(pathname);\n\n if (!contextSegments) {\n return urlSegments[1] || null;\n }\n\n const depth = contextSegments.length;\n return urlSegments[depth] || null;\n}\n\n/**\n * Pure function: compute all selected segments below a layout's depth.\n *\n * @param contextSegments — segments from root to the calling layout, or null if no context\n * @param pathname — current URL pathname\n * @returns all active segments below the layout\n */\nexport function getSelectedSegments(contextSegments: string[] | null, pathname: string): string[] {\n const urlSegments = pathnameToSegments(pathname);\n\n if (!contextSegments) {\n return urlSegments.slice(1).filter(Boolean);\n }\n\n const depth = contextSegments.length;\n return urlSegments.slice(depth).filter(Boolean);\n}\n\n/**\n * Returns the active child segment one level below the layout where this\n * hook is called. Returns `null` if the layout is the leaf (no child segment).\n *\n * Compatible with Next.js's `useSelectedLayoutSegment()` from `next/navigation`.\n *\n * @param parallelRouteKey — Optional parallel route key. Currently unused\n * (parallel route segment tracking is not yet implemented). Accepted for\n * API compatibility with Next.js.\n */\nexport function useSelectedLayoutSegment(parallelRouteKey?: string): string | null {\n void parallelRouteKey;\n const context = useSegmentContext();\n const pathname = usePathname();\n return getSelectedSegment(context?.segments ?? null, pathname);\n}\n\n/**\n * Returns all active segments below the layout where this hook is called.\n * Returns an empty array if the layout is the leaf (no child segments).\n *\n * Compatible with Next.js's `useSelectedLayoutSegments()` from `next/navigation`.\n *\n * @param parallelRouteKey — Optional parallel route key. Currently unused\n * (parallel route segment tracking is not yet implemented). Accepted for\n * API compatibility with Next.js.\n */\nexport function useSelectedLayoutSegments(parallelRouteKey?: string): string[] {\n void parallelRouteKey;\n const context = useSegmentContext();\n const pathname = usePathname();\n return getSelectedSegments(context?.segments ?? null, pathname);\n}\n","/**\n * Client-side form utilities for server actions.\n *\n * Exports a typed `useActionState` that understands the action builder's result shape.\n * Result is typed to:\n * { data: T } | { validationErrors: Record<string, string[]> } | { serverError: { code, data? } } | null\n *\n * The action builder emits a function that satisfies both the direct call signature\n * and React's `(prevState, formData) => Promise<State>` contract.\n *\n * See design/08-forms-and-actions.md §\"Client-Side Form Mechanics\"\n */\n\nimport { useActionState as reactUseActionState, useTransition } from 'react';\nimport type { ActionResult, ValidationErrors } from '#/server/action-client';\nimport type { FormFlashData } from '#/server/form-flash';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * The action function type accepted by useActionState.\n * Must satisfy React's (prevState, formData) => Promise<State> contract.\n */\nexport type UseActionStateFn<TData> = (\n prevState: ActionResult<TData> | null,\n formData: FormData\n) => Promise<ActionResult<TData>>;\n\n/**\n * Return type of useActionState — matches React 19's useActionState return.\n * [result, formAction, isPending]\n */\nexport type UseActionStateReturn<TData> = [\n result: ActionResult<TData> | null,\n formAction: (formData: FormData) => void,\n isPending: boolean,\n];\n\n// ─── useActionState ──────────────────────────────────────────────────────\n\n/**\n * Typed wrapper around React 19's `useActionState` that understands\n * the timber action builder's result shape.\n *\n * @param action - A server action created with createActionClient or a raw 'use server' function.\n * @param initialState - Initial state, typically `null`. Pass `getFormFlash()` for no-JS\n * progressive enhancement — the flash seeds the initial state so the form has a\n * single source of truth for both with-JS and no-JS paths.\n * @param permalink - Optional permalink for progressive enhancement (no-JS fallback URL).\n *\n * @example\n * ```tsx\n * 'use client'\n * import { useActionState } from '@timber-js/app/client'\n * import { createTodo } from './actions'\n *\n * export function NewTodoForm({ flash }) {\n * const [result, action, isPending] = useActionState(createTodo, flash)\n * return (\n * <form action={action}>\n * <input name=\"title\" />\n * {result?.validationErrors?.title && <p>{result.validationErrors.title}</p>}\n * <button disabled={isPending}>Add</button>\n * </form>\n * )\n * }\n * ```\n */\nexport function useActionState<TData>(\n action: UseActionStateFn<TData>,\n initialState: ActionResult<TData> | FormFlashData | null,\n permalink?: string\n): UseActionStateReturn<TData> {\n // FormFlashData is structurally compatible with ActionResult at runtime —\n // the cast satisfies React's generic inference which would otherwise widen TData.\n return reactUseActionState(action, initialState as ActionResult<TData> | null, permalink);\n}\n\n// ─── useFormAction ───────────────────────────────────────────────────────\n\n/**\n * Hook for calling a server action imperatively (not via a form).\n * Returns [execute, isPending] where execute accepts the input directly.\n *\n * @example\n * ```tsx\n * const [deleteTodo, isPending] = useFormAction(deleteTodoAction)\n * <button onClick={() => deleteTodo({ id: todo.id })} disabled={isPending}>\n * Delete\n * </button>\n * ```\n */\nexport function useFormAction<TData>(\n action: (input: unknown) => Promise<ActionResult<TData>>\n): [(input?: unknown) => Promise<ActionResult<TData>>, boolean] {\n const [isPending, startTransition] = useTransition();\n\n const execute = (input?: unknown): Promise<ActionResult<TData>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await action(input);\n resolve(result);\n });\n });\n };\n\n return [execute, isPending];\n}\n\n// ─── useFormErrors ──────────────────────────────────────────────────────\n\n/** Return type of useFormErrors(). */\nexport interface FormErrorsResult {\n /** Per-field validation errors keyed by field name. */\n fieldErrors: Record<string, string[]>;\n /** Form-level errors (from `_root` key). */\n formErrors: string[];\n /** Server error if the action threw an ActionError. */\n serverError: { code: string; data?: Record<string, unknown> } | null;\n /** Whether any errors are present. */\n hasErrors: boolean;\n /** Get the first error message for a field, or null. */\n getFieldError: (field: string) => string | null;\n}\n\n/**\n * Extract per-field and form-level errors from an ActionResult.\n *\n * Pure function (no internal hooks) — follows React naming convention\n * since it's used in render. Accepts the result from `useActionState`\n * or flash data from `getFormFlash()`.\n *\n * @example\n * ```tsx\n * const [result, action, isPending] = useActionState(createTodo, null)\n * const errors = useFormErrors(result)\n *\n * return (\n * <form action={action}>\n * <input name=\"title\" />\n * {errors.getFieldError('title') && <p>{errors.getFieldError('title')}</p>}\n * {errors.formErrors.map(e => <p key={e}>{e}</p>)}\n * </form>\n * )\n * ```\n */\nexport function useFormErrors<TData>(\n result:\n | ActionResult<TData>\n | {\n validationErrors?: ValidationErrors;\n serverError?: { code: string; data?: Record<string, unknown> };\n }\n | null\n): FormErrorsResult {\n const empty: FormErrorsResult = {\n fieldErrors: {},\n formErrors: [],\n serverError: null,\n hasErrors: false,\n getFieldError: () => null,\n };\n\n if (!result) return empty;\n\n const validationErrors = result.validationErrors as ValidationErrors | undefined;\n const serverError = result.serverError as\n | { code: string; data?: Record<string, unknown> }\n | undefined;\n\n if (!validationErrors && !serverError) return empty;\n\n // Separate _root (form-level) errors from field errors\n const fieldErrors: Record<string, string[]> = {};\n const formErrors: string[] = [];\n\n if (validationErrors) {\n for (const [key, messages] of Object.entries(validationErrors)) {\n if (key === '_root') {\n formErrors.push(...messages);\n } else {\n fieldErrors[key] = messages;\n }\n }\n }\n\n const hasErrors =\n Object.keys(fieldErrors).length > 0 || formErrors.length > 0 || serverError != null;\n\n return {\n fieldErrors,\n formErrors,\n serverError: serverError ?? null,\n hasErrors,\n getFieldError(field: string): string | null {\n const errs = fieldErrors[field];\n return errs && errs.length > 0 ? errs[0] : null;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/client/link-navigate-interceptor.tsx","../../src/client/use-link-status.ts","../../src/client/navigation-context.ts","../../src/client/link-status-provider.tsx","../../src/client/link.tsx","../../src/client/segment-cache.ts","../../src/client/history.ts","../../src/client/use-params.ts","../../src/client/router.ts","../../src/client/use-navigation-pending.ts","../../src/client/router-ref.ts","../../src/client/use-router.ts","../../src/client/use-pathname.ts","../../src/client/use-search-params.ts","../../src/client/segment-context.ts","../../src/client/use-selected-layout-segment.ts","../../src/client/form.tsx"],"sourcesContent":["'use client';\n\n// LinkNavigateInterceptor — client component that stores an onNavigate callback\n// on the parent <a> element so the delegated click handler in browser-entry.ts\n// can invoke it before triggering SPA navigation.\n//\n// See design/19-client-navigation.md, TIM-167\n\nimport { useRef, useEffect, type ReactNode } from 'react';\n\n/** Symbol used to store the onNavigate callback on anchor elements. */\nexport const ON_NAVIGATE_KEY = '__timberOnNavigate' as const;\n\nexport type OnNavigateEvent = {\n preventDefault: () => void;\n};\n\nexport type OnNavigateHandler = (e: OnNavigateEvent) => void;\n\n/**\n * Augment HTMLAnchorElement with the optional onNavigate property.\n * Used by browser-entry.ts handleLinkClick to check for the callback.\n */\ndeclare global {\n interface HTMLAnchorElement {\n [ON_NAVIGATE_KEY]?: OnNavigateHandler;\n }\n}\n\n/**\n * Client component rendered inside <Link> that attaches the onNavigate\n * callback to the closest <a> ancestor via a DOM property. The callback\n * is cleaned up on unmount.\n *\n * Renders no extra DOM — just a transparent wrapper.\n */\nexport function LinkNavigateInterceptor({\n onNavigate,\n children,\n}: {\n onNavigate: OnNavigateHandler;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const anchor = ref.current?.closest('a');\n if (!anchor) return;\n anchor[ON_NAVIGATE_KEY] = onNavigate;\n return () => {\n delete anchor[ON_NAVIGATE_KEY];\n };\n }, [onNavigate]);\n\n // Use a <span> with display:contents to avoid affecting layout.\n // The ref lets us walk up to the parent <a> in the effect.\n return (\n <span ref={ref} style={{ display: 'contents' }}>\n {children}\n </span>\n );\n}\n","'use client';\n\n// useLinkStatus — returns { pending: true } while the nearest parent <Link>'s\n// navigation is in flight. No arguments — scoped via React context.\n// See design/19-client-navigation.md §\"useLinkStatus()\"\n\nimport { useContext, createContext } from 'react';\n\nexport interface LinkStatus {\n pending: boolean;\n}\n\n/**\n * React context provided by <Link>. Holds the pending status\n * for that specific link's navigation.\n */\nexport const LinkStatusContext = createContext<LinkStatus>({ pending: false });\n\n/**\n * Returns `{ pending: true }` while the nearest parent `<Link>` component's\n * navigation is in flight. Must be used inside a `<Link>` component's children.\n *\n * Unlike `useNavigationPending()` which is global, this hook is scoped to\n * the nearest parent `<Link>` — only the link the user clicked shows pending.\n *\n * ```tsx\n * 'use client'\n * import { Link, useLinkStatus } from '@timber-js/app/client'\n *\n * function Hint() {\n * const { pending } = useLinkStatus()\n * return <span className={pending ? 'opacity-50' : ''} />\n * }\n *\n * export function NavLink({ href, children }) {\n * return (\n * <Link href={href}>\n * {children} <Hint />\n * </Link>\n * )\n * }\n * ```\n */\nexport function useLinkStatus(): LinkStatus {\n return useContext(LinkStatusContext);\n}\n","'use client';\n\n/**\n * NavigationContext — React context for navigation state.\n *\n * Holds the current route params and pathname, updated atomically\n * with the RSC tree on each navigation. This replaces the previous\n * useSyncExternalStore approach for useParams() and usePathname(),\n * which suffered from a timing gap: the new tree could commit before\n * the external store re-renders fired, causing a frame where both\n * old and new active states were visible simultaneously.\n *\n * By wrapping the RSC payload element in NavigationProvider inside\n * renderRoot(), the context value and the element tree are passed to\n * reactRoot.render() in the same call — atomic by construction.\n * All consumers (useParams, usePathname) see the new values in the\n * same render pass as the new tree.\n *\n * During SSR, no NavigationProvider is mounted. Hooks fall back to\n * the ALS-backed getSsrData() for per-request isolation.\n *\n * IMPORTANT: createContext and useContext are NOT available in the RSC\n * environment (React Server Components use a stripped-down React).\n * The context is lazily initialized on first access, and all functions\n * that depend on these APIs are safe to call from any environment —\n * they return null or no-op when the APIs aren't available.\n *\n * See design/19-client-navigation.md §\"NavigationContext\"\n */\n\nimport React, { createElement, type ReactNode } from 'react';\n\n// ---------------------------------------------------------------------------\n// Context type\n// ---------------------------------------------------------------------------\n\nexport interface NavigationState {\n params: Record<string, string | string[]>;\n pathname: string;\n}\n\n// ---------------------------------------------------------------------------\n// Lazy context initialization\n// ---------------------------------------------------------------------------\n\n/**\n * The context is created lazily to avoid calling createContext at module\n * level. In the RSC environment, React.createContext doesn't exist —\n * calling it at import time would crash the server.\n *\n * IMPORTANT: Context instances are stored on globalThis, NOT in module-\n * level variables. The RSC client bundler duplicates this module across\n * the browser-entry chunk (index) and client-reference chunk (shared-app)\n * because both entry graphs import it. Module-level variables would create\n * separate singleton instances per chunk — the provider in TransitionRoot\n * (index chunk) would use context A while the consumer in LinkStatusProvider\n * (shared-app chunk) reads from context B. globalThis guarantees a single\n * instance regardless of how many times the module is duplicated.\n *\n * See design/19-client-navigation.md §\"Singleton Context Guarantee\"\n */\n\n// Symbol keys for globalThis storage — prevents collisions with user code\nconst NAV_CTX_KEY = Symbol.for('__timber_nav_ctx');\nconst PENDING_CTX_KEY = Symbol.for('__timber_pending_nav_ctx');\n\nfunction getOrCreateContext(): React.Context<NavigationState | null> | undefined {\n const existing = (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] as\n | React.Context<NavigationState | null>\n | undefined;\n if (existing !== undefined) return existing;\n // createContext may not exist in the RSC environment\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<NavigationState | null>(null);\n (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] = ctx;\n return ctx;\n }\n return undefined;\n}\n\n/**\n * Read the navigation context. Returns null during SSR (no provider)\n * or in the RSC environment (no context available).\n * Internal — used by useParams() and usePathname().\n */\nexport function useNavigationContext(): NavigationState | null {\n const ctx = getOrCreateContext();\n if (!ctx) return null;\n // useContext may not exist in the RSC environment — caller wraps in try/catch\n if (typeof React.useContext !== 'function') return null;\n return React.useContext(ctx);\n}\n\n// ---------------------------------------------------------------------------\n// Provider component\n// ---------------------------------------------------------------------------\n\nexport interface NavigationProviderProps {\n value: NavigationState;\n children?: ReactNode;\n}\n\n/**\n * Wraps children with NavigationContext.Provider.\n *\n * Used in browser-entry.ts renderRoot to wrap the RSC payload element\n * so that navigation state updates atomically with the tree render.\n */\nexport function NavigationProvider({\n value,\n children,\n}: NavigationProviderProps): React.ReactElement {\n const ctx = getOrCreateContext();\n if (!ctx) {\n // RSC environment — no context available. Return children as-is.\n return children as React.ReactElement;\n }\n return createElement(ctx.Provider, { value }, children);\n}\n\n// ---------------------------------------------------------------------------\n// Module-level state for renderRoot to read\n// ---------------------------------------------------------------------------\n\n/**\n * Navigation state communicated between the router and renderRoot.\n *\n * The router calls setNavigationState() before renderRoot(). The\n * renderRoot callback reads via getNavigationState() to create the\n * NavigationProvider with the correct params/pathname.\n *\n * This is NOT used by hooks directly — hooks read from React context.\n *\n * Stored on globalThis (like the context instances above) because the\n * router lives in the shared-app chunk while renderRoot lives in the\n * index chunk. Module-level variables would be separate per chunk.\n */\nconst NAV_STATE_KEY = Symbol.for('__timber_nav_state');\n\nfunction _getNavStateStore(): { current: NavigationState } {\n const g = globalThis as Record<symbol, unknown>;\n if (!g[NAV_STATE_KEY]) {\n g[NAV_STATE_KEY] = { current: { params: {}, pathname: '/' } };\n }\n return g[NAV_STATE_KEY] as { current: NavigationState };\n}\n\nexport function setNavigationState(state: NavigationState): void {\n _getNavStateStore().current = state;\n}\n\nexport function getNavigationState(): NavigationState {\n return _getNavStateStore().current;\n}\n\n// ---------------------------------------------------------------------------\n// Pending Navigation Context (same module for singleton guarantee)\n// ---------------------------------------------------------------------------\n\n/**\n * Separate context for the in-flight navigation URL. Provided by\n * TransitionRoot (urgent useState), consumed by LinkStatusProvider\n * and useNavigationPending.\n *\n * Uses globalThis via Symbol.for for the same reason as NavigationContext\n * above — the bundler duplicates this module across chunks, and module-\n * level variables would create separate context instances.\n */\n\nfunction getOrCreatePendingContext(): React.Context<string | null> | undefined {\n const existing = (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] as\n | React.Context<string | null>\n | undefined;\n if (existing !== undefined) return existing;\n if (typeof React.createContext === 'function') {\n const ctx = React.createContext<string | null>(null);\n (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] = ctx;\n return ctx;\n }\n return undefined;\n}\n\n/**\n * Read the pending navigation URL from context.\n * Returns null during SSR (no provider) or in the RSC environment.\n */\nexport function usePendingNavigationUrl(): string | null {\n const ctx = getOrCreatePendingContext();\n if (!ctx) return null;\n if (typeof React.useContext !== 'function') return null;\n return React.useContext(ctx);\n}\n\n/**\n * Provider for the pending navigation URL. Wraps children with\n * the pending context Provider.\n */\nexport function PendingNavigationProvider({\n value,\n children,\n}: {\n value: string | null;\n children?: ReactNode;\n}): React.ReactElement {\n const ctx = getOrCreatePendingContext();\n if (!ctx) {\n return children as React.ReactElement;\n }\n return createElement(ctx.Provider, { value }, children);\n}\n","'use client';\n\n// LinkStatusProvider — client component that provides per-link pending status\n// via React context. Used inside <Link> to power useLinkStatus().\n//\n// Reads pendingUrl from PendingNavigationContext (provided by TransitionRoot).\n// The pending URL is set as an URGENT update at navigation start (shows\n// immediately) and cleared inside startTransition when the new tree commits\n// (atomic with params/pathname). This eliminates both:\n// 1. The delay before showing the spinner (urgent update, not deferred)\n// 2. The gap between spinner disappearing and active state updating (same commit)\n\nimport type { ReactNode } from 'react';\nimport { LinkStatusContext, type LinkStatus } from './use-link-status.js';\nimport { usePendingNavigationUrl } from './navigation-context.js';\n\nconst NOT_PENDING: LinkStatus = { pending: false };\nconst IS_PENDING: LinkStatus = { pending: true };\n\n/**\n * Client component that reads the pending URL from PendingNavigationContext\n * and provides a scoped LinkStatusContext to children. Renders no extra DOM —\n * just a context provider around children.\n */\nexport function LinkStatusProvider({ href, children }: { href: string; children?: ReactNode }) {\n const pendingUrl = usePendingNavigationUrl();\n const status = pendingUrl === href ? IS_PENDING : NOT_PENDING;\n\n return <LinkStatusContext.Provider value={status}>{children}</LinkStatusContext.Provider>;\n}\n","'use client';\n\n// Link component — client-side navigation with progressive enhancement\n// See design/19-client-navigation.md § Progressive Enhancement\n//\n// Without JavaScript, <Link> renders as a plain <a> tag — standard browser\n// navigation. With JavaScript, the client runtime intercepts clicks on links\n// marked with data-timber-link, fetches RSC payloads, and reconciles the DOM.\n//\n// Typed Link: design/09-typescript.md §\"Typed Link\"\n// - href validated against known routes (via codegen overloads, not runtime)\n// - params prop typed per-route, URL interpolated at runtime\n// - searchParams prop serialized via SearchParamsDefinition\n// - params and fully-resolved string href are mutually exclusive\n// - searchParams and inline query string are mutually exclusive\n\nimport type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport type { SearchParamsDefinition } from '#/search-params/create.js';\nimport type { OnNavigateHandler } from './link-navigate-interceptor.js';\nimport { LinkNavigateInterceptor } from './link-navigate-interceptor.js';\nimport { LinkStatusProvider } from './link-status-provider.js';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/**\n * Base props shared by all Link variants.\n */\ninterface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** Prefetch the RSC payload on hover */\n prefetch?: boolean;\n /**\n * Scroll to top on navigation. Defaults to true.\n * Set to false for tabbed interfaces where content changes within a fixed layout.\n */\n scroll?: boolean;\n /**\n * Called before client-side navigation commits. Call `e.preventDefault()`\n * to cancel the default navigation — the caller is then responsible for\n * navigating (e.g. via `router.push()`).\n *\n * Only fires for client-side SPA navigations, not full page loads.\n * Has no effect during SSR.\n */\n onNavigate?: OnNavigateHandler;\n children?: ReactNode;\n}\n\n/**\n * Link with a fully-resolved string href.\n * When using a string href with params already interpolated,\n * the params prop is not available.\n */\nexport interface LinkPropsWithHref extends LinkBaseProps {\n href: string;\n params?: never;\n /**\n * Typed search params — serialized via the route's SearchParamsDefinition.\n * Mutually exclusive with an inline query string in href.\n */\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n}\n\n/**\n * Link with a route pattern + params for interpolation.\n * e.g. <Link href=\"/products/[id]\" params={{ id: \"123\" }}>\n * <Link href=\"/products/[id]\" params={{ id: 123 }}>\n */\nexport interface LinkPropsWithParams extends LinkBaseProps {\n /** Route pattern with dynamic segments (e.g. \"/products/[id]\") */\n href: string;\n /**\n * Dynamic segment values to interpolate into the href.\n * Single dynamic segments accept string | number (numbers are stringified).\n * Catch-all segments accept string[].\n */\n params: Record<string, string | number | string[]>;\n /**\n * Typed search params — serialized via the route's SearchParamsDefinition.\n */\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n}\n\nexport type LinkProps = LinkPropsWithHref | LinkPropsWithParams;\n\n// ─── Dangerous URL Scheme Detection ──────────────────────────────\n\n/**\n * Reject dangerous URL schemes that could execute script.\n * Security: design/13-security.md § Link scheme injection (test #9)\n */\nconst DANGEROUS_SCHEMES = /^\\s*(javascript|data|vbscript):/i;\n\nexport function validateLinkHref(href: string): void {\n if (DANGEROUS_SCHEMES.test(href)) {\n throw new Error(\n `<Link> received a dangerous href: \"${href}\". ` +\n 'javascript:, data:, and vbscript: URLs are not allowed.'\n );\n }\n}\n\n// ─── Internal Link Detection ─────────────────────────────────────\n\n/** Returns true if the href is an internal path (not an external URL) */\nfunction isInternalHref(href: string): boolean {\n // Relative paths, root-relative paths, and hash links are internal\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return true;\n }\n // Anything with a protocol scheme is external\n if (/^[a-z][a-z0-9+.-]*:/i.test(href)) {\n return false;\n }\n // Bare relative paths (e.g., \"dashboard\") are internal\n return true;\n}\n\n// ─── URL Interpolation ──────────────────────────────────────────\n\n/**\n * Interpolate dynamic segments in a route pattern with actual values.\n * e.g. interpolateParams(\"/products/[id]\", { id: \"123\" }) → \"/products/123\"\n *\n * Supports:\n * - [param] → single segment\n * - [...param] → catch-all (joined with /)\n * - [[...param]] → optional catch-all (omitted if undefined/empty)\n */\nexport function interpolateParams(\n pattern: string,\n params: Record<string, string | number | string[]>\n): string {\n return (\n pattern\n .replace(\n /\\[\\[\\.\\.\\.(\\w+)\\]\\]|\\[\\.\\.\\.(\\w+)\\]|\\[(\\w+)\\]/g,\n (_match, optionalCatchAll, catchAll, single) => {\n if (optionalCatchAll) {\n const value = params[optionalCatchAll];\n if (value === undefined || (Array.isArray(value) && value.length === 0)) {\n return '';\n }\n const segments = Array.isArray(value) ? value : [value];\n return segments.map(encodeURIComponent).join('/');\n }\n\n if (catchAll) {\n const value = params[catchAll];\n if (value === undefined) {\n throw new Error(\n `<Link> missing required catch-all param \"${catchAll}\" for pattern \"${pattern}\".`\n );\n }\n const segments = Array.isArray(value) ? value : [value];\n if (segments.length === 0) {\n throw new Error(\n `<Link> catch-all param \"${catchAll}\" must have at least one segment for pattern \"${pattern}\".`\n );\n }\n return segments.map(encodeURIComponent).join('/');\n }\n\n // single dynamic segment\n const value = params[single];\n if (value === undefined) {\n throw new Error(`<Link> missing required param \"${single}\" for pattern \"${pattern}\".`);\n }\n if (Array.isArray(value)) {\n throw new Error(\n `<Link> param \"${single}\" expected a string but received an array for pattern \"${pattern}\".`\n );\n }\n // Accept numbers — coerce to string for URL interpolation\n return encodeURIComponent(String(value));\n }\n )\n // Clean up trailing slash from empty optional catch-all\n .replace(/\\/+$/, '') || '/'\n );\n}\n\n// ─── Resolve Href ───────────────────────────────────────────────\n\n/**\n * Resolve the final href string from Link props.\n *\n * Handles:\n * - params interpolation into route patterns\n * - searchParams serialization via SearchParamsDefinition\n * - Validation that searchParams and inline query strings are exclusive\n */\nexport function resolveHref(\n href: string,\n params?: Record<string, string | number | string[]>,\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n }\n): string {\n let resolvedPath = href;\n\n // Interpolate params if provided\n if (params) {\n resolvedPath = interpolateParams(href, params);\n }\n\n // Serialize searchParams if provided\n if (searchParams) {\n // Validate: searchParams prop and inline query string are mutually exclusive\n if (resolvedPath.includes('?')) {\n throw new Error(\n '<Link> received both a searchParams prop and a query string in href. ' +\n 'These are mutually exclusive — use one or the other.'\n );\n }\n\n const qs = searchParams.definition.serialize(searchParams.values);\n if (qs) {\n resolvedPath = `${resolvedPath}?${qs}`;\n }\n }\n\n return resolvedPath;\n}\n\n// ─── Build Props ─────────────────────────────────────────────────\n\ninterface LinkOutputProps {\n 'href': string;\n 'data-timber-link'?: boolean;\n 'data-timber-prefetch'?: boolean;\n 'data-timber-scroll'?: string;\n}\n\n/**\n * Build the HTML attributes for a Link. Separated from the component\n * for testability — the component just spreads these onto an <a>.\n */\nexport function buildLinkProps(\n props: Pick<LinkPropsWithHref, 'href' | 'prefetch' | 'scroll'> & {\n params?: Record<string, string | number | string[]>;\n searchParams?: {\n definition: SearchParamsDefinition<Record<string, unknown>>;\n values: Record<string, unknown>;\n };\n }\n): LinkOutputProps {\n const resolvedHref = resolveHref(props.href, props.params, props.searchParams);\n\n validateLinkHref(resolvedHref);\n\n const output: LinkOutputProps = { href: resolvedHref };\n const internal = isInternalHref(resolvedHref);\n\n if (internal) {\n output['data-timber-link'] = true;\n\n if (props.prefetch) {\n output['data-timber-prefetch'] = true;\n }\n\n if (props.scroll === false) {\n output['data-timber-scroll'] = 'false';\n }\n }\n\n return output;\n}\n\n// ─── Link Component ──────────────────────────────────────────────\n\n/**\n * Navigation link with progressive enhancement.\n *\n * Renders as a plain `<a>` tag — works without JavaScript. When the client\n * runtime is active, it intercepts clicks on links marked with\n * `data-timber-link` to perform RSC-based client navigation.\n *\n * Supports typed routes via codegen overloads. At runtime:\n * - `params` prop interpolates dynamic segments in the href pattern\n * - `searchParams` prop serializes query parameters via a SearchParamsDefinition\n */\nexport function Link({\n href,\n prefetch,\n scroll,\n params,\n searchParams,\n onNavigate,\n children,\n ...rest\n}: LinkProps) {\n const linkProps = buildLinkProps({ href, prefetch, scroll, params, searchParams });\n\n const inner = <LinkStatusProvider href={linkProps.href}>{children}</LinkStatusProvider>;\n\n return (\n <a {...rest} {...linkProps}>\n {onNavigate ? (\n <LinkNavigateInterceptor onNavigate={onNavigate}>{inner}</LinkNavigateInterceptor>\n ) : (\n inner\n )}\n </a>\n );\n}\n","// Segment Cache — stores the mounted segment tree and prefetched payloads\n// See design/19-client-navigation.md for architecture details.\n\nimport type { HeadElement } from './head';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/** A prefetched RSC result with optional head elements and segment metadata. */\nexport interface PrefetchResult {\n payload: unknown;\n headElements: HeadElement[] | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo?: SegmentInfo[] | null;\n /** Route params from X-Timber-Params header for populating useParams(). */\n params?: Record<string, string | string[]> | null;\n}\n\n/**\n * A node in the client-side segment tree. Each node represents a mounted\n * layout or page segment with its RSC flight payload.\n */\nexport interface SegmentNode {\n /** The segment's URL pattern (e.g., \"/\", \"/dashboard\", \"/projects/[id]\") */\n segment: string;\n /** The RSC flight payload for this segment (opaque to the cache) */\n payload: unknown;\n /** Whether the segment is async (async layouts always re-render on navigation) */\n isAsync: boolean;\n /** Child segments keyed by segment path */\n children: Map<string, SegmentNode>;\n}\n\n/**\n * Serialized state tree sent via X-Timber-State-Tree header.\n * Only sync segments are included — async segments always re-render.\n */\nexport interface StateTree {\n segments: string[];\n}\n\n// ─── Segment Cache ───────────────────────────────────────────────\n\n/**\n * Maintains the client-side segment tree representing currently mounted\n * layouts and pages. Used for navigation reconciliation — the router diffs\n * new routes against this tree to determine which segments to re-fetch.\n */\nexport class SegmentCache {\n private root: SegmentNode | undefined;\n\n get(segment: string): SegmentNode | undefined {\n if (segment === '/' || segment === this.root?.segment) {\n return this.root;\n }\n return undefined;\n }\n\n set(segment: string, node: SegmentNode): void {\n if (segment === '/' || !this.root) {\n this.root = node;\n }\n }\n\n clear(): void {\n this.root = undefined;\n }\n\n /**\n * Serialize the mounted segment tree for the X-Timber-State-Tree header.\n * Only includes sync segments — async segments are excluded because the\n * server must always re-render them (they may depend on request context).\n *\n * This is a performance optimization only, NOT a security boundary.\n * The server always runs all access.ts files regardless of the state tree.\n */\n serializeStateTree(): StateTree {\n const segments: string[] = [];\n if (this.root) {\n collectSyncSegments(this.root, segments);\n }\n return { segments };\n }\n}\n\n/** Recursively collect sync segment paths from the tree */\nfunction collectSyncSegments(node: SegmentNode, out: string[]): void {\n if (!node.isAsync) {\n out.push(node.segment);\n }\n for (const child of node.children.values()) {\n collectSyncSegments(child, out);\n }\n}\n\n// ─── Segment Tree Builder ────────────────────────────────────────\n\n/**\n * Segment metadata from the server, sent via X-Timber-Segments header.\n * Describes a rendered segment's path and whether it's async.\n */\nexport interface SegmentInfo {\n path: string;\n isAsync: boolean;\n}\n\n/**\n * Build a SegmentNode tree from flat segment metadata.\n *\n * Takes an ordered list of segment descriptors (root → leaf) from the\n * server's X-Timber-Segments header and constructs the hierarchical\n * tree structure that SegmentCache expects.\n *\n * Each segment is nested as a child of the previous one, forming a\n * linear chain from root to leaf. The leaf segment (page) is excluded\n * from the tree — pages are never cached across navigations.\n */\nexport function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefined {\n // Need at least a root segment to build a tree\n if (segments.length === 0) return undefined;\n\n // Exclude the leaf (page) — pages always re-render on navigation.\n // Only layouts are cached in the segment tree.\n const layouts = segments.length > 1 ? segments.slice(0, -1) : segments;\n\n let root: SegmentNode | undefined;\n let parent: SegmentNode | undefined;\n\n for (const info of layouts) {\n const node: SegmentNode = {\n segment: info.path,\n payload: null,\n isAsync: info.isAsync,\n children: new Map(),\n };\n\n if (!root) {\n root = node;\n }\n\n if (parent) {\n parent.children.set(info.path, node);\n }\n\n parent = node;\n }\n\n return root;\n}\n\n// ─── Prefetch Cache ──────────────────────────────────────────────\n\ninterface PrefetchEntry {\n result: PrefetchResult;\n expiresAt: number;\n}\n\n/**\n * Short-lived cache for hover-triggered prefetches. Entries expire after\n * 30 seconds. When a link is clicked, the prefetched payload is consumed\n * (moved to the history stack) and removed from this cache.\n *\n * timber.js does NOT prefetch on viewport intersection — only explicit\n * hover on <Link prefetch> triggers a prefetch.\n */\nexport class PrefetchCache {\n private static readonly TTL_MS = 30_000;\n private entries = new Map<string, PrefetchEntry>();\n\n set(url: string, result: PrefetchResult): void {\n this.entries.set(url, {\n result,\n expiresAt: Date.now() + PrefetchCache.TTL_MS,\n });\n }\n\n get(url: string): PrefetchResult | undefined {\n const entry = this.entries.get(url);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.entries.delete(url);\n return undefined;\n }\n return entry.result;\n }\n\n /** Get and remove the entry (used when navigation consumes a prefetch) */\n consume(url: string): PrefetchResult | undefined {\n const result = this.get(url);\n if (result !== undefined) {\n this.entries.delete(url);\n }\n return result;\n }\n}\n","// History Stack — stores RSC payloads by URL for instant back/forward navigation\n// See design/19-client-navigation.md § History Stack\n\nimport type { HeadElement } from './head';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface HistoryEntry {\n /** The complete segment tree payload at the time of navigation */\n payload: unknown;\n /** Resolved head elements for this page (title, meta tags). Null for SSR'd initial page. */\n headElements?: HeadElement[] | null;\n /** Route params for this page (for useParams). Null for SSR'd initial page. */\n params?: Record<string, string | string[]> | null;\n}\n\n// ─── History Stack ───────────────────────────────────────────────\n\n/**\n * Session-lived history stack keyed by URL. Enables instant back/forward\n * navigation without a server roundtrip.\n *\n * On forward navigation, the new page's payload is pushed onto the stack.\n * On popstate, the cached payload is replayed instantly.\n *\n * Scroll positions are stored in history.state (browser History API),\n * not in this stack — see design/19-client-navigation.md §Scroll Restoration.\n *\n * Entries persist for the session duration (no expiry) and are cleared\n * when the tab is closed — matching browser back-button behavior.\n */\nexport class HistoryStack {\n private entries = new Map<string, HistoryEntry>();\n\n push(url: string, entry: HistoryEntry): void {\n this.entries.set(url, entry);\n }\n\n get(url: string): HistoryEntry | undefined {\n return this.entries.get(url);\n }\n\n has(url: string): boolean {\n return this.entries.has(url);\n }\n}\n","/**\n * useParams() — client-side hook for accessing route params.\n *\n * Returns the dynamic route parameters for the current URL.\n * When called with a route pattern argument, TypeScript narrows\n * the return type to the exact params shape for that route.\n *\n * Two layers of type narrowing work together:\n * 1. The generic overload here uses the Routes interface directly —\n * `useParams<R>()` returns `Routes[R]['params']`.\n * 2. Build-time codegen generates per-route string-literal overloads\n * in the .d.ts file for IDE autocomplete (see routing/codegen.ts).\n *\n * When the Routes interface is empty (no codegen yet), the generic\n * overload has `keyof Routes = never`, so only the fallback matches.\n *\n * During SSR, params are read from the ALS-backed SSR data context\n * (populated by ssr-entry.ts) to ensure correct per-request isolation\n * across concurrent requests with streaming Suspense.\n *\n * Reactivity: On the client, useParams() reads from NavigationContext\n * which is updated atomically with the RSC tree render. This replaces\n * the previous useSyncExternalStore approach that suffered from a\n * timing gap between tree render and store notification — causing\n * preserved layout components to briefly show stale active state.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n *\n * Design doc: design/09-typescript.md §\"Typed Routes\"\n */\n\nimport type { Routes } from '#/index.js';\nimport { getSsrData } from './ssr-data.js';\nimport { currentParams, _setCurrentParams, paramsListeners } from './state.js';\nimport { useNavigationContext } from './navigation-context.js';\n\n// ---------------------------------------------------------------------------\n// Module-level subscribe/notify pattern — kept for backward compat and tests\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to params changes.\n * Retained for backward compatibility with tests that verify the\n * subscribe/notify contract. On the client, useParams() reads from\n * NavigationContext instead.\n */\nexport function subscribe(callback: () => void): () => void {\n paramsListeners.add(callback);\n return () => paramsListeners.delete(callback);\n}\n\n/**\n * Get the current params snapshot (module-level fallback).\n * Used by tests and by the hook when called outside a React component.\n */\nexport function getSnapshot(): Record<string, string | string[]> {\n return currentParams;\n}\n\n// ---------------------------------------------------------------------------\n// Framework API — called by the segment router on each navigation\n// ---------------------------------------------------------------------------\n\n/**\n * Set the current route params in the module-level store.\n *\n * Called by the router on each navigation. This updates the fallback\n * snapshot used by tests and by the hook when called outside a React\n * component (no NavigationContext available).\n *\n * On the client, the primary reactivity path is NavigationContext —\n * the router calls setNavigationState() then renderRoot() which wraps\n * the element in NavigationProvider. setCurrentParams is still called\n * for the module-level fallback.\n *\n * During SSR, params are also available via getSsrData().params\n * (ALS-backed).\n */\nexport function setCurrentParams(params: Record<string, string | string[]>): void {\n _setCurrentParams(params);\n}\n\n/**\n * Notify all legacy subscribers that params have changed.\n *\n * Retained for backward compatibility with tests. On the client,\n * the NavigationContext + renderRoot pattern replaces this — params\n * update atomically with the tree render, so explicit notification\n * is no longer needed.\n */\nexport function notifyParamsListeners(): void {\n for (const listener of paramsListeners) {\n listener();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public hook\n// ---------------------------------------------------------------------------\n\n/**\n * Read the current route's dynamic params.\n *\n * The optional `_route` argument exists only for TypeScript narrowing —\n * it does not affect the runtime return value.\n *\n * On the client, reads from NavigationContext (provided by\n * NavigationProvider in renderRoot). This ensures params update\n * atomically with the RSC tree — no timing gap.\n *\n * During SSR, reads from the ALS-backed SSR data context to ensure\n * per-request isolation across concurrent requests with streaming Suspense.\n *\n * When called outside a React component (e.g., in test assertions),\n * falls back to the module-level snapshot.\n *\n * @overload Typed — when a known route path is passed, returns the\n * exact params shape from the generated Routes interface.\n * @overload Fallback — returns the generic params record.\n */\nexport function useParams<R extends keyof Routes>(route: R): Routes[R]['params'];\nexport function useParams(route?: string): Record<string, string | string[]>;\nexport function useParams(_route?: string): Record<string, string | string[]> {\n // Try reading from NavigationContext (client-side, inside React tree).\n // During SSR, no NavigationProvider is mounted, so this returns null.\n // When called outside a React component, useContext throws — caught below.\n try {\n const navContext = useNavigationContext();\n if (navContext !== null) {\n return navContext.params;\n }\n } catch {\n // No React dispatcher available (called outside a component).\n // Fall through to module-level snapshot below.\n }\n\n // SSR path: read from ALS-backed SSR data context.\n // Falls back to module-level currentParams for tests.\n return getSsrData()?.params ?? currentParams;\n}\n","// Segment Router — manages client-side navigation and RSC payload fetching\n// See design/19-client-navigation.md for the full architecture.\n\nimport { SegmentCache, PrefetchCache, buildSegmentTree } from './segment-cache';\nimport type { SegmentInfo } from './segment-cache';\nimport { HistoryStack } from './history';\nimport type { HeadElement } from './head';\nimport { setCurrentParams } from './use-params.js';\nimport { setNavigationState } from './navigation-context.js';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface NavigationOptions {\n /** Set to false to prevent scroll-to-top on forward navigation */\n scroll?: boolean;\n /** Use replaceState instead of pushState (replaces current history entry) */\n replace?: boolean;\n}\n\n/**\n * Function that decodes an RSC Flight stream into a React element tree.\n * In production: createFromFetch from @vitejs/plugin-rsc/browser.\n * In tests: a mock that returns the raw payload.\n */\nexport type RscDecoder = (fetchPromise: Promise<Response>) => unknown;\n\n/**\n * Function that renders a decoded RSC element tree into the DOM.\n * In production: reactRoot.render(element).\n * In tests: a no-op or mock.\n */\nexport type RootRenderer = (element: unknown) => void;\n\n/**\n * Platform dependencies injected for testability. In production these\n * map to browser APIs; in tests they're replaced with mocks.\n */\nexport interface RouterDeps {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n pushState: (data: unknown, unused: string, url: string) => void;\n replaceState: (data: unknown, unused: string, url: string) => void;\n scrollTo: (x: number, y: number) => void;\n getCurrentUrl: () => string;\n getScrollY: () => number;\n /** Decode RSC Flight stream into React elements. If not provided, raw response text is stored. */\n decodeRsc?: RscDecoder;\n /** Render decoded RSC tree into the DOM. If not provided, rendering is a no-op. */\n renderRoot?: RootRenderer;\n /**\n * Schedule a callback after the next paint. In the browser, this is\n * requestAnimationFrame + setTimeout(0) to run after React commits.\n * In tests, this runs the callback synchronously.\n */\n afterPaint?: (callback: () => void) => void;\n /** Apply resolved head elements (title, meta tags) to the DOM after navigation. */\n applyHead?: (elements: HeadElement[]) => void;\n /**\n * Run a navigation inside a React transition with optimistic pending URL.\n * The pending URL shows immediately (useOptimistic urgent update) and\n * reverts when the transition commits (atomic with the new tree).\n *\n * The `perform` callback receives a `wrapPayload` function to wrap the\n * decoded RSC payload with NavigationProvider + NuqsAdapter before\n * TransitionRoot sets it as the new element.\n *\n * If not provided (tests), the router falls back to renderRoot.\n */\n navigateTransition?: (\n pendingUrl: string,\n perform: (wrapPayload: (payload: unknown) => unknown) => Promise<unknown>\n ) => Promise<void>;\n}\n\n/** Result of fetching an RSC payload — includes head elements and segment metadata. */\ninterface FetchResult {\n payload: unknown;\n headElements: HeadElement[] | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo: SegmentInfo[] | null;\n /** Route params from X-Timber-Params header for populating useParams(). */\n params: Record<string, string | string[]> | null;\n}\n\nexport interface RouterInstance {\n /** Navigate to a new URL (forward navigation) */\n navigate(url: string, options?: NavigationOptions): Promise<void>;\n /** Full re-render of the current URL — no state tree sent */\n refresh(): Promise<void>;\n /** Handle a popstate event (back/forward button). scrollY is read from history.state. */\n handlePopState(url: string, scrollY?: number): Promise<void>;\n /** Whether a navigation is currently in flight */\n isPending(): boolean;\n /** The URL currently being navigated to, or null if idle */\n getPendingUrl(): string | null;\n /** Subscribe to pending state changes */\n onPendingChange(listener: (pending: boolean) => void): () => void;\n /** Prefetch an RSC payload for a URL (used by Link hover) */\n prefetch(url: string): void;\n /**\n * Apply a piggybacked revalidation payload from a server action response.\n * Renders the element tree and updates head elements without a server fetch.\n * See design/08-forms-and-actions.md §\"Single-Roundtrip Revalidation\".\n */\n applyRevalidation(element: unknown, headElements: HeadElement[] | null): void;\n /**\n * Populate the segment cache from server-provided segment metadata.\n * Called on initial hydration with segment info embedded in the HTML.\n */\n initSegmentCache(segments: SegmentInfo[]): void;\n /** The segment cache (exposed for tests and <Link> prefetch) */\n segmentCache: SegmentCache;\n /** The prefetch cache (exposed for tests and <Link> prefetch) */\n prefetchCache: PrefetchCache;\n /** The history stack (exposed for tests) */\n historyStack: HistoryStack;\n}\n\n/**\n * Thrown when an RSC payload response contains X-Timber-Redirect header.\n * Caught in navigate() to trigger a soft router navigation to the redirect target.\n */\nclass RedirectError extends Error {\n readonly redirectUrl: string;\n constructor(url: string) {\n super(`Server redirect to ${url}`);\n this.redirectUrl = url;\n }\n}\n\n/**\n * Check if an error is an abort error (connection closed / fetch aborted).\n * Browsers throw DOMException with name 'AbortError' when a fetch is aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n if (error instanceof Error && error.name === 'AbortError') return true;\n return false;\n}\n\n// ─── RSC Fetch ───────────────────────────────────────────────────\n\nconst RSC_CONTENT_TYPE = 'text/x-component';\n\n/**\n * Generate a short random cache-busting ID (5 chars, a-z0-9).\n * Matches the format Next.js uses for _rsc params.\n */\nfunction generateCacheBustId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let id = '';\n for (let i = 0; i < 5; i++) {\n id += chars[(Math.random() * 36) | 0];\n }\n return id;\n}\n\n/**\n * Append a `_rsc=<id>` query parameter to the URL.\n * Follows Next.js's pattern — prevents CDN/browser from serving cached HTML\n * for RSC navigation requests and signals that this is an RSC fetch.\n */\nfunction appendRscParam(url: string): string {\n const separator = url.includes('?') ? '&' : '?';\n return `${url}${separator}_rsc=${generateCacheBustId()}`;\n}\n\nfunction buildRscHeaders(\n stateTree: { segments: string[] } | undefined,\n currentUrl?: string\n): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: RSC_CONTENT_TYPE,\n };\n if (stateTree) {\n headers['X-Timber-State-Tree'] = JSON.stringify(stateTree);\n }\n // Send current URL for intercepting route resolution.\n // The server uses this to determine if an intercepting route should\n // render instead of the actual target route (modal pattern).\n // See design/07-routing.md §\"Intercepting Routes\"\n if (currentUrl) {\n headers['X-Timber-URL'] = currentUrl;\n }\n return headers;\n}\n\n/**\n * Extract head elements from the X-Timber-Head response header.\n * Returns null if the header is missing or malformed.\n */\nfunction extractHeadElements(response: Response): HeadElement[] | null {\n const header = response.headers.get('X-Timber-Head');\n if (!header) return null;\n try {\n return JSON.parse(decodeURIComponent(header));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract segment metadata from the X-Timber-Segments response header.\n * Returns null if the header is missing or malformed.\n *\n * Format: JSON array of {path, isAsync} objects describing the rendered\n * segment chain from root to leaf. Used to populate the client-side\n * segment cache for state tree diffing on subsequent navigations.\n */\nfunction extractSegmentInfo(response: Response): SegmentInfo[] | null {\n const header = response.headers.get('X-Timber-Segments');\n if (!header) return null;\n try {\n return JSON.parse(header);\n } catch {\n return null;\n }\n}\n\n/**\n * Extract route params from the X-Timber-Params response header.\n * Returns null if the header is missing or malformed.\n *\n * Used to populate useParams() after client-side navigation.\n */\nfunction extractParams(response: Response): Record<string, string | string[]> | null {\n const header = response.headers.get('X-Timber-Params');\n if (!header) return null;\n try {\n return JSON.parse(header);\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch an RSC payload from the server. If a decodeRsc function is provided,\n * the response is decoded into a React element tree via createFromFetch.\n * Otherwise, the raw response text is returned (test mode).\n *\n * Also extracts head elements from the X-Timber-Head response header\n * so the client can update document.title and <meta> tags after navigation.\n */\nasync function fetchRscPayload(\n url: string,\n deps: RouterDeps,\n stateTree?: { segments: string[] },\n currentUrl?: string\n): Promise<FetchResult> {\n const rscUrl = appendRscParam(url);\n const headers = buildRscHeaders(stateTree, currentUrl);\n if (deps.decodeRsc) {\n // Production path: use createFromFetch for streaming RSC decoding.\n // createFromFetch takes a Promise<Response> and progressively parses\n // the RSC Flight stream as chunks arrive.\n //\n // Intercept the response to read X-Timber-Head before createFromFetch\n // consumes the body. Reading headers does NOT consume the body stream.\n const fetchPromise = deps.fetch(rscUrl, { headers, redirect: 'manual' });\n let headElements: HeadElement[] | null = null;\n let segmentInfo: SegmentInfo[] | null = null;\n let params: Record<string, string | string[]> | null = null;\n const wrappedPromise = fetchPromise.then((response) => {\n // Detect server-side redirects. The server returns 204 + X-Timber-Redirect\n // for RSC payload requests instead of a raw 302, because fetch with\n // redirect: \"manual\" turns 302s into opaque redirects (status 0, null body)\n // which crashes createFromFetch when it tries to read the body stream.\n const redirectLocation =\n response.headers.get('X-Timber-Redirect') ||\n (response.status >= 300 && response.status < 400 ? response.headers.get('Location') : null);\n if (redirectLocation) {\n throw new RedirectError(redirectLocation);\n }\n headElements = extractHeadElements(response);\n segmentInfo = extractSegmentInfo(response);\n params = extractParams(response);\n return response;\n });\n // Await so headElements/segmentInfo/params are populated before we return.\n // Also await the decoded payload — createFromFetch returns a thenable\n // that resolves to the React element tree.\n await wrappedPromise;\n const payload = await deps.decodeRsc(wrappedPromise);\n return { payload, headElements, segmentInfo, params };\n }\n // Test/fallback path: return raw text\n const response = await deps.fetch(rscUrl, { headers, redirect: 'manual' });\n // Check for redirect in test path too\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('Location');\n if (location) {\n throw new RedirectError(location);\n }\n }\n return {\n payload: await response.text(),\n headElements: extractHeadElements(response),\n segmentInfo: extractSegmentInfo(response),\n params: extractParams(response),\n };\n}\n\n// ─── Router Factory ──────────────────────────────────────────────\n\n/**\n * Create a router instance. In production, called once at app hydration\n * with real browser APIs. In tests, called with mock dependencies.\n */\nexport function createRouter(deps: RouterDeps): RouterInstance {\n const segmentCache = new SegmentCache();\n const prefetchCache = new PrefetchCache();\n const historyStack = new HistoryStack();\n\n let pending = false;\n let pendingUrl: string | null = null;\n const pendingListeners = new Set<(pending: boolean) => void>();\n\n function setPending(value: boolean, url?: string): void {\n const newPendingUrl = value && url ? url : null;\n if (pending === value && pendingUrl === newPendingUrl) return;\n pending = value;\n pendingUrl = newPendingUrl;\n // Notify external store listeners (non-React consumers).\n // React-facing pending state is handled by useOptimistic in\n // TransitionRoot via navigateTransition — not this function.\n for (const listener of pendingListeners) {\n listener(value);\n }\n }\n\n /** Update the segment cache from server-provided segment metadata. */\n function updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void {\n if (!segmentInfo || segmentInfo.length === 0) return;\n const tree = buildSegmentTree(segmentInfo);\n if (tree) {\n segmentCache.set('/', tree);\n }\n }\n\n /** Render a decoded RSC payload into the DOM if a renderer is available. */\n function renderPayload(payload: unknown): void {\n if (deps.renderRoot) {\n deps.renderRoot(payload);\n }\n }\n\n /**\n * Update navigation state (params + pathname) for the next render.\n *\n * Sets both the module-level fallback (for tests and SSR) and the\n * navigation context state (read by renderRoot to wrap the element\n * in NavigationProvider). The context update is atomic with the tree\n * render — both are passed to reactRoot.render() in the same call.\n */\n function updateNavigationState(\n params: Record<string, string | string[]> | null | undefined,\n url: string\n ): void {\n const resolvedParams = params ?? {};\n // Module-level fallback for tests (no NavigationProvider) and SSR\n setCurrentParams(resolvedParams);\n // Navigation context — read by renderRoot to wrap the RSC element\n const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0] || '/';\n setNavigationState({ params: resolvedParams, pathname });\n }\n\n /**\n * Render a payload via navigateTransition (production) or renderRoot (tests).\n * The perform callback should fetch data, update state, and return the payload.\n * In production, the entire callback runs inside a React transition with\n * useOptimistic for the pending URL. In tests, the payload is rendered directly.\n */\n async function renderViaTransition(\n pendingUrl: string,\n perform: () => Promise<FetchResult>\n ): Promise<HeadElement[] | null> {\n if (deps.navigateTransition) {\n let headElements: HeadElement[] | null = null;\n await deps.navigateTransition(pendingUrl, async (wrapPayload) => {\n const result = await perform();\n headElements = result.headElements;\n return wrapPayload(result.payload);\n });\n return headElements;\n }\n // Fallback: no transition (tests, no React tree)\n const result = await perform();\n renderPayload(result.payload);\n return result.headElements;\n }\n\n /** Apply head elements (title, meta tags) to the DOM if available. */\n function applyHead(elements: HeadElement[] | null | undefined): void {\n if (elements && deps.applyHead) {\n deps.applyHead(elements);\n }\n }\n\n /** Run a callback after the next paint (after React commit). */\n function afterPaint(callback: () => void): void {\n if (deps.afterPaint) {\n deps.afterPaint(callback);\n } else {\n callback();\n }\n }\n\n /**\n * Core navigation logic shared between the transition and fallback paths.\n * Fetches the RSC payload, updates all state, and returns the result.\n */\n async function performNavigationFetch(\n url: string,\n options: { replace: boolean }\n ): Promise<FetchResult> {\n // Check prefetch cache first. PrefetchResult has optional segmentInfo/params\n // fields — normalize to null for FetchResult compatibility.\n const prefetched = prefetchCache.consume(url);\n let result: FetchResult | undefined = prefetched\n ? {\n payload: prefetched.payload,\n headElements: prefetched.headElements,\n segmentInfo: prefetched.segmentInfo ?? null,\n params: prefetched.params ?? null,\n }\n : undefined;\n\n if (result === undefined) {\n // Fetch RSC payload with state tree for partial rendering.\n // Send current URL for intercepting route resolution (modal pattern).\n const stateTree = segmentCache.serializeStateTree();\n const rawCurrentUrl = deps.getCurrentUrl();\n const currentUrl = rawCurrentUrl.startsWith('http')\n ? new URL(rawCurrentUrl).pathname\n : new URL(rawCurrentUrl, 'http://localhost').pathname;\n result = await fetchRscPayload(url, deps, stateTree, currentUrl);\n }\n\n // Update the browser history — replace mode overwrites the current entry\n if (options.replace) {\n deps.replaceState({ timber: true, scrollY: 0 }, '', url);\n } else {\n deps.pushState({ timber: true, scrollY: 0 }, '', url);\n }\n\n // Store the payload in the history stack\n historyStack.push(url, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n\n // Update the segment cache with the new route's segment tree.\n updateSegmentCache(result.segmentInfo);\n\n // Update navigation state (params + pathname) before rendering.\n updateNavigationState(result.params, url);\n\n return result;\n }\n\n async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {\n const scroll = options.scroll !== false;\n const replace = options.replace === true;\n\n // Capture the departing page's scroll position for scroll={false} preservation.\n const currentScrollY = deps.getScrollY();\n\n // Save the departing page's scroll position in history.state before\n // pushing a new entry. This ensures back/forward navigation can restore\n // the correct scroll position from the browser's per-entry state.\n deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());\n\n setPending(true, url);\n\n try {\n const headElements = await renderViaTransition(url, () =>\n performNavigationFetch(url, { replace })\n );\n\n // Update document.title and <meta> tags with the new page's metadata\n applyHead(headElements);\n\n // Notify nuqs adapter (and any other listeners) that navigation completed.\n window.dispatchEvent(new Event('timber:navigation-end'));\n\n // Scroll-to-top on forward navigation, or restore captured position\n // for scroll={false}. React's render() on the document root can reset\n // scroll during DOM reconciliation, so all scroll must be actively managed.\n afterPaint(() => {\n if (scroll) {\n deps.scrollTo(0, 0);\n } else {\n deps.scrollTo(0, currentScrollY);\n }\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } catch (error) {\n // Server-side redirect during RSC fetch → soft router navigation.\n if (error instanceof RedirectError) {\n setPending(false);\n await navigate(error.redirectUrl, { replace: true });\n return;\n }\n // Abort errors are not application errors — swallow silently.\n if (isAbortError(error)) return;\n throw error;\n } finally {\n setPending(false);\n }\n }\n\n async function refresh(): Promise<void> {\n const currentUrl = deps.getCurrentUrl();\n\n setPending(true, currentUrl);\n\n try {\n const headElements = await renderViaTransition(currentUrl, async () => {\n // No state tree sent — server renders the complete RSC payload\n const result = await fetchRscPayload(currentUrl, deps);\n historyStack.push(currentUrl, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n updateSegmentCache(result.segmentInfo);\n updateNavigationState(result.params, currentUrl);\n return result;\n });\n\n applyHead(headElements);\n } finally {\n setPending(false);\n }\n }\n\n async function handlePopState(url: string, scrollY: number = 0): Promise<void> {\n // Scroll position is read from history.state by the caller (browser-entry.ts)\n // and passed in. This is more reliable than tracking scroll per-URL in memory\n // because the browser maintains per-entry state even with duplicate URLs.\n const entry = historyStack.get(url);\n\n if (entry && entry.payload !== null) {\n // Replay cached payload — no server roundtrip\n updateNavigationState(entry.params, url);\n renderPayload(entry.payload);\n applyHead(entry.headElements);\n afterPaint(() => {\n deps.scrollTo(0, scrollY);\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } else {\n // No cached payload — fetch from server.\n // This happens when navigating back to the initial SSR'd page\n // (its payload is null since it was rendered via SSR, not RSC fetch)\n // or when the entry doesn't exist at all.\n setPending(true, url);\n try {\n const headElements = await renderViaTransition(url, async () => {\n const stateTree = segmentCache.serializeStateTree();\n const result = await fetchRscPayload(url, deps, stateTree);\n updateSegmentCache(result.segmentInfo);\n updateNavigationState(result.params, url);\n historyStack.push(url, {\n payload: result.payload,\n headElements: result.headElements,\n params: result.params,\n });\n return result;\n });\n\n applyHead(headElements);\n afterPaint(() => {\n deps.scrollTo(0, scrollY);\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n } finally {\n setPending(false);\n }\n }\n }\n\n /**\n * Prefetch an RSC payload for a URL and store it in the prefetch cache.\n * Called on hover of <Link prefetch> elements.\n */\n function prefetch(url: string): void {\n // Don't prefetch if already cached\n if (prefetchCache.get(url) !== undefined) return;\n if (historyStack.has(url)) return;\n\n // Fire-and-forget fetch\n const stateTree = segmentCache.serializeStateTree();\n void fetchRscPayload(url, deps, stateTree).then(\n (result) => {\n prefetchCache.set(url, result);\n },\n () => {\n // Prefetch failure is non-fatal — navigation will fetch fresh\n }\n );\n }\n\n return {\n navigate,\n refresh,\n handlePopState,\n isPending: () => pending,\n getPendingUrl: () => pendingUrl,\n onPendingChange(listener) {\n pendingListeners.add(listener);\n return () => pendingListeners.delete(listener);\n },\n prefetch,\n applyRevalidation(element: unknown, headElements: HeadElement[] | null): void {\n // Render the piggybacked element tree from a server action response.\n // Updates the current history entry with the fresh payload and applies\n // head elements — same as refresh() but without a server fetch.\n const currentUrl = deps.getCurrentUrl();\n historyStack.push(currentUrl, {\n payload: element,\n headElements,\n });\n renderPayload(element);\n applyHead(headElements);\n },\n initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),\n segmentCache,\n prefetchCache,\n historyStack,\n };\n}\n","// useNavigationPending — returns true while an RSC navigation is in flight.\n// See design/19-client-navigation.md §\"useNavigationPending()\"\n//\n// Reads from PendingNavigationContext (provided by TransitionRoot) so the\n// pending state shows immediately (urgent update) and clears atomically\n// with the new tree (same startTransition commit).\n\nimport { usePendingNavigationUrl } from './navigation-context.js';\n\n/**\n * Returns true while an RSC navigation is in flight.\n *\n * The pending state is true from the moment the RSC fetch starts until\n * React reconciliation completes. This includes the fetch itself,\n * RSC stream parsing, and React tree reconciliation.\n *\n * It does NOT include Suspense streaming after the shell — only the\n * initial shell reconciliation.\n *\n * ```tsx\n * 'use client'\n * import { useNavigationPending } from '@timber-js/app/client'\n *\n * export function NavBar() {\n * const isPending = useNavigationPending()\n * return (\n * <nav className={isPending ? 'opacity-50' : ''}>\n * <Link href=\"/dashboard\">Dashboard</Link>\n * </nav>\n * )\n * }\n * ```\n */\nexport function useNavigationPending(): boolean {\n const pendingUrl = usePendingNavigationUrl();\n // During SSR or outside PendingNavigationProvider, no navigation is pending\n return pendingUrl !== null;\n}\n","// Global router reference — shared between browser-entry and client hooks.\n//\n// Delegates to client/state.ts for the actual module-level variable.\n// This ensures singleton semantics regardless of import path — all\n// callers converge on the same state.ts instance via the barrel.\n//\n// See design/18-build-system.md §\"Module Singleton Strategy\"\n\nimport type { RouterInstance } from './router.js';\nimport { globalRouter, _setGlobalRouter } from './state.js';\n\n/**\n * Set the global router instance. Called once during bootstrap.\n */\nexport function setGlobalRouter(router: RouterInstance): void {\n _setGlobalRouter(router);\n}\n\n/**\n * Get the global router instance. Throws if called before bootstrap.\n * Used by client-side hooks (useNavigationPending, etc.)\n */\nexport function getRouter(): RouterInstance {\n if (!globalRouter) {\n throw new Error('[timber] Router not initialized. getRouter() was called before bootstrap().');\n }\n return globalRouter;\n}\n\n/**\n * Get the global router instance or null if not yet initialized.\n * Used by useRouter() methods to avoid silent failures — callers\n * can log a meaningful warning instead of silently no-oping.\n */\nexport function getRouterOrNull(): RouterInstance | null {\n return globalRouter;\n}\n\n/**\n * Reset the global router to null. Used only in tests to isolate\n * module-level state between test cases.\n * @internal\n */\nexport function resetGlobalRouter(): void {\n _setGlobalRouter(null);\n}\n","/**\n * useRouter() — client-side hook for programmatic navigation.\n *\n * Returns a router instance with push, replace, refresh, back, forward,\n * and prefetch methods. Compatible with Next.js's `useRouter()` from\n * `next/navigation` (App Router).\n *\n * This wraps timber's internal RouterInstance in the Next.js-compatible\n * AppRouterInstance shape that ecosystem libraries expect.\n *\n * NOTE: Unlike Next.js, these methods do NOT wrap navigation in\n * startTransition. In Next.js, router state is React state (useReducer)\n * so startTransition defers the update and provides isPending tracking.\n * In timber, navigation calls reactRoot.render() which is a root-level\n * render — startTransition has no effect on root renders.\n *\n * Navigation state (params, pathname) is delivered atomically via\n * NavigationContext embedded in the element tree passed to\n * reactRoot.render(). See design/19-client-navigation.md §\"NavigationContext\".\n *\n * For loading UI during navigation, use:\n * - useLinkStatus() — per-link pending indicator (inside <Link>)\n * - useNavigationPending() — global navigation pending state\n */\n\nimport { getRouterOrNull } from './router-ref.js';\n\nexport interface AppRouterInstance {\n /** Navigate to a URL, pushing a new history entry */\n push(href: string, options?: { scroll?: boolean }): void;\n /** Navigate to a URL, replacing the current history entry */\n replace(href: string, options?: { scroll?: boolean }): void;\n /** Refresh the current page (re-fetch RSC payload) */\n refresh(): void;\n /** Navigate back in history */\n back(): void;\n /** Navigate forward in history */\n forward(): void;\n /** Prefetch an RSC payload for a URL */\n prefetch(href: string): void;\n}\n\n/**\n * Get a router instance for programmatic navigation.\n *\n * Compatible with Next.js's `useRouter()` from `next/navigation`.\n *\n * Methods lazily resolve the global router when invoked (during user\n * interaction) rather than capturing it at render time. This is critical\n * because during hydration, React synchronously executes component render\n * functions *before* the router is bootstrapped in browser-entry.ts.\n * If we eagerly captured the router during render, components would get\n * a null reference and be stuck with silent no-ops forever.\n *\n * Returns safe no-ops during SSR or before bootstrap. The `typeof window`\n * check is insufficient because Vite's client SSR environment defines\n * `window`, so we use a try/catch on getRouter() — but only at method\n * invocation time, not at render time.\n */\nexport function useRouter(): AppRouterInstance {\n return {\n push(href: string, options?: { scroll?: boolean }) {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error(\n '[timber] useRouter().push() called but router is not initialized. This is a bug — please report it.'\n );\n }\n return;\n }\n void router.navigate(href, { scroll: options?.scroll });\n },\n replace(href: string, options?: { scroll?: boolean }) {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error('[timber] useRouter().replace() called but router is not initialized.');\n }\n return;\n }\n void router.navigate(href, { scroll: options?.scroll, replace: true });\n },\n refresh() {\n const router = getRouterOrNull();\n if (!router) {\n if (process.env.NODE_ENV === 'development') {\n console.error('[timber] useRouter().refresh() called but router is not initialized.');\n }\n return;\n }\n void router.refresh();\n },\n back() {\n if (typeof window !== 'undefined') window.history.back();\n },\n forward() {\n if (typeof window !== 'undefined') window.history.forward();\n },\n prefetch(href: string) {\n const router = getRouterOrNull();\n if (!router) return; // Silent — prefetch failure is non-fatal\n router.prefetch(href);\n },\n };\n}\n","/**\n * usePathname() — client-side hook for reading the current pathname.\n *\n * Returns the pathname portion of the current URL (e.g. '/dashboard/settings').\n * Updates when client-side navigation changes the URL.\n *\n * On the client, reads from NavigationContext which is updated atomically\n * with the RSC tree render. This replaces the previous useSyncExternalStore\n * approach which only subscribed to popstate events — meaning usePathname()\n * did NOT re-render on forward navigation (pushState). The context approach\n * fixes this: pathname updates in the same render pass as the new tree.\n *\n * During SSR, reads the request pathname from the SSR ALS context\n * (populated by ssr-entry.ts) instead of window.location.\n *\n * Compatible with Next.js's `usePathname()` from `next/navigation`.\n */\n\nimport { getSsrData } from './ssr-data.js';\nimport { useNavigationContext } from './navigation-context.js';\n\n/**\n * Read the current URL pathname.\n *\n * On the client, reads from NavigationContext (provided by\n * NavigationProvider in renderRoot). During SSR, reads from the\n * ALS-backed SSR data context. Falls back to window.location.pathname\n * when called outside a React component (e.g., in tests).\n */\nexport function usePathname(): string {\n // Try reading from NavigationContext (client-side, inside React tree).\n // During SSR, no NavigationProvider is mounted, so this returns null.\n try {\n const navContext = useNavigationContext();\n if (navContext !== null) {\n return navContext.pathname;\n }\n } catch {\n // No React dispatcher available (called outside a component).\n // Fall through to SSR/fallback below.\n }\n\n // SSR path: read from ALS-backed SSR data context.\n const ssrData = getSsrData();\n if (ssrData) return ssrData.pathname ?? '/';\n\n // Final fallback: window.location (tests, edge cases).\n if (typeof window !== 'undefined') return window.location.pathname;\n return '/';\n}\n","/**\n * useSearchParams() — client-side hook for reading URL search params.\n *\n * Returns a read-only URLSearchParams instance reflecting the current\n * URL's query string. Updates when client-side navigation changes the URL.\n *\n * This is a thin wrapper over window.location.search, provided for\n * Next.js API compatibility (libraries like nuqs import useSearchParams\n * from next/navigation).\n *\n * Unlike Next.js's ReadonlyURLSearchParams, this returns a standard\n * URLSearchParams. Mutation methods (set, delete, append) work on the\n * local copy but do NOT affect the URL — use the router or nuqs for that.\n *\n * During SSR, reads the request search params from the SSR ALS context\n * (populated by ssr-entry.ts) instead of window.location.\n *\n * All mutable state is delegated to client/state.ts for singleton guarantees.\n * See design/18-build-system.md §\"Singleton State Registry\"\n */\n\nimport { useSyncExternalStore } from 'react';\nimport { getSsrData } from './ssr-data.js';\nimport { cachedSearch, cachedSearchParams, _setCachedSearch } from './state.js';\n\nfunction getSearch(): string {\n if (typeof window !== 'undefined') return window.location.search;\n const data = getSsrData();\n if (!data) return '';\n const sp = new URLSearchParams(data.searchParams);\n const str = sp.toString();\n return str ? `?${str}` : '';\n}\n\nfunction getServerSearch(): string {\n const data = getSsrData();\n if (!data) return '';\n const sp = new URLSearchParams(data.searchParams);\n const str = sp.toString();\n return str ? `?${str}` : '';\n}\n\nfunction subscribe(callback: () => void): () => void {\n window.addEventListener('popstate', callback);\n return () => window.removeEventListener('popstate', callback);\n}\n\n// Cache the last search string and its parsed URLSearchParams to avoid\n// creating a new object on every render when the URL hasn't changed.\n// State lives in client/state.ts for singleton guarantees.\n\nfunction getSearchParams(): URLSearchParams {\n const search = getSearch();\n if (search !== cachedSearch) {\n const params = new URLSearchParams(search);\n _setCachedSearch(search, params);\n return params;\n }\n return cachedSearchParams;\n}\n\nfunction getServerSearchParams(): URLSearchParams {\n const data = getSsrData();\n return data ? new URLSearchParams(data.searchParams) : new URLSearchParams();\n}\n\n/**\n * Read the current URL search params.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\nexport function useSearchParams(): URLSearchParams {\n // useSyncExternalStore needs a primitive snapshot for comparison.\n // We use the raw search string as the snapshot, then return the\n // parsed URLSearchParams.\n useSyncExternalStore(subscribe, getSearch, getServerSearch);\n return typeof window !== 'undefined' ? getSearchParams() : getServerSearchParams();\n}\n","/**\n * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.\n *\n * Each layout in the segment tree is wrapped with a SegmentProvider that stores\n * the URL segments from root to the current layout level. The hooks read this\n * context to determine which child segments are active below the calling layout.\n *\n * The context value is intentionally minimal: just the segment path array and\n * parallel route keys. No internal cache details are exposed.\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { createContext, useContext, createElement, useMemo } from 'react';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface SegmentContextValue {\n /** URL segments from root to this layout (e.g. ['', 'dashboard', 'settings']) */\n segments: string[];\n /** Parallel route slot keys available at this layout level (e.g. ['sidebar', 'modal']) */\n parallelRouteKeys: string[];\n}\n\n// ─── Context ─────────────────────────────────────────────────────\n\nconst SegmentContext = createContext<SegmentContextValue | null>(null);\n\n/** Read the segment context. Returns null if no provider is above this component. */\nexport function useSegmentContext(): SegmentContextValue | null {\n return useContext(SegmentContext);\n}\n\n// ─── Provider ────────────────────────────────────────────────────\n\ninterface SegmentProviderProps {\n segments: string[];\n parallelRouteKeys: string[];\n children: React.ReactNode;\n}\n\n/**\n * Wraps each layout to provide segment position context.\n * Injected by rsc-entry.ts during element tree construction.\n */\nexport function SegmentProvider({ segments, parallelRouteKeys, children }: SegmentProviderProps) {\n const value = useMemo(\n () => ({ segments, parallelRouteKeys }),\n // segments and parallelRouteKeys are static per layout — they don't change\n // across navigations. The layout's position in the tree is fixed.\n // Intentionally using derived keys — segments/parallelRouteKeys are static per layout\n [segments.join('/'), parallelRouteKeys.join(',')]\n );\n return createElement(SegmentContext.Provider, { value }, children);\n}\n","/**\n * useSelectedLayoutSegment / useSelectedLayoutSegments — client-side hooks\n * for reading the active segment(s) below the current layout.\n *\n * These hooks are used by navigation UIs to highlight active sections.\n * They match Next.js's API from next/navigation.\n *\n * How they work:\n * 1. Each layout is wrapped with a SegmentProvider that records its depth\n * (the URL segments from root to that layout level).\n * 2. The hooks read the current URL pathname via usePathname().\n * 3. They compare the layout's segment depth against the full URL segments\n * to determine which child segments are \"selected\" below.\n *\n * Example: For URL \"/dashboard/settings/profile\"\n * - Root layout (depth 0, segments: ['']): selected segment = \"dashboard\"\n * - Dashboard layout (depth 1, segments: ['', 'dashboard']): selected = \"settings\"\n * - Settings layout (depth 2, segments: ['', 'dashboard', 'settings']): selected = \"profile\"\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { useSegmentContext } from './segment-context.js';\nimport { usePathname } from './use-pathname.js';\n\n/**\n * Split a pathname into URL segments.\n * \"/\" → [\"\"]\n * \"/dashboard\" → [\"\", \"dashboard\"]\n * \"/dashboard/settings\" → [\"\", \"dashboard\", \"settings\"]\n */\nexport function pathnameToSegments(pathname: string): string[] {\n return pathname.split('/');\n}\n\n/**\n * Pure function: compute the selected child segment given a layout's segment\n * depth and the current URL pathname.\n *\n * @param contextSegments — segments from root to the calling layout, or null if no context\n * @param pathname — current URL pathname\n * @returns the active child segment one level below, or null if at the leaf\n */\nexport function getSelectedSegment(\n contextSegments: string[] | null,\n pathname: string\n): string | null {\n const urlSegments = pathnameToSegments(pathname);\n\n if (!contextSegments) {\n return urlSegments[1] || null;\n }\n\n const depth = contextSegments.length;\n return urlSegments[depth] || null;\n}\n\n/**\n * Pure function: compute all selected segments below a layout's depth.\n *\n * @param contextSegments — segments from root to the calling layout, or null if no context\n * @param pathname — current URL pathname\n * @returns all active segments below the layout\n */\nexport function getSelectedSegments(contextSegments: string[] | null, pathname: string): string[] {\n const urlSegments = pathnameToSegments(pathname);\n\n if (!contextSegments) {\n return urlSegments.slice(1).filter(Boolean);\n }\n\n const depth = contextSegments.length;\n return urlSegments.slice(depth).filter(Boolean);\n}\n\n/**\n * Returns the active child segment one level below the layout where this\n * hook is called. Returns `null` if the layout is the leaf (no child segment).\n *\n * Compatible with Next.js's `useSelectedLayoutSegment()` from `next/navigation`.\n *\n * @param parallelRouteKey — Optional parallel route key. Currently unused\n * (parallel route segment tracking is not yet implemented). Accepted for\n * API compatibility with Next.js.\n */\nexport function useSelectedLayoutSegment(parallelRouteKey?: string): string | null {\n void parallelRouteKey;\n const context = useSegmentContext();\n const pathname = usePathname();\n return getSelectedSegment(context?.segments ?? null, pathname);\n}\n\n/**\n * Returns all active segments below the layout where this hook is called.\n * Returns an empty array if the layout is the leaf (no child segments).\n *\n * Compatible with Next.js's `useSelectedLayoutSegments()` from `next/navigation`.\n *\n * @param parallelRouteKey — Optional parallel route key. Currently unused\n * (parallel route segment tracking is not yet implemented). Accepted for\n * API compatibility with Next.js.\n */\nexport function useSelectedLayoutSegments(parallelRouteKey?: string): string[] {\n void parallelRouteKey;\n const context = useSegmentContext();\n const pathname = usePathname();\n return getSelectedSegments(context?.segments ?? null, pathname);\n}\n","/**\n * Client-side form utilities for server actions.\n *\n * Exports a typed `useActionState` that understands the action builder's result shape.\n * Result is typed to:\n * { data: T } | { validationErrors: Record<string, string[]> } | { serverError: { code, data? } } | null\n *\n * The action builder emits a function that satisfies both the direct call signature\n * and React's `(prevState, formData) => Promise<State>` contract.\n *\n * See design/08-forms-and-actions.md §\"Client-Side Form Mechanics\"\n */\n\nimport { useActionState as reactUseActionState, useTransition } from 'react';\nimport type { ActionResult, ValidationErrors } from '#/server/action-client';\nimport type { FormFlashData } from '#/server/form-flash';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * The action function type accepted by useActionState.\n * Must satisfy React's (prevState, formData) => Promise<State> contract.\n */\nexport type UseActionStateFn<TData> = (\n prevState: ActionResult<TData> | null,\n formData: FormData\n) => Promise<ActionResult<TData>>;\n\n/**\n * Return type of useActionState — matches React 19's useActionState return.\n * [result, formAction, isPending]\n */\nexport type UseActionStateReturn<TData> = [\n result: ActionResult<TData> | null,\n formAction: (formData: FormData) => void,\n isPending: boolean,\n];\n\n// ─── useActionState ──────────────────────────────────────────────────────\n\n/**\n * Typed wrapper around React 19's `useActionState` that understands\n * the timber action builder's result shape.\n *\n * @param action - A server action created with createActionClient or a raw 'use server' function.\n * @param initialState - Initial state, typically `null`. Pass `getFormFlash()` for no-JS\n * progressive enhancement — the flash seeds the initial state so the form has a\n * single source of truth for both with-JS and no-JS paths.\n * @param permalink - Optional permalink for progressive enhancement (no-JS fallback URL).\n *\n * @example\n * ```tsx\n * 'use client'\n * import { useActionState } from '@timber-js/app/client'\n * import { createTodo } from './actions'\n *\n * export function NewTodoForm({ flash }) {\n * const [result, action, isPending] = useActionState(createTodo, flash)\n * return (\n * <form action={action}>\n * <input name=\"title\" />\n * {result?.validationErrors?.title && <p>{result.validationErrors.title}</p>}\n * <button disabled={isPending}>Add</button>\n * </form>\n * )\n * }\n * ```\n */\nexport function useActionState<TData>(\n action: UseActionStateFn<TData>,\n initialState: ActionResult<TData> | FormFlashData | null,\n permalink?: string\n): UseActionStateReturn<TData> {\n // FormFlashData is structurally compatible with ActionResult at runtime —\n // the cast satisfies React's generic inference which would otherwise widen TData.\n return reactUseActionState(action, initialState as ActionResult<TData> | null, permalink);\n}\n\n// ─── useFormAction ───────────────────────────────────────────────────────\n\n/**\n * Hook for calling a server action imperatively (not via a form).\n * Returns [execute, isPending] where execute accepts the input directly.\n *\n * @example\n * ```tsx\n * const [deleteTodo, isPending] = useFormAction(deleteTodoAction)\n * <button onClick={() => deleteTodo({ id: todo.id })} disabled={isPending}>\n * Delete\n * </button>\n * ```\n */\nexport function useFormAction<TData>(\n action: (input: unknown) => Promise<ActionResult<TData>>\n): [(input?: unknown) => Promise<ActionResult<TData>>, boolean] {\n const [isPending, startTransition] = useTransition();\n\n const execute = (input?: unknown): Promise<ActionResult<TData>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await action(input);\n resolve(result);\n });\n });\n };\n\n return [execute, isPending];\n}\n\n// ─── useFormErrors ──────────────────────────────────────────────────────\n\n/** Return type of useFormErrors(). */\nexport interface FormErrorsResult {\n /** Per-field validation errors keyed by field name. */\n fieldErrors: Record<string, string[]>;\n /** Form-level errors (from `_root` key). */\n formErrors: string[];\n /** Server error if the action threw an ActionError. */\n serverError: { code: string; data?: Record<string, unknown> } | null;\n /** Whether any errors are present. */\n hasErrors: boolean;\n /** Get the first error message for a field, or null. */\n getFieldError: (field: string) => string | null;\n}\n\n/**\n * Extract per-field and form-level errors from an ActionResult.\n *\n * Pure function (no internal hooks) — follows React naming convention\n * since it's used in render. Accepts the result from `useActionState`\n * or flash data from `getFormFlash()`.\n *\n * @example\n * ```tsx\n * const [result, action, isPending] = useActionState(createTodo, null)\n * const errors = useFormErrors(result)\n *\n * return (\n * <form action={action}>\n * <input name=\"title\" />\n * {errors.getFieldError('title') && <p>{errors.getFieldError('title')}</p>}\n * {errors.formErrors.map(e => <p key={e}>{e}</p>)}\n * </form>\n * )\n * ```\n */\nexport function useFormErrors<TData>(\n result:\n | ActionResult<TData>\n | {\n validationErrors?: ValidationErrors;\n serverError?: { code: string; data?: Record<string, unknown> };\n }\n | null\n): FormErrorsResult {\n const empty: FormErrorsResult = {\n fieldErrors: {},\n formErrors: [],\n serverError: null,\n hasErrors: false,\n getFieldError: () => null,\n };\n\n if (!result) return empty;\n\n const validationErrors = result.validationErrors as ValidationErrors | undefined;\n const serverError = result.serverError as\n | { code: string; data?: Record<string, unknown> }\n | undefined;\n\n if (!validationErrors && !serverError) return empty;\n\n // Separate _root (form-level) errors from field errors\n const fieldErrors: Record<string, string[]> = {};\n const formErrors: string[] = [];\n\n if (validationErrors) {\n for (const [key, messages] of Object.entries(validationErrors)) {\n if (key === '_root') {\n formErrors.push(...messages);\n } else {\n fieldErrors[key] = messages;\n }\n }\n }\n\n const hasErrors =\n Object.keys(fieldErrors).length > 0 || formErrors.length > 0 || serverError != null;\n\n return {\n fieldErrors,\n formErrors,\n serverError: serverError ?? null,\n hasErrors,\n getFieldError(field: string): string | null {\n const errs = fieldErrors[field];\n return errs && errs.length > 0 ? errs[0] : null;\n },\n };\n}\n"],"mappings":"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"}
@@ -46,7 +46,7 @@ export interface NavigationProviderProps {
46
46
  * Used in browser-entry.ts renderRoot to wrap the RSC payload element
47
47
  * so that navigation state updates atomically with the tree render.
48
48
  */
49
- export declare function NavigationProvider({ value, children }: NavigationProviderProps): React.ReactElement;
49
+ export declare function NavigationProvider({ value, children, }: NavigationProviderProps): React.ReactElement;
50
50
  export declare function setNavigationState(state: NavigationState): void;
51
51
  export declare function getNavigationState(): NavigationState;
52
52
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"navigation-context.d.ts","sourceRoot":"","sources":["../../src/client/navigation-context.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM7D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyCD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,eAAe,GAAG,IAAI,CAM7D;AAMD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAOnG;AA6BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAE/D;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAEpD;AA6BD;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAKvD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,GAAG,KAAK,CAAC,YAAY,CAMrB"}
1
+ {"version":3,"file":"navigation-context.d.ts","sourceRoot":"","sources":["../../src/client/navigation-context.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM7D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyCD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,eAAe,GAAG,IAAI,CAM7D;AAMD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,GACT,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAO9C;AA6BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAE/D;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAEpD;AA6BD;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAKvD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,GAAG,KAAK,CAAC,YAAY,CAMrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/client/router.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/client/router.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transition-root.d.ts","sourceRoot":"","sources":["../../src/client/transition-root.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAuBf;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GAAG,SAAS,CAyC7E;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAIzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
1
+ {"version":3,"file":"transition-root.d.ts","sourceRoot":"","sources":["../../src/client/transition-root.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAsB/E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GAAG,SAAS,CAyC7E;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAIzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-query-states.d.ts","sourceRoot":"","sources":["../../src/client/use-query-states.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,SAAS,EACT,kBAAkB,EACnB,MAAM,2BAA2B,CAAC;AAoCnC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,aAAa,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,MAAM,EAClE,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAsDnB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,UAAU,EAAE,sBAAsB,CAAC,CAAC,CAAC,GACpC,CAAC,OAAO,CAAC,EAAE,kBAAkB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAIrD"}
1
+ {"version":3,"file":"use-query-states.d.ts","sourceRoot":"","sources":["../../src/client/use-query-states.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,SAAS,EACT,kBAAkB,EACnB,MAAM,2BAA2B,CAAC;AAoCnC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,aAAa,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,MAAM,EAClE,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAyDnB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,UAAU,EAAE,sBAAsB,CAAC,CAAC,CAAC,GACpC,CAAC,OAAO,CAAC,EAAE,kBAAkB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAIrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-router.d.ts","sourceRoot":"","sources":["../../src/client/use-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,6DAA6D;IAC7D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,sDAAsD;IACtD,OAAO,IAAI,IAAI,CAAC;IAChB,+BAA+B;IAC/B,IAAI,IAAI,IAAI,CAAC;IACb,kCAAkC;IAClC,OAAO,IAAI,IAAI,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,IAAI,iBAAiB,CA4C7C"}
1
+ {"version":3,"file":"use-router.d.ts","sourceRoot":"","sources":["../../src/client/use-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,6DAA6D;IAC7D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,sDAAsD;IACtD,OAAO,IAAI,IAAI,CAAC;IAChB,+BAA+B;IAC/B,IAAI,IAAI,IAAI,CAAC;IACb,kCAAkC;IAClC,OAAO,IAAI,IAAI,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,IAAI,iBAAiB,CA8C7C"}
@@ -1,5 +1,5 @@
1
- import "../_chunks/als-registry-c0AGnbqS.js";
2
- import { n as cookies } from "../_chunks/request-context-C69VW4xS.js";
1
+ import "../_chunks/als-registry-k-AtAQ9R.js";
2
+ import { n as cookies } from "../_chunks/request-context-CRj2Zh1E.js";
3
3
  import "../_chunks/ssr-data-DLnbYpj1.js";
4
4
  import { t as useCookie } from "../_chunks/use-cookie-dDbpCTx-.js";
5
5
  //#region src/cookies/define-cookie.ts