{"version":3,"file":"index.mjs","names":["NOOP_INSTANCE","NOOP_INSTANCE","NOOP_INSTANCE"],"sources":["../../src/components/RouteView/components.tsx","../../src/components/RouteView/helpers.tsx","../../src/useSyncExternalStore.ts","../../src/hooks/useNavigator.tsx","../../src/hooks/useRouter.tsx","../../src/hooks/useRouteNode.tsx","../../src/components/RouteView/RouteView.tsx","../../src/constants.ts","../../../../shared/dom-utils/route-announcer.ts","../../../../shared/dom-utils/scroll-restore.ts","../../../../shared/dom-utils/scroll-spy.ts","../../../../shared/dom-utils/view-transitions.ts","../../../../shared/dom-utils/link-utils.ts","../../src/hooks/useIsActiveRoute.tsx","../../src/components/Link.tsx","../../src/components/RouterErrorBoundary.tsx","../../src/hooks/useRouteUtils.tsx","../../src/hooks/useRouterTransition.tsx","../../src/hooks/useRouteExit.tsx","../../src/hooks/useRouteEnter.tsx","../../src/RouterProvider.tsx"],"sourcesContent":["import type { MatchProps, NotFoundProps, SelfProps } from \"./types\";\n\nexport function Match(_props: MatchProps): null {\n  return null;\n}\n\nMatch.displayName = \"RouteView.Match\";\n\nexport function Self(_props: SelfProps): null {\n  return null;\n}\n\nSelf.displayName = \"RouteView.Self\";\n\nexport function NotFound(_props: NotFoundProps): null {\n  return null;\n}\n\nNotFound.displayName = \"RouteView.NotFound\";\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\nimport { startsWithSegment } from \"@real-router/route-utils\";\nimport { Fragment, isValidElement, toChildArray } from \"preact\";\nimport { Suspense } from \"preact/compat\";\n\nimport { Match, NotFound, Self } from \"./components\";\n\nimport type { MatchProps, NotFoundProps, SelfProps } from \"./types\";\nimport type { VNode, ComponentChildren } from \"preact\";\n\nconst MARKER_TYPES: ReadonlySet<unknown> = new Set([Match, Self, NotFound]);\n\ninterface FallbackSlots {\n  selfChildren: ComponentChildren;\n  selfFallback: ComponentChildren | undefined;\n  selfFound: boolean;\n  notFoundChildren: ComponentChildren;\n  notFoundFound: boolean;\n}\n\nfunction isSegmentMatch(\n  routeName: string,\n  fullSegmentName: string,\n  exact: boolean,\n): boolean {\n  if (fullSegmentName === \"\") {\n    return false;\n  }\n\n  if (exact) {\n    return routeName === fullSegmentName;\n  }\n\n  return startsWithSegment(routeName, fullSegmentName);\n}\n\nexport function collectElements(\n  children: ComponentChildren,\n  result: VNode[],\n): void {\n  for (const child of toChildArray(children)) {\n    if (!isValidElement(child)) {\n      continue;\n    }\n\n    if (MARKER_TYPES.has(child.type)) {\n      result.push(child);\n    } else {\n      collectElements(\n        (child.props as { readonly children: ComponentChildren }).children,\n        result,\n      );\n    }\n  }\n}\n\nfunction renderSlot(\n  slotChildren: ComponentChildren,\n  key: string,\n  fallback?: ComponentChildren,\n): VNode {\n  const content =\n    fallback === undefined ? (\n      slotChildren\n    ) : (\n      <Suspense fallback={fallback}>{slotChildren}</Suspense>\n    );\n\n  return <Fragment key={key}>{content}</Fragment>;\n}\n\nfunction isFallbackKind(child: VNode): boolean {\n  return child.type === NotFound || child.type === Self;\n}\n\nfunction assignFallbackSlot(child: VNode, slots: FallbackSlots): void {\n  if (child.type === NotFound) {\n    // First-wins: subsequent <NotFound> elements are ignored, symmetric with\n    // <Self> below and the React adapter (#1220 / #1439). A boolean flag, not a\n    // `notFoundChildren === null` sentinel: a first <NotFound>{null}</NotFound>\n    // leaves the slot null, and a sentinel guard would let a later one overwrite.\n    if (!slots.notFoundFound) {\n      slots.notFoundChildren = (child.props as NotFoundProps).children;\n      slots.notFoundFound = true;\n    }\n\n    return;\n  }\n\n  if (!slots.selfFound) {\n    slots.selfChildren = (child.props as SelfProps).children;\n    slots.selfFallback = (child.props as SelfProps).fallback;\n    slots.selfFound = true;\n  }\n}\n\nfunction processMatch(\n  child: VNode,\n  routeName: string,\n  nodeName: string,\n  alreadyActive: boolean,\n): VNode | null {\n  const {\n    segment,\n    exact = false,\n    fallback,\n    children,\n  } = child.props as MatchProps;\n  const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;\n  const isActive =\n    !alreadyActive && isSegmentMatch(routeName, fullSegmentName, exact);\n\n  if (!isActive) {\n    return null;\n  }\n\n  return renderSlot(children, fullSegmentName, fallback);\n}\n\nfunction appendFallback(\n  rendered: VNode[],\n  routeName: string,\n  nodeName: string,\n  slots: FallbackSlots,\n): void {\n  if (slots.selfFound && routeName === nodeName) {\n    rendered.push(\n      renderSlot(slots.selfChildren, \"__route-view-self__\", slots.selfFallback),\n    );\n\n    return;\n  }\n\n  if (routeName === UNKNOWN_ROUTE && slots.notFoundChildren !== null) {\n    rendered.push(\n      <Fragment key=\"__route-view-not-found__\">\n        {slots.notFoundChildren}\n      </Fragment>,\n    );\n  }\n}\n\nexport function buildRenderList(\n  elements: VNode[],\n  routeName: string,\n  nodeName: string,\n): { rendered: VNode[]; activeMatchFound: boolean } {\n  const slots: FallbackSlots = {\n    selfChildren: null,\n    selfFallback: undefined,\n    selfFound: false,\n    notFoundChildren: null,\n    notFoundFound: false,\n  };\n  let activeMatchFound = false;\n  const rendered: VNode[] = [];\n\n  for (const child of elements) {\n    if (isFallbackKind(child)) {\n      assignFallbackSlot(child, slots);\n\n      continue;\n    }\n\n    const matchRendered = processMatch(\n      child,\n      routeName,\n      nodeName,\n      activeMatchFound,\n    );\n\n    if (matchRendered !== null) {\n      activeMatchFound = true;\n      rendered.push(matchRendered);\n    }\n  }\n\n  if (!activeMatchFound) {\n    appendFallback(rendered, routeName, nodeName, slots);\n  }\n\n  return { rendered, activeMatchFound };\n}\n","import { useEffect, useState } from \"preact/hooks\";\n\n/**\n * Polyfill for React's useSyncExternalStore.\n *\n * Preact does not provide a native useSyncExternalStore.\n * This implementation uses useState + useEffect to subscribe\n * to external stores.\n *\n * Race condition handling: the value may change between\n * `useState(getSnapshot)` (render) and `useEffect` (commit).\n * We synchronize by calling `setValue(getSnapshot())` before\n * subscribing in the effect.\n *\n * The updater uses `Object.is` to bail out when the snapshot\n * is referentially stable, preventing redundant re-renders.\n *\n * SSR semantics: `_getServerSnapshot` is intentionally ignored.\n * Preact's `preact-render-to-string` runs `useState(getSnapshot)`\n * on the server and does not commit effects, so the initial\n * render already uses `getSnapshot()`. Real-Router's `createRouteSource`\n * (and friends) return the same value on server and client given the\n * same `router` instance, so passing `getSnapshot` itself as the third\n * argument at every call site is the symmetric SSR contract; a separate\n * `getServerSnapshot` would diverge during hydration. Consumers that\n * truly need a different server value should branch in `getSnapshot`.\n *\n * Stable-reference contract: `subscribe` and `getSnapshot` are deps of\n * the subscription effect. If a consumer passes inline closures, every\n * render triggers `unsubscribe → subscribe` plus a fresh `sync()` pass\n * — a silent O(N) reconnect that the Preact polyfill cannot bail out\n * of (React's native impl uses an internal sub-store keyed by identity;\n * we cannot replicate that without losing the latest-snapshot guarantee).\n * All Real-Router hooks pass router-keyed cached factories from\n * `@real-router/sources`, which produce stable refs per `(router, args…)`\n * — keep that pattern for every external use.\n */\nexport function useSyncExternalStore<T>(\n  subscribe: (onStoreChange: () => void) => () => void,\n  getSnapshot: () => T,\n  _getServerSnapshot?: () => T,\n): T {\n  const [value, setValue] = useState(getSnapshot);\n\n  useEffect(() => {\n    const sync = (): void => {\n      setValue((prev) => {\n        const next = getSnapshot();\n\n        return Object.is(prev, next) ? prev : next;\n      });\n    };\n\n    sync();\n\n    return subscribe(sync);\n  }, [subscribe, getSnapshot]);\n\n  return value;\n}\n","import { createUseContextOrThrow, NavigatorContext } from \"../context\";\n\nimport type { Navigator } from \"@real-router/core\";\n\nexport const useNavigator: () => Navigator = createUseContextOrThrow(\n  NavigatorContext,\n  \"useNavigator\",\n);\n","import { createUseContextOrThrow, RouterContext } from \"../context\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport const useRouter: () => Router = createUseContextOrThrow(\n  RouterContext,\n  \"useRouter\",\n);\n","import { createRouteNodeSource } from \"@real-router/sources\";\nimport { useMemo } from \"preact/hooks\";\n\nimport { useSyncExternalStore } from \"../useSyncExternalStore\";\nimport { useNavigator } from \"./useNavigator\";\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouteContext } from \"../types\";\n\nexport function useRouteNode(nodeName: string): RouteContext {\n  const router = useRouter();\n  const navigator = useNavigator();\n\n  // `createRouteNodeSource` is the cached factory from `@real-router/sources`\n  // keyed on (router, nodeName) — identical args return identical refs across\n  // renders. No `useMemo` needed.\n  const store = createRouteNodeSource(router, nodeName);\n\n  const { route, previousRoute } = useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n  );\n\n  // Public stable-ref contract (locked by `useRouteNode.test.tsx` \"should\n  // return stable reference when nothing changes\"): consecutive renders with\n  // identical (navigator, route, previousRoute) return the same RouteContext\n  // ref. Drop this `useMemo` and downstream consumers re-render on every\n  // parent re-render.\n  return useMemo(\n    (): RouteContext => ({ navigator, route, previousRoute }),\n    [navigator, route, previousRoute],\n  );\n}\n","import { useMemo } from \"preact/hooks\";\n\nimport { Match, NotFound, Self } from \"./components\";\nimport { buildRenderList, collectElements } from \"./helpers\";\nimport { useRouteNode } from \"../../hooks/useRouteNode\";\n\nimport type { RouteViewProps } from \"./types\";\nimport type { VNode } from \"preact\";\n\nfunction RouteViewRoot({\n  nodeName,\n  children,\n}: Readonly<RouteViewProps>): VNode | null {\n  const { route } = useRouteNode(nodeName);\n\n  // Cache the flattened Match/Self/NotFound list across renders with unchanged\n  // children. children only differs when the parent re-renders with a new\n  // node, so this memoises the steady-state traversal.\n  const elements = useMemo(() => {\n    const collected: VNode[] = [];\n\n    collectElements(children, collected);\n\n    return collected;\n  }, [children]);\n\n  const routeName = route?.name;\n\n  // buildRenderList is O(N) over Match/Self/NotFound children. Memo on\n  // (elements, routeName, nodeName) skips the re-walk on parent re-renders\n  // that don't change the active route; navigations always invalidate via\n  // routeName.\n  const rendered = useMemo(() => {\n    if (routeName === undefined) {\n      return [];\n    }\n\n    return buildRenderList(elements, routeName, nodeName).rendered;\n  }, [elements, routeName, nodeName]);\n\n  return rendered.length > 0 ? <>{rendered}</> : null;\n}\n\nRouteViewRoot.displayName = \"RouteView\";\n\nexport const RouteView = Object.assign(RouteViewRoot, {\n  Match,\n  Self,\n  NotFound,\n});\n\nexport type {\n  RouteViewProps,\n  MatchProps as RouteViewMatchProps,\n  SelfProps as RouteViewSelfProps,\n  NotFoundProps as RouteViewNotFoundProps,\n} from \"./types\";\n","/**\n * Stable empty object for default params\n */\nexport const EMPTY_PARAMS = Object.freeze({});\n\n/**\n * Stable empty options object\n */\nexport const EMPTY_OPTIONS = Object.freeze({});\n","import type { Router, State } from \"@real-router/core\";\n\nconst CLEAR_DELAY = 7000;\nconst SAFARI_READY_DELAY = 100;\nconst ANNOUNCER_ATTR = \"data-real-router-announcer\";\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\nconst VISUALLY_HIDDEN =\n  \"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border:0\";\n\nexport interface RouteAnnouncerOptions {\n  prefix?: string;\n  getAnnouncementText?: (route: State) => string;\n}\n\nconst NOOP_INSTANCE: { destroy: () => void } = Object.freeze({\n  destroy: () => {\n    /* no-op */\n  },\n});\n\n// Live (non-NOOP) instances sharing the single `[data-real-router-announcer]`\n// aria-live element. The element is created once by the first instance\n// (`getOrCreateAnnouncer`) and reused by the rest; it must be removed only when\n// the LAST holder is destroyed. Without this count the first provider's\n// destroy() would detach the shared node while sibling providers (micro-\n// frontends — the same multi-provider scenario `scroll-restore`'s `storageKey`\n// exists for) keep writing to the now-orphaned node → silent screen reader (#783).\n// ⚑ The ref-count and the generation live ON THE ELEMENT (#1924). The element\n// is found with `document.querySelector`, so it is document-scoped, while a\n// module variable is bundle-scoped — and a page carrying a second adapter\n// bundle, the micro-frontend case the ref-count exists for, shares the element\n// and not the scope.\n//\n// ⚠ Module-scoped counters reach #783 verbatim through that seam: the second\n// bundle takes the `existing` branch, reads its own generation, passes the\n// #1217 ownership guard, decrements its own count to zero and removes the live\n// element.\nconst REFS_ATTR = \"data-rr-announcer-refs\";\nconst GENERATION_ATTR = \"data-rr-announcer-generation\";\n\nfunction readCount(element: HTMLElement): number {\n  // `Number(null)` is 0, so an absent attribute needs no fallback term.\n  return Number(element.getAttribute(REFS_ATTR));\n}\n\nfunction writeCount(element: HTMLElement, value: number): void {\n  element.setAttribute(REFS_ATTR, String(value));\n}\n// Generation token (#1217): bumped each time a FRESH shared element is created.\n// Each instance captures the generation live at construction; on destroy it\n// touches the shared refcount / element ONLY if its generation is still current.\n// A stale instance — whose element a host wiped without calling destroy() — must\n// not decrement the new generation's refcount (→ negative) or remove its live\n// element (a selector-based removeAnnouncer takes whoever is in the DOM).\n\nexport function createRouteAnnouncer(\n  router: Router,\n  options?: RouteAnnouncerOptions,\n): { destroy: () => void } {\n  // Defensive SSR / non-browser guard: in SSR (Node.js) or non-DOM\n  // environments, `document` is undefined and the announcer cannot\n  // attach its aria-live region. Return a frozen NOOP_INSTANCE — same\n  // pattern as `createDirectionTracker`, `createScrollRestoration`, and\n  // `createViewTransitions`. Without this guard, `NavigationAnnouncer`\n  // component construction would throw `ReferenceError: document is not\n  // defined` under `@angular/ssr` rendering, tearing down the whole SSR\n  // bootstrap. Closes review-2026-05-10 §5.10 ⛔ \"NavigationAnnouncer\n  // SSR mode\" MED.\n  if (typeof document === \"undefined\") {\n    return NOOP_INSTANCE;\n  }\n\n  const prefix = options?.prefix ?? \"Navigated to \";\n  const getCustomText = options?.getAnnouncementText;\n\n  let isInitialNavigation = true;\n  let isReady = false;\n  let isDestroyed = false;\n  let lastAnnouncedText = \"\";\n  let pendingText: string | null = null;\n  let clearTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n  const { element: announcer, generation: myGeneration } =\n    getOrCreateAnnouncer();\n\n  writeCount(announcer, readCount(announcer) + 1);\n\n  const doAnnounce = (text: string, h1: HTMLElement | null): void => {\n    lastAnnouncedText = text;\n    clearTimeout(clearTimeoutId);\n    announcer.textContent = text;\n    clearTimeoutId = setTimeout(() => {\n      announcer.textContent = \"\";\n      lastAnnouncedText = \"\";\n    }, CLEAR_DELAY);\n\n    manageFocus(h1);\n  };\n\n  // Safari-ready delay: announcing before VoiceOver wires up the aria-live region\n  // causes the first announcement to be silently dropped. Wait SAFARI_READY_DELAY ms\n  // before marking the announcer \"ready\" — any navigation during that window is\n  // buffered in pendingText and flushed once the delay expires.\n  const safariTimeoutId = setTimeout(() => {\n    isReady = true;\n\n    if (pendingText !== null && !isDestroyed) {\n      const text = pendingText;\n\n      pendingText = null;\n      doAnnounce(text, document.querySelector<HTMLElement>(\"h1\"));\n    }\n  }, SAFARI_READY_DELAY);\n\n  const unsubscribe = router.subscribe(({ route }) => {\n    if (isInitialNavigation) {\n      isInitialNavigation = false;\n\n      return;\n    }\n\n    // Double rAF: waits for two paint frames so the incoming route's DOM\n    // (including the new <h1>) is fully rendered before resolveText reads it.\n    // Single rAF fires before the new route's template has been attached,\n    // which would cause resolveText to pick up the OLD h1 or fall back to\n    // document.title / route.name prematurely.\n    requestAnimationFrame(() => {\n      requestAnimationFrame(() => {\n        if (isDestroyed) {\n          return;\n        }\n\n        const h1 = document.querySelector<HTMLElement>(\"h1\");\n        const text = resolveText(route, prefix, getCustomText, h1);\n\n        if (!text || text === lastAnnouncedText) {\n          return;\n        }\n\n        if (!isReady) {\n          // Defer announcement until Safari-ready window elapses (see safariTimeoutId).\n          pendingText = text;\n\n          return;\n        }\n\n        doAnnounce(text, h1);\n      });\n    });\n  });\n\n  return {\n    destroy() {\n      // Idempotency guard — required so the ref-count is decremented EXACTLY\n      // once per instance. A double destroy() must not drop the count below the\n      // number of live holders (which would detach a sibling's element, or\n      // leave it attached forever).\n      if (isDestroyed) {\n        return;\n      }\n\n      isDestroyed = true;\n      unsubscribe();\n      clearTimeout(clearTimeoutId);\n      clearTimeout(safariTimeoutId);\n\n      // Ownership guard (#1217): if a host wiped the shared element out from\n      // under us, the next getOrCreateAnnouncer bumped the generation for the\n      // fresh element. A stale instance must NOT decrement the new generation's\n      // refcount (→ negative) or remove its live element — bail if not current.\n      // ⚠ Against the DOCUMENT's generation, never the captured element's: the\n      // element a stale instance holds was removed, and its own attribute\n      // still reads its own generation, so comparing the two can only ever be\n      // equal and the guard would be dead.\n      if (myGeneration !== currentGeneration()) {\n        return;\n      }\n\n      const remaining = readCount(announcer) - 1;\n\n      writeCount(announcer, remaining);\n\n      // Only the last holder tears down the shared element — via our captured\n      // ref, not a selector query (which would delete whoever's element is\n      // currently in the DOM, i.e. a newer generation's).\n      if (remaining === 0) {\n        removeAnnouncer(announcer);\n      }\n    },\n  };\n}\n\n/**\n * The next generation, derived from the document rather than a module counter:\n * a wiped element leaves no node to read, so the value is kept on `<html>`.\n */\nfunction currentGeneration(): number {\n  return Number(document.documentElement.getAttribute(GENERATION_ATTR));\n}\n\nfunction nextGeneration(): number {\n  const root = document.documentElement;\n  const next = currentGeneration() + 1;\n\n  root.setAttribute(GENERATION_ATTR, String(next));\n\n  return next;\n}\n\nfunction getOrCreateAnnouncer(): {\n  element: HTMLElement;\n  generation: number;\n} {\n  const existing = document.querySelector<HTMLElement>(`[${ANNOUNCER_ATTR}]`);\n\n  if (existing) {\n    return {\n      element: existing,\n      generation: Number(existing.getAttribute(GENERATION_ATTR)),\n    };\n  }\n\n  // Creating a FRESH element means no live instance is validly sharing one, so\n  // the ref-count restarts from zero (the caller increments immediately after).\n  // Without this, an element removed out from under live instances — a host\n  // wiping the subtree, or a consumer test whose teardown clears the DOM\n  // without calling every instance's destroy() — would leave a stale positive\n  // count that prevents the new element from ever being torn down (#783).\n  // The generation bump (#1217) lets the wiped element's instances recognize\n  // themselves as stale so their destroy() does not touch this fresh element.\n  const element = document.createElement(\"div\");\n\n  element.setAttribute(\"style\", VISUALLY_HIDDEN);\n  element.setAttribute(\"aria-live\", \"assertive\");\n  element.setAttribute(\"aria-atomic\", \"true\");\n  element.setAttribute(ANNOUNCER_ATTR, \"\");\n  // A fresh element restarts the count at zero (the caller increments straight\n  // after) and takes the next generation, so instances holding the element a\n  // host wiped recognise themselves as stale. Both live on the node, so every\n  // bundle reads the same values.\n  writeCount(element, 0);\n  element.setAttribute(GENERATION_ATTR, String(nextGeneration()));\n\n  // Defensive SSR / pre-`<body>` guard: in some environments (early\n  // injection, deferred-body documents, certain SSR rehydration paths)\n  // `document.body` can be null when the announcer is constructed.\n  // `document.body.prepend(...)` would throw `TypeError: Cannot read\n  // properties of null`, tearing down the consumer's RouterProvider /\n  // NavigationAnnouncer mount. Fallback to `documentElement` keeps the\n  // announcer working for SR users; visual-hidden styling means there is\n  // no visible artifact regardless of mount point.\n  //\n  // TS dom lib types `document.body` as `HTMLElement` (non-null), but\n  // runtime can return null per spec. The `as` cast narrows the type to\n  // include null so the `??` short-circuit is type-safe.\n  ((document.body as HTMLElement | null) ?? document.documentElement).prepend(\n    element,\n  );\n\n  return {\n    element,\n    generation: Number(element.getAttribute(GENERATION_ATTR)),\n  };\n}\n\nfunction removeAnnouncer(element: HTMLElement): void {\n  element.remove();\n}\n\nfunction resolveText(\n  route: State,\n  prefix: string,\n  getCustomText: ((route: State) => string) | undefined,\n  h1: HTMLElement | null,\n): string {\n  if (getCustomText) {\n    try {\n      const customText = getCustomText(route);\n\n      // Mini-sprint E.4 (audit-5 §4.2 #4) — empty-string fallback.\n      // A consumer pattern like\n      //   getAnnouncementText: (route) => myMap[route.name] ?? \"\"\n      // returns `\"\"` for routes outside the map. The subscribe loop\n      // then sees an empty text and silently no-announces — screen\n      // readers stay quiet without any signal to the developer. Treat\n      // a falsy custom result (`\"\"` / `null` / `undefined`) as\n      // \"consumer doesn't have a name for this route\" and fall through\n      // to the default resolution chain (h1 → title → route name).\n      if (customText) {\n        return customText;\n      }\n    } catch (error) {\n      // A throwing consumer callback inside the router's subscribe loop\n      // would tear down sibling listeners — log and fall through to the\n      // built-in resolution chain so the announcer keeps working.\n      console.error(\n        \"[real-router] getAnnouncementText threw; falling back to default resolution.\",\n        error,\n      );\n    }\n  }\n\n  const h1Text = (h1?.textContent ?? \"\").trim();\n  const routeName = route.name.startsWith(INTERNAL_ROUTE_PREFIX)\n    ? \"\"\n    : route.name;\n  const rawText =\n    h1Text || document.title || routeName || globalThis.location.pathname;\n\n  return `${prefix}${rawText}`;\n}\n\nfunction manageFocus(h1: HTMLElement | null): void {\n  if (!h1) {\n    return;\n  }\n\n  if (!h1.hasAttribute(\"tabindex\")) {\n    h1.setAttribute(\"tabindex\", \"-1\");\n  }\n\n  h1.focus({ preventScroll: true });\n}\n","import type { Router, State } from \"@real-router/core\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2072). */\nconst objectCreate = Object.create;\n\nconst DEFAULT_STORAGE_KEY = \"real-router:scroll\";\n\n// Bounded retry budget for resolving a late-mounting scroll container on the\n// restore path. A per-route container (e.g. an `overflow:auto` div rendered\n// only on one route) can be committed to the DOM a few frames after the\n// navigation settles — heavier routes paint later than the subscribe's rAF.\n// ~10 frames (≈160ms at 60fps) comfortably covers a React commit of a large\n// route without being perceptible. See the doc-block on `restorePos`.\nconst RESTORE_RETRY_FRAMES = 10;\n\nconst NOOP_INSTANCE: { destroy: () => void } = Object.freeze({\n  destroy: () => {\n    /* no-op */\n  },\n});\n\nexport type ScrollRestorationMode = \"restore\" | \"top\" | \"native\";\n\nexport interface ScrollRestorationOptions {\n  mode?: ScrollRestorationMode | undefined;\n  anchorScrolling?: boolean | undefined;\n  scrollContainer?: (() => HTMLElement | null) | undefined;\n  /**\n   * Scroll behavior passed to `scrollTo({ behavior })` and\n   * `scrollIntoView({ behavior })`.\n   *\n   * - `\"auto\"` (default) — browser-defined, usually instant.\n   * - `\"instant\"` — explicit instant jump (no animation).\n   * - `\"smooth\"` — animated transition. Note: smooth restore on back/traverse\n   *   can feel disorienting if the user expects to land at the saved position\n   *   immediately. Recommended for `mode: \"top\"` or anchor scroll only.\n   *\n   * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior).\n   */\n  behavior?: ScrollBehavior | undefined;\n  /**\n   * sessionStorage key for persisting saved scroll positions. Default:\n   * `\"real-router:scroll\"`. Override only when multiple independent\n   * `RouterProvider` instances share the same document and you need to\n   * isolate their scroll stores (e.g. micro-frontends, embedded widgets,\n   * or testing). For a single app with one provider the default is fine.\n   */\n  storageKey?: string | undefined;\n}\n\ninterface NavigationContext {\n  direction?: \"forward\" | \"back\" | \"unknown\";\n  navigationType?: \"push\" | \"replace\" | \"traverse\" | \"reload\";\n}\n\nexport function createScrollRestoration(\n  router: Router,\n  options?: ScrollRestorationOptions,\n): { destroy: () => void } {\n  if (typeof globalThis.window === \"undefined\") {\n    return NOOP_INSTANCE;\n  }\n\n  const mode = options?.mode ?? \"restore\";\n\n  // mode \"native\" = utility does nothing. Don't flip history.scrollRestoration,\n  // don't subscribe, don't register pagehide — `history.scrollRestoration`\n  // stays at the browser default (\"auto\") so the browser handles scroll\n  // restore natively. (Note: this is the OPPOSITE of `history.scrollRestoration\n  // === \"manual\"` — utility's \"native\" leaves the DOM property at \"auto\" so\n  // the browser is in charge.)\n  if (mode === \"native\") {\n    return NOOP_INSTANCE;\n  }\n\n  const anchorEnabled = options?.anchorScrolling ?? true;\n  const getContainer = options?.scrollContainer;\n  const behavior: ScrollBehavior = options?.behavior ?? \"auto\";\n  const storageKey = options?.storageKey ?? DEFAULT_STORAGE_KEY;\n\n  // Write-through in-memory cache: parse sessionStorage once per provider\n  // mount, then mutate in-memory. Avoids a JSON.parse + JSON.stringify pair\n  // on every subscribeLeave / pagehide event.\n  let store: Record<string, number> | undefined;\n\n  const loadStore = (): Record<string, number> => {\n    if (store !== undefined) {\n      return store;\n    }\n\n    // ⚑ A PROTOTYPE-LESS record, and here that is the cheap fix rather than the\n    // expensive one (#1852). The key is `${route}:${json}`, so both the\n    // skip-same-value READ and the write below consult a chain under a name the\n    // page never chose; with no chain there is nothing to consult. Core pays for\n    // the same guarantee with `putField` because its bags are read on every\n    // render and V8 keeps a prototype-less object in dictionary mode — this one\n    // is a small per-mount cache read a few times per navigation, so the tax is\n    // expected to be negligible and no primitive needs importing.\n    //\n    // ⚠ \"Expected\", not measured, and the word is chosen: nothing benches this\n    // cache. Its sibling in `putField`'s docblock is the reason to be careful —\n    // \"not measurable\" there turned out to be a statement about the instrument,\n    // not the cost. Measure before repeating the word.\n    //\n    // ⚠ `JSON.parse` DEFINES, so a stored `\"__proto__\"` key arrives as ordinary\n    // data either way; what this closes is the ambient half.\n    try {\n      const raw = sessionStorage.getItem(storageKey);\n      const parsed = raw\n        ? (JSON.parse(raw) as Record<string, number>)\n        : undefined;\n\n      store = Object.assign(\n        objectCreate(null) as Record<string, number>,\n        parsed,\n      );\n    } catch {\n      store = objectCreate(null) as Record<string, number>;\n    }\n\n    return store;\n  };\n\n  const putPos = (key: string, pos: number): void => {\n    try {\n      const cached = loadStore();\n\n      // Skip-same-value: when a route is left at the same scroll position it\n      // already holds in the cache (e.g. tab-switching without scrolling),\n      // both the in-memory write and the JSON.stringify + setItem pair are\n      // no-ops. Eliminates redundant serialization on the navigation hot\n      // path for the common \"click tabs without scrolling\" case.\n      if (cached[key] === pos) {\n        return;\n      }\n\n      cached[key] = pos;\n      sessionStorage.setItem(storageKey, JSON.stringify(cached));\n    } catch {\n      // Ignore quota / security errors.\n    }\n  };\n\n  const prevScrollRestoration = history.scrollRestoration;\n\n  try {\n    history.scrollRestoration = \"manual\";\n  } catch {\n    // Ignore — some embedded contexts may reject the assignment.\n  }\n\n  // Resolve the container lazily on every event so containers mounted AFTER\n  // the provider still get correct scroll handling. Falls back to window when\n  // the getter is absent or returns null (pre-mount).\n  const readPos = (): number => {\n    const element = getContainer?.();\n\n    return element ? element.scrollTop : globalThis.scrollY;\n  };\n\n  const writePos = (top: number): void => {\n    const element = getContainer?.();\n\n    if (element) {\n      element.scrollTo({ top, left: 0, behavior });\n    } else {\n      globalThis.scrollTo({ top, left: 0, behavior });\n    }\n  };\n\n  // Restore path (back / traverse / reload). Unlike `writePos`, this tolerates a\n  // scroll container that both MOUNTS and LAYS OUT a few frames AFTER the\n  // navigation settles.\n  //\n  // The capture-side `readPos` always runs against an already-mounted DOM (the\n  // route being left). On restore the target route — and its container — is\n  // still being committed by the view layer. The subscribe callback schedules a\n  // single rAF; for a heavy route (e.g. a long virtual list) the framework's\n  // commit can land AFTER that frame. Two distinct failures follow, each losing\n  // the saved position (Scenario 6 e2e, reproduced under CI's slower runner):\n  //\n  //   1. Container not mounted yet → `getContainer()` is `null`, the scroll\n  //      silently falls back to `window`, which on a container-only route has\n  //      nothing to scroll.\n  //   2. Container mounted but its content not laid out yet → `scrollHeight`\n  //      is still small, so a single `scrollTo({ top })` clamps short of the\n  //      saved position and never re-applies once layout grows.\n  //\n  // With no `scrollContainer` getter the target is always `window`, present\n  // from the first frame — restore in a single shot (unchanged behaviour). When\n  // a getter is configured we cannot tell \"this route legitimately uses window\"\n  // from \"the container is still mounting\", so re-apply the scroll on every\n  // frame for a bounded budget: window as a fallback while the container is\n  // absent (harmless clamp on container routes), the container itself once it\n  // appears. For instant restores we stop early the moment the position sticks;\n  // smooth restores animate asynchronously, so they run the full budget. The\n  // frame budget is the hard backstop against an unreachable target (saved\n  // position taller than the restored content).\n  let restoreToken = 0;\n\n  const restorePos = (top: number): void => {\n    if (!getContainer) {\n      globalThis.scrollTo({ top, left: 0, behavior });\n\n      return;\n    }\n\n    let frames = 0;\n    // ⚑ A per-restore token, the idea `view-transitions.ts` carries as\n    // `scheduledVT` (#781) and this file already carries as `scrollSettled` on\n    // the capture side (#782). Without it the budget is gated by `destroyed`\n    // alone, so a loop whose target is unreachable — a container that clamps\n    // short and keeps retrying — is still running when the next navigation\n    // lands, and writes the PREVIOUS route's offset onto the current page the\n    // moment that container's layout grows (#1924).\n    const token = (restoreToken += 1);\n\n    const attempt = (): void => {\n      if (destroyed || token !== restoreToken) {\n        return;\n      }\n\n      const element = getContainer();\n\n      if (element) {\n        element.scrollTo({ top, left: 0, behavior });\n\n        // Instant restore landed within rounding tolerance → done; no point\n        // re-applying. Smooth restore never matches synchronously, so let it\n        // ride the budget.\n        if (behavior !== \"smooth\" && Math.abs(element.scrollTop - top) <= 1) {\n          return;\n        }\n      } else {\n        globalThis.scrollTo({ top, left: 0, behavior });\n      }\n\n      if (frames >= RESTORE_RETRY_FRAMES) {\n        return;\n      }\n\n      frames += 1;\n      requestAnimationFrame(attempt);\n    };\n\n    attempt();\n  };\n\n  const scrollToHashOrTop = (route: State): void => {\n    // URL plugin path (#532): `state.context.url.hash` is the source of truth\n    // when one of the URL plugins (browser-plugin / navigation-plugin) is\n    // installed. The value is already DECODED — feeding it through\n    // `decodeURIComponent` again would throw on a bare `%`.\n    const ctxHash = (route.context as { url?: { hash?: string } } | undefined)\n      ?.url?.hash;\n\n    if (ctxHash !== undefined) {\n      if (anchorEnabled && ctxHash.length > 0) {\n        // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars\n        const element = document.getElementById(ctxHash);\n\n        if (element) {\n          element.scrollIntoView({ behavior });\n\n          return;\n        }\n      }\n\n      writePos(0);\n\n      return;\n    }\n\n    // Fallback path: no URL plugin, read the DOM. `location.hash` is\n    // percent-encoded; ids in the DOM are the raw string, so decode for the\n    // match. Fall back to the raw slice if the hash contains a malformed\n    // escape sequence (decodeURIComponent throws on those).\n    const hash = globalThis.location.hash;\n\n    if (anchorEnabled && hash.length > 1) {\n      let id: string;\n\n      try {\n        id = decodeURIComponent(hash.slice(1));\n      } catch {\n        id = hash.slice(1);\n      }\n\n      // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars\n      const element = document.getElementById(id);\n\n      if (element) {\n        element.scrollIntoView({ behavior });\n\n        return;\n      }\n    }\n\n    writePos(0);\n  };\n\n  let destroyed = false;\n  // Capture/effect seam guard (#782). previousRoute's position is captured\n  // synchronously in `subscribe`, but the snap/restore effect runs a frame\n  // later in rAF. Across that window the viewport still shows the route BEFORE\n  // previousRoute, so a second navigation landing in the same frame would\n  // capture that foreign position under previousRoute's key. `scrollSettled` is\n  // false across the window — capture is skipped (previousRoute's own stored\n  // value survives the transit). A real user scroll in this <16ms window is\n  // physically impossible.\n  let scrollSettled = true;\n\n  const unsubscribe = router.subscribe(({ route, previousRoute }) => {\n    const nav = (route.context as { navigation?: NavigationContext })\n      .navigation;\n\n    // Browsers dispatch reload as the initial navigation after refresh, so\n    // previousRoute is undefined and capture is naturally skipped. The\n    // pre-refresh position was already persisted via pagehide. Capture is also\n    // skipped while the scroll is unsettled — a second navigation in the same\n    // frame, before the prior nav's rAF snap (see `scrollSettled`, #782).\n    if (previousRoute && scrollSettled) {\n      putPos(keyOf(previousRoute), readPos());\n    }\n\n    // This navigation's scroll effect is now pending: the viewport position no\n    // longer belongs to `route` until the rAF below runs and settles it.\n    scrollSettled = false;\n\n    requestAnimationFrame(() => {\n      if (destroyed) {\n        return;\n      }\n\n      // Effect running — the position now belongs to `route`, so the next\n      // capture is honest again.\n      scrollSettled = true;\n\n      if (mode === \"top\") {\n        scrollToHashOrTop(route);\n\n        return;\n      }\n\n      // Restore branches (reload, back/traverse) MUST be evaluated before the\n      // replace-skip below. Since #657 lifted `replace` into TransitionMeta, a\n      // history TRAVERSAL (back/forward) under navigation-plugin carries\n      // `transition.replace === true` — a traversal reuses an existing history\n      // entry, which is replace-shaped at the history level. If the replace-skip\n      // ran first it would swallow every back/forward navigation and restore\n      // would never fire (the Scenario 6 e2e regression). Genuine in-place\n      // replaces (`router.navigate({ replace: true })`, navigateToNotFound) are\n      // not traversals and fall through to the skip below.\n      //\n      // Both arms of each check are required: `transition.reload` only fires for\n      // programmatic `router.navigate({reload:true})`. F5 under navigation-plugin\n      // primes `nav.navigationType === \"reload\"` via #531 getActivationType but\n      // leaves opts.reload undefined, so dropping the plugin arm would regress F5\n      // scroll-restore. Browser-plugin's F5 is not covered (no priming, out of\n      // scope).\n      // `?.` on a required field: core commits a foreign State's ABSENT\n      // `transition` rather than fabricating one (#1792 / #1976), and this runs\n      // on whatever `subscribe` hands over. Absent falls through to the plugin\n      // arm, which is the pre-#1976 answer for a state carrying no meta.\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- the required field is genuinely absent on a foreign committed State\n      if (route.transition?.reload || nav?.navigationType === \"reload\") {\n        restorePos(loadStore()[keyOf(route)] ?? 0);\n\n        return;\n      }\n\n      if (nav?.direction === \"back\" || nav?.navigationType === \"traverse\") {\n        restorePos(loadStore()[keyOf(route)] ?? 0);\n\n        return;\n      }\n\n      // Genuine in-place replace (not a traversal) — leave scroll untouched.\n      // `?.` for the same reason as the reload arm above.\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- the required field is genuinely absent on a foreign committed State\n      if (route.transition?.replace || nav?.navigationType === \"replace\") {\n        return;\n      }\n\n      scrollToHashOrTop(route);\n    });\n  });\n\n  const onPageHide = (): void => {\n    const current = router.getState();\n\n    if (current) {\n      putPos(keyOf(current), readPos());\n    }\n  };\n\n  globalThis.addEventListener(\"pagehide\", onPageHide);\n\n  return {\n    destroy: () => {\n      // No `if (destroyed) return` guard: every teardown below is idempotent —\n      // `unsubscribe()` is a `set.delete` in the core EventEmitter, DOM\n      // `removeEventListener` is spec-idempotent, and the history assignment is\n      // a plain re-set. There is no ref-count to protect (unlike\n      // route-announcer's shared announcer element), so a double `destroy()` is\n      // harmless. `destroyed = true` still gates any pending restore rAF / retry.\n      destroyed = true;\n      unsubscribe();\n      globalThis.removeEventListener(\"pagehide\", onPageHide);\n\n      try {\n        history.scrollRestoration = prevScrollRestoration;\n      } catch {\n        // Ignore.\n      }\n    },\n  };\n}\n\n/**\n * Internal cache-key builder for scroll-position storage.\n *\n * **Exported for testing only — not part of the public API** (intentionally\n * excluded from `index.ts` barrel). Adapter property tests import it rather\n * than replicating it (§8b H20 / audit-2026-05-16 #S3): a replica drifts\n * silently the moment the key changes. A change to the key format loses saved\n * positions across an upgrade, so the test set is the contract.\n *\n * ## Not memoized\n *\n * The key is a string property read, which is cheaper than the `WeakMap`\n * lookup any cache would need — so there is nothing here to cache.\n */\nexport function keyOf(state: State): string {\n  // The key is the LOCATION, and `state.path` is the form core prints it in\n  // (#1923). Deriving it from the bags instead makes this a SECOND place that\n  // has to know how a value prints — and the two domains disagree there: the\n  // URL direction parses `?page=2` into the number `2`, an intent keeps `\"2\"`,\n  // and `packages/core/src/helpers.ts` says comparison is the single place that\n  // knows they describe one location. Reading the printed form asks core rather\n  // than re-deriving it, so a route's query, its `?id` carve-out twin and the\n  // order its params were written in are all already settled here.\n  return state.path;\n}\n","import { getTransitionSource } from \"@real-router/sources\";\n\nimport type { NavigationOptions, Router } from \"@real-router/core\";\n\n/**\n * Router-coordinated scroll spy (#575).\n *\n * On `IntersectionObserver` notifications the utility picks the topmost\n * visible anchor inside the configured scroll container and emits a forced\n * same-route transition with `{ hash, replace: true, force: true, hashChange:\n * true }` through `router.navigate(...)`. The URL plugin\n * (`@real-router/browser-plugin` or `@real-router/navigation-plugin`) updates\n * `state.context.url.hash` so sibling hash-aware `<Link hash>` re-highlights\n * via the standard `createActiveRouteSource` pipeline.\n *\n * **Anti-flicker gates** (RFC §5.2):\n * 1. `getTransitionSource(router).getSnapshot().isTransitioning` — skip emits\n *    while a transition is in-flight (re-entrant lock).\n * 2. `coolingDown` — set on a user-driven hash transition (e.g. `<Link hash>`\n *    click + smooth `scrollIntoView`). Cleared on `scrollend` or after a\n *    500ms safety timeout. Spy's own emits are excluded via the synchronous\n *    `selfEmitting` flag — required so the spy doesn't rate-limit itself.\n *\n * **Self-healing** (RFC §7.3): if the initial URL contains a hash without a\n * matching `id` (e.g. `/page#nonexistent`), the first IO event emitted right\n * after observe()-ing picks the topmost real anchor and corrects the URL.\n *\n * **Hash-only transition pipeline cost** (RFC §5.3): for same-route same-\n * params hash-only navigations, `getTransitionPath` returns empty\n * `toDeactivate` / `toActivate` arrays, so `runGuards` is a no-op. The only\n * work is the URL plugin's `onTransitionSuccess` write and the\n * `getTransitionSource` flip — cheap.\n *\n * **Architecture**: decomposed into 4 private subsystem closure factories\n * (`createUrlPluginDetector`, `createCooldown`, `createDebouncer`,\n * `createObserverPair`). The main `createScrollSpy` wires them together\n * around the shared `silenced` / `destroyed` / `selfEmitting` flags and the\n * `flush()` emit logic. Each subsystem owns its state + cleanup; `destroy()`\n * delegates to each. See section banners below.\n *\n * @returns A `ScrollSpy` handle whose `destroy()` is idempotent.\n */\nexport interface ScrollSpyOptions {\n  /**\n   * CSS selector for anchor candidates. Empty string `\"\"` or `undefined`\n   * disables the spy (returns a NOOP handle). Common values:\n   * `\"[id]\"`, `\"[id]:is(h1,h2,h3)\"`, `\"section[id]\"`.\n   */\n  selector: string;\n\n  /**\n   * `IntersectionObserver` `rootMargin`. Default\n   * `\"-20% 0px -60% 0px\"` — an anchor is considered \"active\" once it crosses\n   * into the top 20 % of the viewport (or scroll container).\n   */\n  rootMargin?: string | undefined;\n\n  /**\n   * Lazy getter for the scrollable container. Consulted at creation and\n   * re-consulted on every reconcile (DOM mutation), so a container that\n   * MOUNTS or CHANGES after the spy is created is honoured: the\n   * `IntersectionObserver` root and `MutationObserver` target — both immutable\n   * once constructed — are rebuilt to match (#780). `null` (or a missing\n   * getter) falls back to the window viewport (`root: null` on the\n   * `IntersectionObserver`).\n   */\n  scrollContainer?: (() => HTMLElement | null) | undefined;\n}\n\nexport interface ScrollSpy {\n  /** Tear down observer + listeners. Idempotent. */\n  destroy: () => void;\n}\n\nconst NOOP_INSTANCE: ScrollSpy = Object.freeze({\n  destroy: () => {\n    /* no-op */\n  },\n});\n\n// Hardcoded internals (RFC §5.1 — promote only with evidence).\nconst RAF_DEBOUNCE_MS = 150;\nconst MUTATION_DEBOUNCE_MS = 250;\nconst COOLDOWN_TIMEOUT_MS = 500;\nconst DEFAULT_ROOT_MARGIN = \"-20% 0px -60% 0px\";\n\n// Local extension type — browser-plugin / navigation-plugin augment\n// `NavigationOptions` with `hash` and `hashChange`, but `shared/dom-utils`\n// is plugin-agnostic and cannot rely on the augmentation. Mirrors the\n// `HashAwareNavigationOptions` pattern in `link-utils.ts`.\ntype HashAwareNavigationOptions = NavigationOptions & {\n  hash?: string;\n  hashChange?: boolean;\n};\n\n// The `url` namespace contract is owned by browser-env: both URL plugins\n// (browser-plugin, navigation-plugin) write `{ hash: string; hashChanged }` on\n// every transition. This is a local mirror (keeps dom-utils independent of\n// browser-env) and must match that canonical shape — `hash` is always present,\n// never a partial slice.\ninterface UrlContextSlice {\n  hash: string;\n  hashChanged: boolean;\n}\n\nconst getUrlContext = (state: {\n  context?: unknown;\n}): UrlContextSlice | undefined =>\n  (state.context as { url?: UrlContextSlice } | undefined)?.url;\n\n// =============================================================================\n// Picker — pure, no state. RFC §5.2 selection rule.\n// =============================================================================\n\n// Pick the anchor closest to the active zone top in viewport coordinates.\n// `entry.rootBounds.top` already reflects `rootMargin` (per W3C IO spec\n// §3.3) — for `rootMargin: \"-20% 0px -60% 0px\"` it returns 20% of root\n// height, for `\"-50% 0px -50% 0px\"` it returns the center, etc. Distance\n// = boundingClientRect.top − zoneTop in viewport pixels: positive = anchor\n// below zone top (just entered), negative = anchor above zone top (body\n// crossing zone from above). We prefer smallest non-negative; fall back to\n// least-negative when no entry has crossed yet.\n// Falls back to zoneTop = 0 when rootBounds is null (cross-origin roots,\n// unit tests). Single pass — handles `Iterable` so flushes can pass\n// `Map.values()` directly without realising the array.\nconst pickTopmost = (\n  entries: Iterable<IntersectionObserverEntry>,\n): IntersectionObserverEntry | null => {\n  let bestPositive: IntersectionObserverEntry | null = null;\n  let bestPositiveDist = Number.POSITIVE_INFINITY;\n  let bestNegative: IntersectionObserverEntry | null = null;\n  let bestNegativeDist = Number.NEGATIVE_INFINITY;\n\n  for (const entry of entries) {\n    if (!entry.isIntersecting) {\n      continue;\n    }\n\n    const zoneTop = entry.rootBounds?.top ?? 0;\n    const distance = entry.boundingClientRect.top - zoneTop;\n\n    if (distance >= 0) {\n      if (distance < bestPositiveDist) {\n        bestPositive = entry;\n        bestPositiveDist = distance;\n      }\n    } else if (distance > bestNegativeDist) {\n      bestNegative = entry;\n      bestNegativeDist = distance;\n    }\n  }\n\n  return bestPositive ?? bestNegative;\n};\n\n// =============================================================================\n// Subsystem: URL plugin detector (RFC §5.5)\n// Calls `onMissing` if `state.context` is published but `url` key is missing\n// (i.e. no URL plugin installed). Either synchronous on start, or deferred\n// via a one-shot `router.subscribe` if the router has not started yet.\n// `silenced` flag itself lives in main scope — detector signals via callback\n// (per Oracle Q1 — `silenced` has multiple unrelated triggers; main scope\n// owns the kill switch).\n// =============================================================================\n\ninterface UrlPluginDetector {\n  destroy: () => void;\n}\n\nconst createUrlPluginDetector = (\n  router: Router,\n  onMissing: () => void,\n): UrlPluginDetector => {\n  let detectionUnsub: (() => void) | null = null;\n\n  const verify = (state: { context?: unknown }): void => {\n    const context = state.context as\n      (Record<string, unknown> & { url?: unknown }) | undefined;\n\n    if (context && context.url === undefined) {\n      console.warn(\n        \"[real-router] scroll-spy: state.context.url is not claimed. \" +\n          \"Spy requires browser-plugin or navigation-plugin. Disabling.\",\n      );\n      onMissing();\n    }\n  };\n\n  const peekState = router.getState();\n\n  if (peekState) {\n    verify(peekState);\n  } else {\n    // Re-entry guard: `router.subscribe` MAY invoke the callback synchronously\n    // from inside `.subscribe(...)` before the function returns. In that case\n    // `detectionUnsub` is still `null` when the callback fires. Without this\n    // boolean, a hypothetical multi-fire would double-warn.\n    let detectionConsumed = false;\n\n    detectionUnsub = router.subscribe(({ route }) => {\n      /* v8 ignore next 3 -- @preserve: the multi-fire is hypothetical (see above) — the real router never invokes a subscriber synchronously twice before unsub; defensive guard, not testable without a contract-violating fake */\n      if (detectionConsumed) {\n        return;\n      }\n\n      detectionConsumed = true;\n      verify(route);\n\n      detectionUnsub?.();\n      detectionUnsub = null;\n    });\n  }\n\n  return {\n    destroy(): void {\n      detectionUnsub?.();\n      detectionUnsub = null;\n    },\n  };\n};\n\n// =============================================================================\n// Subsystem: Cooldown gate (RFC §5.2 — anti-flicker for smooth scrollIntoView)\n// Set on user-driven `<Link hash>` click → smooth scroll. Cleared on\n// `scrollend` (Baseline 2026) or 500ms safety timeout (older Safari).\n// =============================================================================\n\ninterface Cooldown {\n  readonly active: boolean;\n  start: () => void;\n  destroy: () => void;\n}\n\nconst createCooldown = (getContainer: () => HTMLElement | null): Cooldown => {\n  let active = false;\n  let timeout: ReturnType<typeof setTimeout> | null = null;\n  let listenerContainer: HTMLElement | null = null;\n  let listener: (() => void) | null = null;\n\n  const clear = (): void => {\n    if (timeout !== null) {\n      clearTimeout(timeout);\n      timeout = null;\n    }\n\n    if (listener) {\n      const target: EventTarget = listenerContainer ?? globalThis;\n\n      target.removeEventListener(\"scrollend\", listener);\n    }\n\n    listener = null;\n    listenerContainer = null;\n    active = false;\n  };\n\n  return {\n    get active(): boolean {\n      return active;\n    },\n    start(): void {\n      // Reset rather than stack timers if cooldown is already active.\n      clear();\n\n      active = true;\n\n      const lift = (): void => {\n        clear();\n      };\n\n      listener = lift;\n      listenerContainer = getContainer();\n\n      const target: EventTarget = listenerContainer ?? globalThis;\n\n      target.addEventListener(\"scrollend\", lift, { once: true });\n\n      timeout = setTimeout(lift, COOLDOWN_TIMEOUT_MS);\n    },\n    destroy(): void {\n      clear();\n    },\n  };\n};\n\n// =============================================================================\n// Subsystem: rAF + trailing debounce (RFC §5.1)\n// Coalesces a burst of IO events into ≤ 1 callback per debounce window.\n// rAF reduces N setTimeout creations to 1 per animation frame; the trailing\n// 150ms setTimeout waits for the IO stream to quiesce.\n// =============================================================================\n\ninterface Debouncer {\n  schedule: () => void;\n  destroy: () => void;\n}\n\nconst createDebouncer = (\n  callback: () => void,\n  trailingMs: number,\n): Debouncer => {\n  let raf: number | null = null;\n  let timeout: ReturnType<typeof setTimeout> | null = null;\n\n  return {\n    schedule(): void {\n      if (raf !== null) {\n        return;\n      }\n\n      raf = requestAnimationFrame(() => {\n        raf = null;\n\n        if (timeout !== null) {\n          clearTimeout(timeout);\n        }\n\n        timeout = setTimeout(() => {\n          timeout = null;\n          callback();\n        }, trailingMs);\n      });\n    },\n    destroy(): void {\n      if (raf !== null) {\n        cancelAnimationFrame(raf);\n        raf = null;\n      }\n\n      if (timeout !== null) {\n        clearTimeout(timeout);\n        timeout = null;\n      }\n    },\n  };\n};\n\n// =============================================================================\n// Subsystem: Observer pair (IntersectionObserver + MutationObserver)\n// IO + MO genuinely form one subsystem — both write/read `observed` set and\n// `pending` map, and reconcile flow couples them. Per Oracle Q10, splitting\n// would force cross-subsystem references that re-introduce the wiring\n// problem we're trying to solve.\n//\n// Exposes `pending` directly (per Oracle Q4: hiding behind `consume()` adds\n// boilerplate without isolating the shared mutable state — observers write\n// from IO callbacks while main scope reads in `flush()`).\n// =============================================================================\n\ninterface ObserverPair {\n  readonly pending: Map<Element, IntersectionObserverEntry>;\n  /** True when a resolved container has since detached from the DOM (#1216). */\n  isContainerDetached: () => boolean;\n  /** Re-resolve the container + re-observe matches (rebuilds the pair on change). */\n  reconcile: () => void;\n  destroy: () => void;\n}\n\nconst createObserverPair = (\n  selector: string,\n  rootMargin: string,\n  getContainer: () => HTMLElement | null,\n  onIntersection: () => void,\n  onInvalidSelector: () => void,\n  isStopped: () => boolean,\n): ObserverPair => {\n  const observed = new Set<Element>();\n  // Latest IO entry per target — accumulated across batches. IO delivers\n  // entries only for targets whose intersection state CHANGED (W3C IO\n  // §3.2.1), so a fast scroll that lands two callbacks inside the same\n  // debounce window must merge by target, not overwrite. Entries are\n  // dropped from the map when their target leaves the DOM (see `reconcile`)\n  // and on `destroy()`.\n  const pending = new Map<Element, IntersectionObserverEntry>();\n\n  let duplicateIdWarned = false;\n  let mutationTimer: ReturnType<typeof setTimeout> | null = null;\n\n  const handleIntersection: IntersectionObserverCallback = (entries) => {\n    // Defensive: IO callback may fire AFTER `destroy()` if a queued event\n    // was already scheduled by the browser before `disconnect()`. Cheap\n    // belt-and-suspenders.\n    if (isStopped()) {\n      return;\n    }\n\n    for (const entry of entries) {\n      pending.set(entry.target, entry);\n    }\n\n    onIntersection();\n  };\n\n  // Build (and rebuild) the IntersectionObserver for a given root. A root is\n  // immutable once its IO is constructed, so `reconcile` recreates the IO via\n  // this same factory when the resolved container changes (#780) — one\n  // definition, two call sites.\n  const makeIo = (container: HTMLElement | null): IntersectionObserver =>\n    new IntersectionObserver(handleIntersection, {\n      root: container,\n      rootMargin,\n      threshold: 0,\n    });\n\n  // Container the IntersectionObserver root + MutationObserver target are\n  // built with. Both are immutable once the observer is constructed (W3C), so\n  // a `scrollContainer` that resolves to a different element after creation —\n  // most importantly one that MOUNTS after the spy starts (Angular wires the\n  // spy at bootstrap, before any component renders; a docs route's container\n  // mounts on navigation) — is only honoured by rebuilding the pair in\n  // `reconcile`. Tracked here so `reconcile` can compare on every run (#780).\n  let observerContainer = getContainer();\n\n  let io = makeIo(observerContainer);\n\n  const observeMatches = (): void => {\n    const scope = getContainer() ?? document;\n    let candidates: NodeListOf<Element>;\n\n    try {\n      candidates = scope.querySelectorAll(selector);\n    } catch {\n      onInvalidSelector();\n\n      return;\n    }\n\n    const seenIds = new Set<string>();\n\n    for (const element of candidates) {\n      // Detect duplicate ids once (RFC §7.7). The DOM permits duplicate ids\n      // even though it is a markup bug; the spy keeps working but picks the\n      // first one deterministically via the topmost-visible rule.\n      const id = (element as HTMLElement).id;\n\n      if (id && !duplicateIdWarned) {\n        if (seenIds.has(id)) {\n          duplicateIdWarned = true;\n\n          console.warn(\n            `[real-router] scroll-spy: duplicate id \"${id}\" observed. ` +\n              \"Selection picks the topmost visible match deterministically.\",\n          );\n        }\n\n        seenIds.add(id);\n      }\n\n      if (observed.has(element)) {\n        continue;\n      }\n\n      io.observe(element);\n      observed.add(element);\n    }\n  };\n\n  // MutationObserver init — reused when the observer is re-pointed at a new\n  // container in `reconcile`. `childList: true, subtree: true` catches\n  // structural changes; `attributes: true, attributeFilter: [\"id\"]` catches\n  // anchor id renames (typical for client-rendered docs). The MO targets the\n  // scroll container (or document.body for the window viewport).\n  const MUTATION_OBSERVE_INIT: MutationObserverInit = {\n    childList: true,\n    subtree: true,\n    attributes: true,\n    attributeFilter: [\"id\"],\n  };\n\n  // Null-then-assigned — the same forward-reference idiom as `flush` in the\n  // main scope. `reconcile` re-points it on a container change, so it must be\n  // in scope above its assignment; it is non-null by the time any async\n  // mutation callback (or `reconcile`) runs.\n  let mo: MutationObserver | null = null;\n\n  const reconcile = (): void => {\n    // Drop observed elements that left the DOM. Avoids observer holding\n    // strong refs to detached nodes. Also drop their accumulated entry so\n    // stale \"was intersecting\" state for a removed node cannot be picked\n    // by `pickTopmost` after the node is gone.\n    for (const element of observed) {\n      if (element.isConnected) {\n        continue;\n      }\n\n      io.unobserve(element);\n      observed.delete(element);\n      pending.delete(element);\n    }\n\n    // Honour a container that mounted (or changed) after construction (#780).\n    // The IntersectionObserver root and MutationObserver target cannot be\n    // mutated in place, so rebuild the pair under the new container. Clearing\n    // `observed` + `pending` makes the rebuild equivalent to constructing the\n    // spy with this container from the start: `observeMatches` below\n    // re-populates the tracked set from the new container's scope, and the\n    // stale merged snapshot (computed against the old root's geometry) is\n    // dropped — one empty debounce window, acceptable for a rare event.\n    const nextContainer = getContainer();\n\n    if (nextContainer !== observerContainer) {\n      observerContainer = nextContainer;\n\n      io.disconnect();\n      io = makeIo(nextContainer);\n      observed.clear();\n      pending.clear();\n\n      mo?.disconnect();\n      mo?.observe(nextContainer ?? document.body, MUTATION_OBSERVE_INIT);\n    }\n\n    observeMatches();\n  };\n\n  observeMatches();\n\n  mo = new MutationObserver(() => {\n    if (mutationTimer !== null) {\n      clearTimeout(mutationTimer);\n    }\n\n    mutationTimer = setTimeout(() => {\n      mutationTimer = null;\n      reconcile();\n    }, MUTATION_DEBOUNCE_MS);\n  });\n\n  mo.observe(observerContainer ?? document.body, MUTATION_OBSERVE_INIT);\n\n  return {\n    pending,\n    // #1216: the MutationObserver is pointed at the container's OWN subtree, so\n    // the container's removal (a mutation of its PARENT) is invisible — reconcile\n    // never fires on it and a remounted container is never re-observed. Expose a\n    // detach check + reconcile so the router.subscribe callback can re-resolve on\n    // navigation (exactly when route-tied containers mount/die). When\n    // `observerContainer` is null the MO already watches `document.body`, which\n    // sees container mounts directly — so only a resolved-then-detached container\n    // needs this nav-time nudge.\n    isContainerDetached: (): boolean =>\n      observerContainer !== null && !observerContainer.isConnected,\n    reconcile,\n    destroy(): void {\n      io.disconnect();\n      mo.disconnect();\n\n      if (mutationTimer !== null) {\n        clearTimeout(mutationTimer);\n        mutationTimer = null;\n      }\n\n      observed.clear();\n      pending.clear();\n    },\n  };\n};\n\n// =============================================================================\n// Main: compositional wiring\n// =============================================================================\n\nexport function createScrollSpy(\n  router: Router,\n  options: ScrollSpyOptions,\n): ScrollSpy {\n  // SSR guard (RFC §7.5) — return early without warnings.\n  if (typeof document === \"undefined\") {\n    return NOOP_INSTANCE;\n  }\n\n  // Feature-detect IntersectionObserver — no polyfill ships (RFC §4).\n  if (typeof IntersectionObserver === \"undefined\") {\n    return NOOP_INSTANCE;\n  }\n\n  const { selector } = options;\n\n  // Empty selector → disabled. Documented opt-out for conditional enabling\n  // (RFC §5.4 `scrollSpy={{ selector: enable ? \"[id]\" : \"\" }}`).\n  if (!selector) {\n    return NOOP_INSTANCE;\n  }\n\n  const rootMargin = options.rootMargin ?? DEFAULT_ROOT_MARGIN;\n  const getContainer = options.scrollContainer;\n  const resolveContainer = (): HTMLElement | null => getContainer?.() ?? null;\n\n  // Shared lifecycle flags (Oracle Q1 — `silenced` has multiple unrelated\n  // triggers; Oracle Q3 — `selfEmitting` synchronously bracketed around\n  // `router.navigate()` cannot cleanly extract). Kept in main scope.\n  let destroyed = false;\n  let silenced = false;\n  let selfEmitting = false;\n\n  const isStopped = (): boolean => silenced || destroyed;\n\n  // Symmetric late-binding (Oracle Q2): declare `flush` as nullable, wire\n  // debouncer + observers, then assign the real implementation. Reads as\n  // intentional wiring rather than accidental closure capture ordering.\n  // The `flush?.()` call below safely no-ops if a callback somehow fires\n  // before assignment (impossible in practice — IO/debounce are async).\n  let flush: (() => void) | null = null;\n\n  const transitionSource = getTransitionSource(router);\n\n  const detector = createUrlPluginDetector(router, () => {\n    silenced = true;\n  });\n\n  const cooldown = createCooldown(resolveContainer);\n\n  const debouncer = createDebouncer(() => {\n    flush?.();\n  }, RAF_DEBOUNCE_MS);\n\n  const observers = createObserverPair(\n    selector,\n    rootMargin,\n    resolveContainer,\n    () => {\n      debouncer.schedule();\n    },\n    () => {\n      if (silenced) {\n        return;\n      }\n\n      silenced = true;\n\n      console.warn(\n        `[real-router] scroll-spy: invalid selector \"${selector}\". Disabling.`,\n      );\n    },\n    isStopped,\n  );\n\n  flush = (): void => {\n    if (destroyed || silenced) {\n      observers.pending.clear();\n\n      return;\n    }\n\n    // Gate-skipped flushes keep `pendingEntries` populated — the merged\n    // state is still the best-known snapshot, and the next non-gated flush\n    // consumes it. Clearing under a gate would re-introduce the overwrite\n    // bug for any anchor whose intersection state did not change during\n    // the gate window.\n    if (transitionSource.getSnapshot().isTransitioning) {\n      return;\n    }\n\n    if (cooldown.active) {\n      return;\n    }\n\n    // No `if (pending.size === 0) return` fast-path: `pending` is never empty\n    // here via any real path (a real IntersectionObserver always delivers ≥1\n    // entry, so `handleIntersection` populates `pending` before scheduling; the\n    // mutation reconcile that could drop entries runs at MUTATION_DEBOUNCE_MS\n    // 250 > RAF_DEBOUNCE_MS 150, i.e. always AFTER the flush). And an empty map\n    // is already handled identically below — `pickTopmost(∅)` is `null` → the\n    // `if (!picked) return` guard — so the fast-path was both unreachable and\n    // redundant.\n\n    // Successful flush consumes the merged snapshot. We clear so that the\n    // next debounce window starts fresh; an anchor that is still\n    // intersecting will only stay observable if IO emits another event for\n    // it (which it does whenever the anchor's intersection state actually\n    // changes). Skipping the clear here would leak state from one user-\n    // perceived \"scroll stop\" into the next.\n    const picked = pickTopmost(observers.pending.values());\n\n    observers.pending.clear();\n\n    if (!picked) {\n      // No anchor visible / above zone — preserve last hash (RFC §10 #5).\n      return;\n    }\n\n    const newHash = (picked.target as HTMLElement).id;\n\n    if (!newHash) {\n      return;\n    }\n\n    const state = router.getState();\n\n    if (!state) {\n      return;\n    }\n\n    // `getUrlContext` is guaranteed present here (the URL-plugin detector\n    // silences the spy otherwise), so `?.` only satisfies the `| undefined`\n    // slice type. `newHash` is a non-empty id (guarded above), so it can never\n    // equal an absent hash — no `?? \"\"` normalization needed for the compare.\n    const currentHash = getUrlContext(state)?.hash;\n\n    if (newHash === currentHash) {\n      return;\n    }\n\n    // Emit the same-route same-params hash-only transition. URL plugin\n    // writes `state.context.url.hash = newHash` + `hashChanged = true` in\n    // its `onTransitionSuccess` claim.\n    const opts: HashAwareNavigationOptions = {\n      hash: newHash,\n      replace: true,\n      force: true,\n      hashChange: true,\n    };\n\n    // Self-emit guard (RFC §5.2): set synchronously around our own\n    // `router.navigate()` so the `router.subscribe` callback skips the\n    // cooldown setup for spy-emitted transitions — otherwise spy would\n    // rate-limit itself to ≤ 2 emits/s, contradicting the ≤ 10/s benchmark\n    // target. Test coupling (Q8): preserve exact `.catch(noop).finally(reset)`\n    // chain — migrating to `try/finally` over `await router.navigate(...)`\n    // changes microtask schedule and breaks \"spy continues after rejection\".\n    selfEmitting = true;\n    router\n      // Both channels of the CURRENT state, not just the path bag (RFC-4 M2 /\n      // #1548): this is a same-route re-navigation to move the hash, so\n      // whatever query the user is already looking at has to survive it.\n      // Passing `undefined` in slot 3 dropped it — scrolling a page at\n      // `/docs?tab=api` silently rewrote the URL to `/docs`. Options stay at\n      // slot 4.\n      .navigate(state.name, state.params, state.search, opts)\n      .catch(() => {\n        // Fire-and-forget — suppress expected rejections (concurrent\n        // navigate, router stopped, etc.) consistent with `<Link>` adapter\n        // patterns.\n      })\n      .finally(() => {\n        selfEmitting = false;\n      });\n  };\n\n  // Cooldown setup on user-driven hash transitions. Spy's own emits are\n  // distinguished via the synchronous `selfEmitting` flag (see `flush`).\n  const unsubscribeRouter = router.subscribe(({ route }) => {\n    if (selfEmitting) {\n      return;\n    }\n\n    // #1216: a route-tied scroll container may have unmounted since the last\n    // navigation. The container-scoped MutationObserver can't observe its own\n    // removal (a mutation of its parent), so re-resolve + re-observe here —\n    // navigation is exactly when such containers mount / die.\n    if (observers.isContainerDetached()) {\n      observers.reconcile();\n    }\n\n    if (getUrlContext(route)?.hashChanged) {\n      cooldown.start();\n    }\n  });\n\n  return {\n    destroy(): void {\n      // No `if (destroyed) return` idempotency guard: every subsystem teardown\n      // below is itself idempotent (null-guarded timers, `set.delete` on the\n      // router unsubscribe, spec-idempotent `IntersectionObserver.disconnect`),\n      // and `destroy()` is not a hot path — so a redundant guard would only add\n      // an unreachable branch. `destroyed = true` is still set to gate any\n      // late-arriving IO/router callback via `isStopped()`.\n      destroyed = true;\n\n      // Unsubscribe FIRST to prevent late-arriving router transition\n      // callback from calling `cooldown.start()` on a half-destroyed\n      // instance. Without this ordering, a transition with `hashChanged:\n      // true` firing between subsystem teardown and `unsubscribeRouter()`\n      // would re-install a 500ms timer that survives `destroy()`. Verified\n      // via Oracle review (Q5/Q7).\n      unsubscribeRouter();\n\n      observers.destroy();\n      debouncer.destroy();\n      cooldown.destroy();\n      detector.destroy();\n    },\n  };\n}\n","import type { Router } from \"@real-router/core\";\n\nexport interface ViewTransitions {\n  destroy: () => void;\n}\n\nconst NOOP_INSTANCE: ViewTransitions = Object.freeze({\n  destroy: () => {\n    /* no-op */\n  },\n});\n\nexport function createViewTransitions(router: Router): ViewTransitions {\n  if (\n    typeof document === \"undefined\" ||\n    typeof document.startViewTransition !== \"function\"\n  ) {\n    return NOOP_INSTANCE;\n  }\n\n  let closeVT: (() => void) | null = null;\n  let currentVT: { skipTransition?: () => void } | null = null;\n  // Tracks whether TRANSITION_SUCCESS fired for the current leave. Used to\n  // distinguish \"benign cleanup abort\" (router's async path aborts its own\n  // controller in a finally block after successful navigation) from \"real\n  // cancellation\" (concurrent navigate, guard rejection, dispose).\n  let successFired = false;\n\n  const resolveAndClear = (): void => {\n    closeVT?.();\n    closeVT = null;\n  };\n\n  const offLeave = router.subscribeLeave(({ signal }) => {\n    // Reentrant abort: signal already aborted when we're called. Open no VT\n    // — router will fall through to TRANSITION_CANCELLED via isCurrentNav()\n    // after leave resolves. addEventListener(\"abort\", ...) does not re-fire\n    // for past events, so skipping startViewTransition is the safe path.\n    if (signal.aborted) {\n      return;\n    }\n\n    successFired = false;\n    resolveAndClear();\n\n    // Return a Promise so the router awaits until the browser invokes\n    // updateCallback. This ensures old DOM snapshot is captured BEFORE the\n    // router commits the new state — giving correct exit→state→entry\n    // ordering (vs fire-and-forget, where URL changes before VT captures).\n    return new Promise<void>((resolveLeave) => {\n      // Capture the resolver synchronously BEFORE startViewTransition() is\n      // called. The browser invokes updateCallback in a later task, but\n      // router.subscribe (TRANSITION_SUCCESS) can fire before that. If we\n      // captured `resolve` inside the callback, subscribe would see closeVT\n      // still null and skip resolving — the deferred would hang for 4s\n      // until the VT API aborts with TimeoutError.\n      // eslint-disable-next-line unicorn/prefer-promise-with-resolvers -- frozen shared primitive; resolver captured synchronously before startViewTransition() by design (see comment above)\n      const deferred = new Promise<void>((resolve) => {\n        closeVT = resolve;\n      });\n\n      signal.addEventListener(\n        \"abort\",\n        () => {\n          if (successFired) {\n            // Router's async path (#finishAsyncNavigation) aborts its own\n            // controller in a finally block AFTER completeTransition (and\n            // thus AFTER subscribe fired). This is cleanup, not\n            // cancellation — VT is progressing normally, do nothing.\n            return;\n          }\n\n          // Real cancellation (concurrent navigate, dispose). Resolve the\n          // deferred so updateCallback can complete, skip the VT so no\n          // stale animation leaks, and unblock the router if the abort\n          // fires before updateCallback was invoked.\n          resolveAndClear();\n          currentVT?.skipTransition?.();\n          resolveLeave();\n        },\n        { once: true },\n      );\n\n      try {\n        currentVT = document.startViewTransition(() => {\n          // Resolving here unblocks the router at the moment the browser\n          // enters updateCallback — by spec, old DOM snapshot is captured\n          // before this callback runs. Router now proceeds through\n          // activation guards and setState; the VT animation waits on\n          // `deferred`, which is resolved from router.subscribe after a\n          // task-queue tick (see NOTE on setTimeout below).\n          resolveLeave();\n\n          return deferred;\n        });\n      } catch {\n        // Defensive: spec says startViewTransition doesn't throw under\n        // normal conditions, but Chromium has had edge cases (detached\n        // document, extension interference). Clean up and unblock router.\n        resolveAndClear();\n        resolveLeave();\n      }\n    });\n  });\n\n  const offSuccess = router.subscribe(() => {\n    const resolver = closeVT;\n\n    successFired = true;\n    closeVT = null;\n\n    if (resolver === null) {\n      currentVT = null;\n    } else {\n      // The VT this resolver belongs to. If the next navigation opens a new VT\n      // in the task-queue window before this setTimeout runs, the stale\n      // resolver must NOT null the new `currentVT` — otherwise a later\n      // cancellation reads `null` and skips nothing, leaking a stale animation\n      // (#781). Same identity-guard technique the navigation-plugin uses to\n      // heal its seams.\n      const scheduledVT = currentVT;\n\n      // CRITICAL: CANNOT use requestAnimationFrame here. When the router\n      // takes the async path (leave returned a Promise), subscribe fires\n      // AFTER the browser has already transitioned VT into the\n      // \"update-callback-called\" phase. In that phase Chromium sets\n      // rendering suppression to true, which ALSO blocks rAF callbacks.\n      // rAF would never fire → deferred never resolves → browser aborts\n      // vt.ready with TimeoutError after 4s (observed in Chromium).\n      //\n      // setTimeout runs on the task queue independent of the rendering\n      // pipeline, so it fires regardless of suppression. React's scheduler\n      // uses MessageChannel tasks, which are queued before our setTimeout,\n      // so the new DOM is committed by the time our callback runs.\n      setTimeout(() => {\n        resolver();\n\n        if (currentVT === scheduledVT) {\n          currentVT = null;\n        }\n      }, 0);\n    }\n  });\n\n  return {\n    destroy: () => {\n      offLeave();\n      offSuccess();\n      currentVT?.skipTransition?.();\n      currentVT = null;\n      resolveAndClear();\n    },\n  };\n}\n","import { getPluginApi } from \"@real-router/core/api\";\nimport { putField } from \"@real-router/core/utils\";\n\nimport type {\n  NavigationOptions,\n  NavigationTarget,\n  Params,\n  Router,\n  SearchParams,\n  State,\n} from \"@real-router/core\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — they answer \"what is on this object\" for a value this module\n * did not build. Read off the live global they can be re-pointed after boot, and\n * `shared/` is the half where that fails OPEN: measured in `browser-env`, a\n * re-pointed `getPrototypeOf` admits a `Date` into `state.params` and a\n * re-pointed `keys` skips option validation entirely.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n/**\n * The brand `packages/core/src/internals.ts` puts on every router it registers,\n * RE-DECLARED rather than imported (#2294).\n *\n * ⚑ `Symbol.for` makes the two the same symbol, so **the STRING is the\n * contract** — change it in one place and this warning silently stops firing.\n * The same shape core already uses for `CONFIG_FAULT`, declared twice across a\n * boundary it cannot import over, and pinned the same way: the guard is\n * `foreign-router-href-2294.test.ts`, whose cells fail on exactly that drift.\n */\nconst ROUTER_BRAND = Symbol.for(\"real-router.router\");\n\n/**\n * Is this a real router that core cannot read — as opposed to the `Router`-SHAPED\n * double this helper accepts by contract?\n *\n * ⚠ Read defensively: the argument is the caller's object and may be a `Proxy`\n * whose `get` trap throws. A diagnostic that throws would change where the error\n * comes FROM, which is the #1572 class, so a throwing read means \"cannot tell\".\n */\nfunction isUnreadableRouter(candidate: unknown): boolean {\n  try {\n    return (\n      (candidate as Record<symbol, unknown> | null | undefined)?.[\n        ROUTER_BRAND\n      ] === true\n    );\n  } catch {\n    return false;\n  }\n}\n\n/**\n * The registry lookup, ALONE — and standing alone is what makes the warning\n * correct (#2294).\n *\n * ⚑ Inside the resolve-and-print `try` this could not be told apart from a\n * `forwardState` that threw for a reason the arm handles deliberately. The\n * channel guard (#1572) is exactly that, and it fires on a router that IS\n * registered and IS branded — so a check made there would report duplicated core\n * on a perfectly healthy one.\n */\nfunction readPluginApi(\n  router: Router,\n  routeName: string,\n): ReturnType<typeof getPluginApi> | undefined {\n  try {\n    return getPluginApi(router);\n  } catch {\n    if (isUnreadableRouter(router)) {\n      console.error(\n        `[real-router] Route \"${routeName}\" rendered its LITERAL path: this IS a router, ` +\n          \"but not one this copy of @real-router/core built, so `forwardTo` was not resolved. \" +\n          \"It is either wrapped in a Proxy (Vue `reactive()` / Pinia — store it with `markRaw`), \" +\n          \"or your dependency tree holds two copies of @real-router/core — dedupe it to one.\",\n      );\n    }\n\n    return undefined;\n  }\n}\n\n/**\n * Resolved navigation channels for a `<Link>` — the single `{ name, params,\n * search }` shape every adapter feeds into `buildHref` / `navigateWithHash` /\n * the active-route source, regardless of which prop form the consumer used.\n */\nexport interface ResolvedLinkTarget {\n  name: string;\n  params: Params | undefined;\n  search: SearchParams | undefined;\n}\n\n/**\n * Collapses a `<Link>`'s two prop forms (RFC-4 M2 B2, #1548) into one channel\n * triple:\n *\n * - **Descriptor** — `to={{ name, params?, search? }}` (a `NavigationTarget`).\n * - **Channel props** — `routeName` + `routeParams?` + `routeSearch?`.\n *\n * The forms are mutually exclusive: the TS union on each adapter's `LinkProps`\n * rejects mixing them at compile time, and this helper is the runtime backstop —\n * when `to` is present it **wins**, and a `dev`-visible `console.warn` fires if\n * channel props were also supplied (a JS consumer, an object spread, or an\n * adapter without a strict union can still slip both through). `routeOptions` /\n * `hash` are separate props under BOTH forms (hash is not part of\n * `NavigationTarget` — #532), so they are resolved by the caller, not here.\n */\nexport function resolveLinkTarget(\n  to: NavigationTarget | undefined,\n  routeName: string,\n  routeParams: Params | undefined,\n  routeSearch: SearchParams | undefined,\n): ResolvedLinkTarget {\n  if (to !== undefined) {\n    if (\n      routeName !== \"\" ||\n      routeParams !== undefined ||\n      routeSearch !== undefined\n    ) {\n      console.warn(\n        \"[real-router] <Link> received both `to` and channel props \" +\n          \"(routeName / routeParams / routeSearch). `to` wins; the channel \" +\n          \"props are ignored. Use one form or the other.\",\n      );\n    }\n\n    return { name: to.name, params: to.params, search: to.search };\n  }\n\n  return { name: routeName, params: routeParams, search: routeSearch };\n}\n\nexport function shouldNavigate(evt: MouseEvent): boolean {\n  return (\n    evt.button === 0 &&\n    !evt.metaKey &&\n    !evt.altKey &&\n    !evt.ctrlKey &&\n    !evt.shiftKey\n  );\n}\n\n/**\n * Does an anchor's `target` send this navigation somewhere the router cannot\n * follow? (#1834)\n *\n * `target` names the browsing context the author wants the URL loaded into.\n * Three values are the router's — absent, empty, `_self` — and every other one\n * goes to the browser, the only thing that can resolve a context name.\n * Intercepting instead is how a `<Link target=\"_blank\">` ends up reloading the\n * same tab rather than opening a new one. React Router's\n * `shouldProcessLinkClick` and TanStack Router's `handleClick` split the same\n * way; neither reproduces browsing contexts inside the router, and neither does\n * this.\n *\n * ⚠ The split is by SPELLING, not by where the value resolves to, and three\n * spellings resolve back to this context anyway: `_parent` and `_top` fall back\n * to `_self` in a document with no ancestor, and `_SELF` matches `_self`\n * ASCII-case-insensitively (MDN, `<a>` § target). All three are handed to the\n * browser, which reaches the right destination by a full page load instead of a\n * transition. Resolving them properly means reproducing frame ancestry and\n * keyword folding here; both reference routers decline, and the cost is a page\n * load rather than a wrong destination.\n *\n * ⚠ Ask this only about an `<a>`. On a `<button v-link>` or a `<div use:link>`\n * the attribute is inert markup the browser will not act on, so deferring there\n * would leave the activation unhandled by anyone. Callers that cannot assume an\n * anchor ask {@link anchorTargetsAnotherContext} instead, which narrows first;\n * `target-predicate-authority-1834` owns which call site is in which set.\n */\nexport function targetsAnotherContext(\n  target: string | null | undefined,\n): boolean {\n  return Boolean(target) && target !== \"_self\";\n}\n\n/**\n * The same question, asked about an ELEMENT — for the `use:link` / `v-link`\n * forms, which attach to whatever element the consumer wrote and so cannot\n * assume an anchor (#1834). The `<Link>` components pass a value instead\n * (they render the anchor), and so does Angular's directive (its selector is\n * `a[realLink]`).\n *\n * ⚠ `tagName`, never `instanceof HTMLAnchorElement`, for the reason\n * `applyLinkA11y` sets out below: the constructor belongs to the realm this\n * module loaded in, so a real anchor from an iframe `contentDocument` or a\n * micro-frontend fails the check. Failing it HERE re-intercepts the very\n * `target=\"_blank\"` click this predicate exists to leave alone.\n *\n * Anything that is not an HTML anchor answers `false`: `target` is\n * anchor-specific markup the browser will not act on, so a `<button use:link>`\n * or a `<div v-link>` carrying one must still navigate in-app.\n */\nexport function anchorTargetsAnotherContext(\n  element: Element | null | undefined,\n): boolean {\n  if (element?.tagName !== \"A\") {\n    return false;\n  }\n\n  return targetsAnotherContext(element.getAttribute(\"target\"));\n}\n\n/**\n * RFC 3986 fragment encoding: preserve sub-delims (`&`, `=`, `?`, `:`),\n * encode space, `%`, control chars, non-ASCII via encodeURI; defensively\n * escape `#` (encodeURI does not). Kept BYTE-FOR-BYTE identical to\n * `encodeHashFragment` in `shared/browser-env/url-context.ts` — duplicated\n * because the shared/dom-utils symlink graph does not reach shared/browser-env;\n * a sync test (`link-utils` functional suite) asserts the two stay identical.\n *\n * **STRICTLY-DECODED contract (#1211 / D1=A).** The `<Link hash>` value is a\n * DECODED fragment (no leading `#`) and is encoded verbatim. This OVERTURNS\n * audit-2026-05-17 §5 E.1 — the earlier percent-escape probe (decode + re-encode\n * for copy-from-`location.hash` tolerance) is REMOVED, so both the adapter and\n * the plugin layer obey one contract: `<Link hash=\"a%20b\">` renders `#a%2520b`\n * (the literal fragment `a%20b`) under every runtime. Consumers who want the\n * fragment `a b` pass `hash=\"a b\"`; passing raw `location.hash` (percent-encoded)\n * is no longer supported — it was the source of the plugin↔adapter divergence.\n */\nfunction encodeFragmentInline(decoded: string): string {\n  return encodeURI(decoded).replaceAll(\"#\", \"%23\");\n}\n\ntype BuildUrlFn = (\n  name: string,\n  params: Params,\n  search?: SearchParams,\n  options?: { hash?: string },\n) => string | undefined;\n\n/**\n * Builds an href for a `<Link>` element.\n *\n * - Prefers the URL plugin's `buildUrl` (browser-plugin, navigation-plugin,\n *   hash-plugin) when present.\n * - Falls back to the core's resolving door for runtimes without a URL plugin\n *   (memory-plugin, console UIs, NativeScript). In that fallback the hash\n *   is appended manually so the rendered href is still correct.\n * - The optional 4th argument is the decoded hash fragment (no leading \"#\";\n *   `<Link hash=\"#section\">` is accepted defensively — leading \"#\" stripped),\n *   passed positionally to mirror `navigateWithHash(router, name, params, hash)`\n *   (#1442). Previous 3-arg call sites continue to work unchanged.\n */\nexport function buildHref(\n  router: Router,\n  routeName: string,\n  routeParams: Params,\n  routeSearch?: SearchParams,\n  hash?: string,\n): string | undefined {\n  try {\n    let normHash: string | undefined;\n\n    if (hash !== undefined) {\n      normHash = hash.startsWith(\"#\") ? hash.slice(1) : hash;\n    }\n\n    const buildUrl = router.buildUrl as BuildUrlFn | undefined;\n\n    if (buildUrl) {\n      const url = buildUrl(\n        routeName,\n        routeParams,\n        // Query channel at position 3 (RFC-4 M2 / #1548) — from the `routeSearch`\n        // prop; hash options at position 4. `undefined` when the link has no\n        // `routeSearch` (its query, if any, still rides in routeParams).\n        routeSearch,\n        normHash === undefined ? undefined : { hash: normHash },\n      );\n\n      // Accept only non-empty strings. The BuildUrlFn type contract is\n      // `string | undefined`, but defensive against:\n      //   - `\"\"` (empty string) → would render `<a href=\"\">`, which resolves\n      //     to the current page URL → silent self-navigation on click.\n      //   - `null` (type-contract violation) → would render `<a href={null}>`,\n      //     stringified to `\"null\"` in some renderers.\n      // Either case falls through to the `router.buildPath` fallback below.\n      if (typeof url === \"string\" && url.length > 0) {\n        return url;\n      }\n    }\n\n    // ⚑ RESOLVE, then print (#2250). An href is a promise about where the click\n    // lands, and the click resolves `forwardTo`. `router.buildPath` alone is\n    // class LITERAL by record and answers about the route it was NAMED —\n    // INVARIANTS #8 keeps that so a plugin can build a state for an alias\n    // without being teleported off it — so the chain is resolved FIRST and the\n    // printer prints the target. The door choice belongs here rather than one\n    // layer down.\n    //\n    // ⚠ **The `??` is not reached by a name the table does not hold, and the\n    // claim that it was described a door this arm stopped calling.** That read\n    // `buildNavigationState`, which answers `undefined` for an unknown route;\n    // since #2265 the resolving door is `forwardState`, which ANSWERS for one\n    // (measured — it does not validate the name), leaving the printer to throw.\n    // `packages/react/INVARIANTS.md` row 3 still holds — `Both throw →\n    // undefined + console.error` — but it is reached by both printers throwing,\n    // not by a `undefined` the `??` absorbs. What the `??` IS load-bearing for\n    // is the two cases below.\n    //\n    // ⚠ **The fallback is also where the channel guard lands (#1572).** A route's\n    // declared query name handed in the PATH bag makes the class-① door throw\n    // where `buildPath` answers, so the `??` prints the literal path — the same\n    // href this arm has always rendered. Under a URL plugin the guard is not\n    // swallowed: `router.buildUrl` throws and the outer `catch` drops the href.\n    //\n    // ⚠ **The inner `try` keeps this helper's STRUCTURAL contract.** `buildHref`\n    // is handed a `Router`-shaped object, not necessarily a registered one, and\n    // `getPluginApi` resolves it through `getInternals` — a WeakMap keyed on\n    // identity, which REFUSES a test double or a `Proxy` wrapper. Such a router\n    // keeps the literal path; the stub-router CONTROL in\n    // `packages/react/tests/functional/dom-utils/forwarding-link-href-2250.test.ts`\n    // owns that.\n    let resolved: string | undefined;\n    const api = readPluginApi(router, routeName);\n\n    try {\n      // ⚠ **`forwardState`, not `buildNavigationState`.** Both resolve the whole\n      // chain, and the href is identical — but the committing door opts into\n      // `reportUndeclaredParamKey`, and an href COMMITS NOTHING. That diagnostic\n      // is for a state you are about to persist, which is why `canNavigateTo`\n      // is silent despite sharing `navigate`'s form (#2248 / #1581).\n      // ⚑ **`buildPathResolved`, not `router.buildPath` — ONE href is ONE pass\n      // of the chain (#2260).** The facade's printer runs the `forwardState`\n      // seam a door lower (#2087), which is right for a caller holding a raw\n      // intent and a SECOND pass for this one, which just resolved. Counted,\n      // because nothing in either door's source says \"twice\": the second pass\n      // is what two individually-correct doors compose to.\n      //\n      // ⚠ A plugin author registers one interceptor and has no reason to expect\n      // two invocations per href — a stateful one double-counts. Cost is the\n      // lesser half: +1333 ns per href with `search-schema` +\n      // `persistent-params` installed, per `<Link>` per render.\n      //\n      // ⚠ `api?.` is a TYPE gate, not a runtime guard, and naming it so keeps it\n      // from being read as one: calling `forwardState` on `undefined` throws\n      // into the same `catch` for the same outcome, so a mutant dropping the\n      // check is EQUIVALENT. What it buys is that TypeScript can see the call is\n      // safe below a lookup that may have failed (#2294).\n      const forwarded = api?.forwardState(routeName, routeParams, routeSearch);\n\n      resolved =\n        forwarded &&\n        api?.buildPathResolved(\n          forwarded.name,\n          forwarded.params,\n          forwarded.search,\n        );\n    } catch {\n      resolved = undefined;\n    }\n\n    const path =\n      resolved ?? router.buildPath(routeName, routeParams, routeSearch);\n\n    // Symmetric to the buildUrl guard above (#S1 audit, Invariant 12).\n    // `router.buildPath` is typed `string`, but defends against:\n    //   - `\"\"` (empty string) — would render `<a href=\"\">`, which resolves\n    //     to the current page URL → silent self-navigation on click.\n    //   - non-string type-contract violations from custom path-matchers.\n    // Both yield `undefined` (renderer drops the attribute) with a warning.\n    if (typeof path !== \"string\" || path.length === 0) {\n      console.error(\n        `[real-router] Route \"${routeName}\" yielded an empty path. The element will render without an href attribute.`,\n      );\n\n      return undefined;\n    }\n\n    return normHash ? `${path}#${encodeFragmentInline(normHash)}` : path;\n  } catch {\n    console.error(\n      `[real-router] Route \"${routeName}\" is not defined. The element will render without an href attribute.`,\n    );\n\n    return undefined;\n  }\n}\n\n/**\n * Local extended-options type. Adapters that depend only on `@real-router/core`\n * (without a URL plugin) do not see the `NavigationOptions` augmentation that\n * declares `hash` / `hashChange`. Casting to this widened type inside the\n * helper keeps shared/dom-utils self-contained — adapters do not need to\n * augment NavigationOptions themselves to consume `<Link hash>`.\n */\ntype HashAwareNavigationOptions = NavigationOptions & {\n  hash?: string;\n  hashChange?: boolean;\n};\n\n/**\n * `<Link>` click-handler navigation helper (#532).\n *\n * Wraps `router.navigate(name, params, search, opts)` — the query channel took\n * slot 3 in RFC-4 M2 (#1548) — with same-route different-hash\n * detection: when the consumer clicks a hash-bearing Link that targets the\n * current route with the same params but a different fragment, core's\n * SAME_STATES check would otherwise reject the navigation. The helper adds\n * `force: true` and `hashChange: true` automatically — subscribers can then\n * disambiguate via `state.context.url.hashChanged`.\n *\n * For pure programmatic same-route hash-only navigation, callers are\n * documented to pass `{ force: true }` themselves; the auto-bypass here is\n * a UX convenience for `<Link hash>` that all 6 framework adapters share.\n */\nexport function navigateWithHash(\n  router: Router,\n  routeName: string,\n  routeParams: Params,\n  routeSearch: SearchParams | undefined,\n  hash: string | undefined,\n  extraOptions?: NavigationOptions,\n): Promise<State> {\n  const opts: HashAwareNavigationOptions = { ...extraOptions };\n\n  if (hash !== undefined) {\n    // ⚑ `putField`, not `opts.hash = …` (#2141 / #1852). The spread above produces\n    // no own key for this slot unless the caller's extra options carried one, so\n    // a plain assignment walks the prototype: an ambient accessor an application\n    // or a polyfill put on `Object.prototype` takes the value and the navigation\n    // runs without the fragment it was asked for, or — getter-only — throws.\n    //\n    // ⚠ This dir is shared, so one unguarded write here multiplies by its whole\n    // consumer set — `packages/react` is the coverage and authority owner\n    // (#1838) and owns that count. Its scan classifies COMPUTED-key writes only,\n    // which is why a literal slot like this one sat outside it.\n    putField(opts as unknown as Record<string, unknown>, \"hash\", hash);\n  }\n\n  const current = router.getState();\n\n  // What the navigation gets — the caller's `routeSearch` unless the bypass\n  // below substitutes it (#1925).\n  let navigatedSearch = routeSearch;\n\n  if (current !== undefined) {\n    // ONE expression, asked ONCE and navigated with under the bypass (#1925).\n    // The predicate's answer is only as good as the value it answered ABOUT, so\n    // the two must be the same value — a second copy of this expression would\n    // let them drift apart with nothing to catch it. The name is what keeps\n    // them in agreement structurally rather than by convention.\n    const sameLocationSearch = routeSearch ?? current.search;\n\n    // \"Does this link point at where we already are?\" is a question the router\n    // owns, and asking it here by hand got two things wrong at once (#1555).\n    //\n    // The hand-rolled version compared with `Object.is` per key, so a link\n    // writing `routeSearch={{ page: \"2\" }}` never matched a state parsed from\n    // `?page=2`, where the value is the NUMBER 2 — the bypass silently did not\n    // fire, core rejected the navigation as SAME_STATES, and `<Link hash>`\n    // looked dead. The predicate carries the provenance-tolerant comparison\n    // (#1554) that makes the two forms equal because they print the same URL.\n    //\n    // It also compared the caller's whole `routeParams` bag against\n    // `state.params`, which stopped meaning anything once the channels split\n    // (#1548) — the predicate applies the channel rule instead of re-deriving\n    // it.\n    //\n    // `strictEquality: true` — a link to a PARENT route is a different\n    // location, so the hierarchical arm must not match. `ignoreQueryParams:\n    // false` — the query is part of the location here; ignoring it would fire\n    // the bypass across a real query change and let `force: true` smuggle it\n    // through as a hash change.\n    if (\n      router.isActiveRoute(\n        routeName,\n        routeParams,\n        sameLocationSearch,\n        true,\n        false,\n      )\n    ) {\n      const currentHash =\n        (current.context as { url?: { hash?: string } } | undefined)?.url\n          ?.hash ?? \"\";\n      const newHash = hash ?? currentHash;\n\n      if (currentHash !== newHash) {\n        opts.force = true;\n        opts.hashChange = true;\n\n        // Reaching here means the predicate answered \"this is where we already\n        // are, only the fragment differs\", and `force` + `hashChange` announce\n        // exactly that. The bare `routeSearch` would say \"no query\" rather than\n        // \"unchanged\" — on `/docs?tab=api` a fragment link would land on\n        // `/docs`, moving the location it just announced as unmoved. Same slot,\n        // same conclusion as `scroll-spy.ts`, which re-navigates with both\n        // channels of the CURRENT state.\n        //\n        // ⚠ Only under the bypass. Without it the helper claims no sameness, so\n        // `<Link routeName=\"docs\">` means `/docs` — substituting there would\n        // turn a real navigation into a no-op. An explicit `routeSearch={{}}`\n        // still clears the query, because `{}` is not nullish. Both pinned by\n        // the #1925 suite.\n        navigatedSearch = sameLocationSearch;\n      }\n    }\n  }\n\n  // Query channel at position 3 (RFC-4 M2 / #1548); opts at position 4.\n  return router.navigate(routeName, routeParams, navigatedSearch, opts);\n}\n\n// Match-any-whitespace regex shared across calls. RegExp literals at\n// call-site recompile in some engines; lifting it avoids that microcost\n// for the slow-path branch.\nconst WHITESPACE_PROBE = /\\s/;\nconst WHITESPACE_SPLIT = /\\S+/g;\n\n// `value` is always a truthy class string: both call sites narrow it first\n// (`if (isActive && activeClassName)` / `if (!baseClassName) return …`), so an\n// `if (!value) return []` guard here would be unreachable (#809).\nfunction parseTokens(value: string): string[] {\n  // Hot-path fast-path (audit-2026-05-17 §8b #1): >99% of active-class\n  // inputs at `<Link>` emit are single-token strings like `\"active\"` or\n  // `\"is-current\"` — no whitespace, no leading/trailing pad. Skip the\n  // regex match and Array result allocation: a literal `[value]` works\n  // because the slow-path `match(/\\S+/g)` would return exactly `[value]`\n  // for the same input. PBT lock: linkUtils.properties.ts Invariant 13.\n  if (!WHITESPACE_PROBE.test(value)) {\n    return [value];\n  }\n\n  return value.match(WHITESPACE_SPLIT) ?? [];\n}\n\nexport function buildActiveClassName(\n  isActive: boolean,\n  activeClassName: string | undefined,\n  baseClassName: string | undefined,\n): string | undefined {\n  if (isActive && activeClassName) {\n    const activeTokens = parseTokens(activeClassName);\n\n    if (activeTokens.length === 0) {\n      return baseClassName ?? undefined;\n    }\n    if (!baseClassName) {\n      return activeTokens.join(\" \");\n    }\n\n    const baseTokens = parseTokens(baseClassName);\n    const seen = new Set(baseTokens);\n\n    for (const token of activeTokens) {\n      if (seen.has(token)) {\n        continue;\n      }\n\n      seen.add(token);\n      baseTokens.push(token);\n    }\n\n    return baseTokens.join(\" \");\n  }\n\n  return baseClassName ?? undefined;\n}\n\n/**\n * One-level structural equality using `Object.is` per key.\n *\n * **String-keyed properties only (Mini-sprint E.3 — audit-5 §4.2 #3).**\n * Implementation walks `Object.keys()` which by spec returns only\n * enumerable own STRING keys. Symbol-keyed properties — created via\n * `obj[Symbol(\"brand\")] = value` or `{ [Symbol(...)]: value }` — are\n * NOT compared. Two records that differ only in a Symbol-keyed value\n * will compare as equal.\n *\n * This is intentional: route params and Link options are documented as\n * string-keyed primitives (string | number | boolean) — Symbol-keyed\n * metadata (e.g. brand markers, private state) doesn't belong in a\n * cache-key comparison. Switching to `Reflect.ownKeys()` would extend\n * the contract to symbols at the cost of one extra allocation per call\n * (Reflect.ownKeys composes string-keys + symbol-keys arrays). If a\n * consumer relies on symbol-keyed metadata for navigation\n * disambiguation, they should encode it into a string key instead.\n *\n * Mirrors React's `shallowEqual` (packages/shared/shallowEqual.js) in\n * both the string-keys-only semantics and the `hasOwnProperty` guard\n * below.\n */\nexport function shallowEqual(\n  prev: object | undefined,\n  next: object | undefined,\n): boolean {\n  if (Object.is(prev, next)) {\n    return true;\n  }\n  if (!prev || !next) {\n    return false;\n  }\n\n  const prevKeys = objectKeys(prev);\n  const nextKeys = objectKeys(next);\n\n  if (prevKeys.length !== nextKeys.length) {\n    return false;\n  }\n\n  const prevRecord = prev as Record<string, unknown>;\n  const nextRecord = next as Record<string, unknown>;\n\n  for (const key of prevKeys) {\n    // ⚑ Membership is decided from the LIST the count produced, never from a\n    // second question put to the record (#2064; #1815 settled the same question\n    // for `recordsShallowEqual` in core). `Object.keys` is own AND enumerable\n    // while `hasOwnProperty` is own only — and on a Proxy it is whatever the\n    // `getOwnPropertyDescriptor` trap answers.\n    //\n    // ⚠ `key in next`, `Object.hasOwn` and `propertyIsEnumerable` are the same\n    // family, and none of them is the fix: each leaves a cell of this file's\n    // own suites red. `lint:membership` is the ratchet over the class.\n    //\n    // The second array is free: the count already built it and threw it away.\n    if (\n      !nextKeys.includes(key) ||\n      !Object.is(prevRecord[key], nextRecord[key])\n    ) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nexport function applyLinkA11y(element: HTMLElement | null | undefined): void {\n  if (!element) {\n    return;\n  }\n\n  // Cross-realm safety (audit-2026-05-17 §5 HIGH #4):\n  // `instanceof HTMLAnchorElement` compares against the constructor from\n  // the CURRENT realm. An element created in a different window (iframe\n  // contentDocument, micro-frontend, embedded widget) fails the check\n  // even when it IS a real anchor — the helper would then inject\n  // role=\"link\" + tabindex=\"0\" on top of native anchor semantics,\n  // breaking screen reader output (\"link link\") and focus order.\n  //\n  // tagName is realm-agnostic and is uppercase for HTML-namespaced\n  // elements in any document. SVG `<a>` has lowercase tagName plus a\n  // different prototype (SVGAElement) — skipping it here is wrong by\n  // accident: SVG anchors don't have keyboard activation semantics the\n  // helper would add. But they also don't reach this helper in\n  // practice (router Link components emit HTML anchors). Lock the\n  // uppercase compare to keep the contract narrow.\n  const tag = element.tagName;\n\n  if (tag === \"A\" || tag === \"BUTTON\") {\n    return;\n  }\n  if (!element.hasAttribute(\"role\")) {\n    element.setAttribute(\"role\", \"link\");\n  }\n  if (!element.hasAttribute(\"tabindex\")) {\n    element.setAttribute(\"tabindex\", \"0\");\n  }\n}\n","import { createActiveSource } from \"@real-router/sources\";\nimport { useMemo } from \"preact/hooks\";\n\nimport { useSyncExternalStore } from \"../useSyncExternalStore\";\nimport { useRouter } from \"./useRouter\";\n\nimport type { Params, SearchParams } from \"@real-router/core\";\n\nexport function useIsActiveRoute(\n  routeName: string,\n  params?: Params,\n  search?: SearchParams,\n  strict = false,\n  ignoreQueryParams = true,\n  hash?: string,\n): boolean {\n  const router = useRouter();\n\n  // The fast/slow decision — and the `routeName !== \"\"` guard that keeps\n  // `useIsActiveRoute(\"\")` in sync with `router.isActiveRoute(\"\")` (a misused\n  // empty name matches nothing, #1427) — lives in the shared `createActiveSource`\n  // builder, so the adapters built on it resolve active state identically —\n  // Solid is the exception, its `Link` carrying its own copy of the decision\n  // (#1249 landed the fast path inline here; #1427 folded it into the shared\n  // builder). The `useMemo` wrap skips the branch + `canonicalJson(params)` +\n  // cache lookup on every render when all deps (including the `params`\n  // reference) are stable.\n  const store = useMemo(\n    () =>\n      createActiveSource(\n        router,\n        routeName,\n        params,\n        search,\n        strict,\n        ignoreQueryParams,\n        hash,\n      ),\n    [router, routeName, params, search, strict, ignoreQueryParams, hash],\n  );\n\n  return useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n  );\n}\n","import { memo } from \"preact/compat\";\n\nimport { EMPTY_PARAMS, EMPTY_OPTIONS } from \"../constants\";\nimport {\n  shouldNavigate,\n  targetsAnotherContext,\n  buildHref,\n  buildActiveClassName,\n  navigateWithHash,\n  resolveLinkTarget,\n  shallowEqual,\n} from \"../dom-utils\";\nimport { useIsActiveRoute } from \"../hooks/useIsActiveRoute\";\nimport { useRouter } from \"../hooks/useRouter\";\n\nimport type { LinkProps } from \"../types\";\nimport type { FunctionComponent, TargetedMouseEvent } from \"preact\";\n\n/**\n * Custom comparator for `Link`'s `memo()` wrapper.\n *\n * **Maintenance contract:** every field in `LinkProps` MUST appear in either\n * the `===` chain (primitives + identity-checked references like `onClick` /\n * `children` / `style`) or in the `shallowEqual` arms (object-valued\n * `routeParams` / `routeOptions`). When adding a new prop to `LinkProps`,\n * extend this function in the same PR — `tests/functional/Link.test.tsx`\n * contains a regression-guard that fails the build if `LinkProps` gains a\n * field that is not compared here.\n *\n * **Intentional omissions:** `props` (the rest-spread of HTMLAnchorElement\n * attributes — `aria-label`, `data-*`, `rel`, etc.) is NOT compared. A change\n * to `aria-label` will NOT trigger a Link re-render. This is by design: dynamic `aria-label` is rare; consumers who\n * truly need a reactive aria-label should call `<Link key={ariaLabel}>` to\n * force a remount.\n */\nfunction areLinkPropsEqual(\n  prev: Readonly<LinkProps>,\n  next: Readonly<LinkProps>,\n): boolean {\n  return (\n    prev.routeName === next.routeName &&\n    prev.className === next.className &&\n    prev.activeClassName === next.activeClassName &&\n    prev.activeStrict === next.activeStrict &&\n    prev.ignoreQueryParams === next.ignoreQueryParams &&\n    prev.onClick === next.onClick &&\n    prev.target === next.target &&\n    prev.style === next.style &&\n    prev.children === next.children &&\n    prev.hash === next.hash &&\n    shallowEqual(prev.routeParams, next.routeParams) &&\n    shallowEqual(prev.routeSearch, next.routeSearch) &&\n    shallowEqual(prev.to, next.to) &&\n    shallowEqual(prev.routeOptions, next.routeOptions)\n  );\n}\n\nexport const Link: FunctionComponent<LinkProps> = memo(\n  ({\n    routeName,\n    routeParams,\n    routeSearch,\n    to,\n    routeOptions = EMPTY_OPTIONS,\n    className,\n    activeClassName = \"active\",\n    activeStrict = false,\n    ignoreQueryParams = true,\n    hash,\n    onClick,\n    target,\n    children,\n    ...props\n  }) => {\n    const router = useRouter();\n\n    // Resolve the two prop forms into one channel triple (RFC-4 M2 B2, #1548):\n    // a `to` descriptor supersedes the channel props (dev-warn on conflict).\n    const { name, params, search } = resolveLinkTarget(\n      to,\n      routeName ?? \"\",\n      routeParams,\n      routeSearch,\n    );\n\n    // memo + areLinkPropsEqual guarantees that on bail-out the component does\n    // not render; on render, routeParams/routeOptions changed reference (true\n    // change caught by shallowEqual), so they're safe to use directly in hook\n    // deps without useStableValue.\n\n    // Pass `routeParams` straight through (possibly `undefined`) — do NOT default\n    // to EMPTY_PARAMS before the active-route call. `createActiveRouteSource` keys\n    // `params === undefined` as \"\" but EMPTY_PARAMS ({}) as \"{}\", so a no-params\n    // `<Link>` and a manual `useIsActiveRoute(routeName)` only share ONE cached\n    // source (one router subscription) when both pass `undefined`; defaulting here\n    // would split the same question into a second eager subscription (#776).\n    // `shallowEqual(undefined, undefined)` keeps the memo fast-path unchanged.\n    //\n    // Hash-aware active (#532) — see useIsActiveRoute for the contract.\n    const isActive = useIsActiveRoute(\n      name,\n      params,\n      search,\n      activeStrict,\n      ignoreQueryParams,\n      hash,\n    );\n\n    // Navigation/href building need a concrete params object — default here only.\n    // `search` stays raw (`undefined` when unset).\n    const paramsForNav = params ?? EMPTY_PARAMS;\n\n    // `buildHref` is a cheap synchronous call (route-tree lookup + string\n    // concat). Wrapping it in `useMemo` allocates a deps array on every\n    // render that does not bail out — and on bail-out the function body\n    // doesn't execute, so the cache never pays off. Same logic for\n    // `buildActiveClassName` and `handleClick` below.\n    const href = buildHref(router, name, paramsForNav, search, hash);\n\n    const handleClick = (evt: TargetedMouseEvent<HTMLAnchorElement>): void => {\n      if (onClick) {\n        // Isolate a throwing user handler (#1436): native <a> logs a throwing\n        // click listener and still performs the default action. Without this\n        // the throw escapes before navigateWithHash, silently aborting\n        // navigation. The user's own preventDefault() runs before any throw, so\n        // the defaultPrevented contract below is unchanged. Mirrors vue's #1352.\n        try {\n          onClick(evt);\n        } catch (error) {\n          console.error(\n            \"[real-router] A <Link> onClick handler threw; navigation is unaffected.\",\n            error,\n          );\n        }\n\n        if (evt.defaultPrevented) {\n          return;\n        }\n      }\n\n      if (!shouldNavigate(evt) || targetsAnotherContext(target)) {\n        return;\n      }\n\n      evt.preventDefault();\n      navigateWithHash(\n        router,\n        name,\n        paramsForNav,\n        search,\n        hash,\n        routeOptions,\n      ).catch(() => {});\n    };\n\n    const finalClassName = buildActiveClassName(\n      isActive,\n      activeClassName,\n      className,\n    );\n\n    return (\n      <a\n        {...props}\n        target={target}\n        href={href}\n        className={finalClassName}\n        onClick={handleClick}\n      >\n        {children}\n      </a>\n    );\n  },\n  areLinkPropsEqual,\n);\n\nLink.displayName = \"Link\";\n","import { createDismissableError } from \"@real-router/sources\";\nimport { Fragment } from \"preact\";\nimport { useEffect, useLayoutEffect, useRef } from \"preact/hooks\";\n\nimport { useRouter } from \"../hooks/useRouter\";\nimport { useSyncExternalStore } from \"../useSyncExternalStore\";\n\nimport type { RouterError, State } from \"@real-router/core\";\nimport type { ComponentChildren, VNode } from \"preact\";\n\nexport interface RouterErrorBoundaryProps {\n  readonly children: ComponentChildren;\n  readonly fallback: (\n    error: RouterError,\n    resetError: () => void,\n  ) => ComponentChildren;\n  readonly onError?: (\n    error: RouterError,\n    toRoute: State | null,\n    fromRoute: State | null,\n  ) => void;\n}\n\n/**\n * Declarative navigation-error boundary.\n *\n * **Not** a Preact `componentDidCatch`-style ErrorBoundary — this component\n * does NOT catch render-time exceptions from `children`. It is a compositional\n * component that subscribes to `createDismissableError` from\n * `@real-router/sources` and renders `fallback(error, resetError)` ALONGSIDE\n * `children` (wrapped in a `<Fragment>`) when the router emits a navigation\n * error (guard rejection, ROUTE_NOT_FOUND, etc.). The boundary auto-resets on\n * the next successful navigation; `resetError()` lets the consumer dismiss\n * the fallback imperatively.\n *\n * For real exception boundaries, wrap children in a Preact ErrorBoundary\n * (e.g. `preact-iso/ErrorBoundary` or a custom `componentDidCatch` class) —\n * the two can coexist.\n */\nexport function RouterErrorBoundary({\n  children,\n  fallback,\n  onError,\n}: RouterErrorBoundaryProps): VNode {\n  const router = useRouter();\n\n  // `createDismissableError` is the cached factory from `@real-router/sources`\n  // — keyed per-router, identity stable across renders. `useMemo` would wrap\n  // a call that already memoizes downstream.\n  const store = createDismissableError(router);\n  const snapshot = useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n  );\n\n  const onErrorRef = useRef(onError);\n\n  useLayoutEffect(() => {\n    onErrorRef.current = onError;\n  });\n\n  // snapshot.version is the @real-router/sources dismissable-error invariant:\n  // it is the only field that monotonically advances on each new error episode\n  // (snapshot.error/toRoute/fromRoute are correlated reads within the same\n  // version frame), so depending on it covers all error fields by construction.\n  useEffect(() => {\n    if (snapshot.error) {\n      onErrorRef.current?.(\n        snapshot.error,\n        snapshot.toRoute,\n        snapshot.fromRoute,\n      );\n    }\n    // eslint-disable-next-line @eslint-react/exhaustive-deps -- onError tracked via ref, snapshot fields accessed inside callback\n  }, [snapshot.version]);\n\n  return (\n    <Fragment>\n      {children}\n      {snapshot.error ? fallback(snapshot.error, snapshot.resetError) : null}\n    </Fragment>\n  );\n}\n","import { getPluginApi } from \"@real-router/core/api\";\nimport { getRouteUtils } from \"@real-router/route-utils\";\n\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouteUtils } from \"@real-router/route-utils\";\n\nexport const useRouteUtils = (): RouteUtils => {\n  const router = useRouter();\n\n  return getRouteUtils(getPluginApi(router).getTree());\n};\n","import { getTransitionSource } from \"@real-router/sources\";\n\nimport { useSyncExternalStore } from \"../useSyncExternalStore\";\nimport { useRouter } from \"./useRouter\";\n\nimport type { RouterTransitionSnapshot } from \"@real-router/sources\";\n\nexport function useRouterTransition(): RouterTransitionSnapshot {\n  const router = useRouter();\n  const store = getTransitionSource(router);\n\n  return useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n  );\n}\n","import { guardLeaveListener } from \"@real-router/sources\";\nimport { useEffect, useLayoutEffect, useRef } from \"preact/hooks\";\n\nimport { useRouter } from \"./useRouter\";\n\nimport type { State } from \"@real-router/core\";\n\nexport interface RouteExitContext {\n  /** The route being left. */\n  route: State;\n  /** The route being navigated to. */\n  nextRoute: State;\n  /**\n   * AbortSignal that fires when this navigation is superseded by a later\n   * one (rapid clicks). Already filtered: when the handler runs,\n   * `signal.aborted` is guaranteed to be `false`. Use\n   * `signal.addEventListener(\"abort\", cleanup, { once: true })` for\n   * cleanup that must run on cancellation.\n   */\n  signal: AbortSignal;\n}\n\nexport interface UseRouteExitOptions {\n  /**\n   * Skip the handler when `route.name === nextRoute.name`\n   * (sort/filter/query-only navigations on the same route). Default:\n   * `true`.\n   */\n  skipSameRoute?: boolean;\n}\n\nexport type RouteExitHandler = (\n  context: RouteExitContext,\n) => void | Promise<void>;\n\n/**\n * Subscribe to the router's leave-window with the universal guards baked\n * in. Wraps `router.subscribeLeave` so consumers don't repeat the same\n * boilerplate every time:\n *\n *   - **Reentrant abort pre-check**: if `signal.aborted` is already `true`\n *     when the handler would run (rapid navigation superseded a slower\n *     one), the handler is skipped entirely. `signal.addEventListener(\n *     \"abort\", ...)` does not fire retroactively, so without this guard\n *     downstream cleanup would never trigger.\n *   - **Same-route skip**: by default, `route.name === nextRoute.name`\n *     short-circuits the handler — query-only navigations (sort, filter,\n *     pagination) skip the work. Opt out with `skipSameRoute: false`.\n *   - **Stable handler reference**: the handler can change identity on\n *     every render without causing resubscription — internal ref keeps\n *     the latest handler accessible to the long-lived subscription.\n *\n * Returns nothing — the subscription's lifecycle is bound to the\n * component's mount.\n *\n * If the handler returns a Promise, the router blocks on it. If the\n * Promise resolves, navigation proceeds. If it **rejects**, the router\n * rejects `navigate()` with the handler's **original error** and emits\n * `TRANSITION_ERROR` — it is NOT re-coded to `TRANSITION_CANCELLED`\n * (that arises only when the navigation's `signal` aborts: a superseding\n * navigation, `stop()`, `dispose()`, or an external `opts.signal`).\n *\n * @example Animation\n * ```tsx\n * const ref = useRef<HTMLDivElement>(null);\n *\n * useRouteExit(async ({ signal }) => {\n *   const el = ref.current;\n *   if (!el) return;\n *   el.classList.add(\"fade-out\");\n *   const cleanup = () => el.classList.remove(\"fade-out\");\n *   signal.addEventListener(\"abort\", cleanup, { once: true });\n *   try {\n *     el.getBoundingClientRect();   // style flush\n *     await Promise.allSettled(el.getAnimations().map((a) => a.finished));\n *   } finally {\n *     cleanup();\n *   }\n * });\n * ```\n *\n * @example Auto-save form draft\n * ```tsx\n * useRouteExit(async ({ signal }) => {\n *   if (formState.dirty) await api.saveDraft(formState, { signal });\n * });\n * ```\n *\n * @example Cancel inflight requests\n * ```tsx\n * useRouteExit(() => {\n *   inflightController.abort();\n * });\n * ```\n *\n * @example Library-coordinated exit (motion / framer-motion)\n * ```tsx\n * const exitResolverRef = useRef<(() => void) | null>(null);\n *\n * useRouteExit(({ signal }) => {\n *   return new Promise<void>((resolve) => {\n *     exitResolverRef.current = resolve;\n *     signal.addEventListener(\"abort\", () => resolve(), { once: true });\n *   });\n * });\n *\n * const onExitComplete = () => exitResolverRef.current?.();\n * // pass onExitComplete to <AnimatePresence>\n * ```\n *\n * @example Detecting that you are leaving a subtree\n * ```tsx\n * const inProducts = (name: string) =>\n *   name === \"products\" || name.startsWith(\"products.\");\n *\n * useRouteExit(({ route, nextRoute }) => {\n *   if (inProducts(route.name) && !inProducts(nextRoute.name)) {\n *     // leaving the products subtree entirely — flush product-related caches\n *     productCache.clear();\n *   }\n * });\n * ```\n *\n * ⚠ Do NOT read `nextRoute.transition` here. `nextRoute` is the PENDING target,\n * and the pipeline gives it the neutral default — empty `segments`, optional\n * flags `undefined` — so the example this replaced read `[]` and `undefined`\n * whatever the navigation was. (It threw outright until real-router#1976\n * attached the field.) The real metadata is written at the COMMIT: read it in\n * `router.subscribe`, `onTransitionSuccess`, or `getState()`.\n */\nexport function useRouteExit(\n  handler: RouteExitHandler,\n  options?: UseRouteExitOptions,\n): void {\n  const router = useRouter();\n  const handlerRef = useRef(handler);\n  const skipSameRoute = options?.skipSameRoute ?? true;\n\n  // Keep the latest handler accessible to the subscription without\n  // resubscribing on every render — the subscription registers the\n  // wrapper once and dispatches to whatever ref points to.\n  // useLayoutEffect (synchronous, post-render, pre-paint) updates the\n  // ref before the browser can dispatch any router events that could\n  // observe a stale closure.\n  useLayoutEffect(() => {\n    handlerRef.current = handler;\n  });\n\n  useEffect(() => {\n    // The same-route + reentrant-abort guards and the Promise passthrough\n    // live in the shared listener (@real-router/sources, #1435). The ref-thunk\n    // keeps the latest handler reachable from the long-lived subscription\n    // without resubscribing on every render.\n    return router.subscribeLeave(\n      guardLeaveListener((context) => handlerRef.current(context), {\n        skipSameRoute,\n      }),\n    );\n  }, [router, skipSameRoute]);\n}\n","import { createRouteEnterGate } from \"@real-router/sources\";\nimport { useEffect, useLayoutEffect, useRef, useState } from \"preact/hooks\";\n\nimport { useRoute } from \"./useRoute\";\n\nimport type { State } from \"@real-router/core\";\n\nexport interface RouteEnterContext {\n  /** The route that was just activated. */\n  route: State;\n  /** The route that was active immediately before this navigation. */\n  previousRoute: State;\n}\n\nexport type RouteEnterHandler = (context: RouteEnterContext) => void;\n\nexport interface UseRouteEnterOptions {\n  /**\n   * Skip the handler when `route.name === previousRoute.name`\n   * (sort/filter/query-only navigations on the same route). Default:\n   * `true`. Symmetric with `useRouteExit`'s same-name option.\n   */\n  skipSameRoute?: boolean;\n}\n\n/**\n * Fire `handler` once when the component mounts as a result of a\n * navigation. Mirror of `useRouteExit` for the entry side.\n *\n * What this hook covers that ad-hoc `useEffect` + `useRoute()` doesn't:\n *\n *   - **Skip-initial**: handler is skipped when there is no\n *     `previousRoute` (i.e. first-load mount). Most consumers want to\n *     fire side effects only on real navigations, not on hydration.\n *   - **Same-route skip** (default): handler is skipped when\n *     `route.name === previousRoute.name`. Sort/filter/query-only\n *     navigations re-run the effect (because `route` reference changes\n *     in `useRoute`'s snapshot), but they are not \"entries\" in the\n *     animation / analytics sense — the component instance has stayed\n *     mounted throughout. Opt out with `skipSameRoute: false` when\n *     the handler legitimately needs to fire on every navigation\n *     (e.g. analytics tracking each query-param flip).\n *   - **Latest-handler ref**: the handler can change identity on every\n *     render without re-running the effect — the registered wrapper\n *     dispatches to whatever `handlerRef.current` points to.\n *   - **Mount-time `route` / `previousRoute` snapshot**: the handler\n *     receives the values that were live at the moment of mount, not\n *     the latest ones (which may have moved on if the user navigated\n *     again before the effect drained).\n *\n * Race-safety: `useRoute()` is wired through `useSyncExternalStore` from\n * `@real-router/sources` (Preact polyfill: useState + useEffect, same\n * post-commit semantics), so by the time the new component's effect\n * runs, the snapshot is the post-commit one. This is the reason we can\n * read mount-time context from `useRoute()` instead of subscribing to\n * `router.subscribe` directly (which fires before Preact schedules a\n * re-render — the well-known race in distributed components).\n *\n * Note: Preact does not expose a `StrictMode` equivalent, so the shared\n * gate's dedupe arm (`createRouteEnterGate`, `@real-router/sources`) is a\n * defensive no-op here — kept for parity with React and tested once in\n * sources.\n *\n * @example Direction-aware entry animation\n * ```tsx\n * useRouteEnter(({ route }) => {\n *   const direction = route.context.browser?.direction;\n *   ref.current?.classList.add(\n *     direction === \"back\" ? \"slide-from-left\" : \"slide-from-right\",\n *   );\n * });\n * ```\n *\n * @example Source-aware focus management\n * ```tsx\n * useRouteEnter(({ route }) => {\n *   if (route.context.browser?.source === \"navigate\") {\n *     headingRef.current?.focus();\n *   }\n * });\n * ```\n *\n * @example Analytics page-enter event (skip-initial built-in)\n * ```tsx\n * useRouteEnter(({ route, previousRoute }) => {\n *   analytics.track(\"page_enter\", {\n *     route: route.name,\n *     from: previousRoute.name,\n *   });\n * });\n * ```\n *\n * @example Reading rich transition metadata via `route.transition`\n * ```tsx\n * useRouteEnter(({ route }) => {\n *   // route.transition: TransitionMeta — populated by core for every state\n *   // ⚠ NOT `transition.redirected` — the router never sets it (it only ever\n *   // carries what a caller passed as `{ redirected: true }`), so branching on\n *   // it here is silently dead for a `forwardTo` or a guard redirect.\n *   // `from` needs no check: this hook does not fire without one.\n *   showToast(`Arrived from ${route.transition.from}`);\n *   if (route.transition.segments.activated.includes(\"products\")) {\n *     // products subtree just became active (could be products or\n *     // products.detail). Useful for subtree-scoped side effects.\n *   }\n * });\n * ```\n */\nexport function useRouteEnter(\n  handler: RouteEnterHandler,\n  options?: UseRouteEnterOptions,\n): void {\n  const { route, previousRoute } = useRoute();\n  const handlerRef = useRef(handler);\n  // The canonical enter-guard set + `lastHandledRoute` dedupe live in the\n  // shared gate (@real-router/sources, #1435) — created once via useState's\n  // lazy initializer so it stays stable (a ref write during render is\n  // disallowed; the gate is never re-set, so no re-render is triggered).\n  // Preact has no StrictMode double-invoke, so the dedupe arm is a defensive\n  // no-op here (kept for parity with React), now tested once in sources rather\n  // than v8-ignored per adapter. The gate also owns the `!previousRoute`\n  // guard — the sole defense of the non-nullable\n  // `RouteEnterContext.previousRoute` contract (#1218).\n  const [gate] = useState(() => createRouteEnterGate());\n  const skipSameRoute = options?.skipSameRoute ?? true;\n\n  // Keep the latest handler reference accessible without re-running\n  // the effect. useLayoutEffect (synchronous, post-render, pre-paint)\n  // updates the ref before the effect can read it.\n  useLayoutEffect(() => {\n    handlerRef.current = handler;\n  });\n\n  useEffect(() => {\n    const context = gate(route, previousRoute, skipSameRoute);\n\n    if (context) {\n      handlerRef.current(context);\n    }\n  }, [gate, route, previousRoute, skipSameRoute]);\n}\n","import { getNavigator } from \"@real-router/core\";\nimport { createRouteSource, primeErrorSource } from \"@real-router/sources\";\nimport { useEffect, useMemo } from \"preact/hooks\";\n\nimport { NavigatorContext, RouteContext, RouterContext } from \"./context\";\nimport {\n  createRouteAnnouncer,\n  createScrollRestoration,\n  createScrollSpy,\n  createViewTransitions,\n} from \"./dom-utils\";\nimport { useSyncExternalStore } from \"./useSyncExternalStore\";\n\nimport type {\n  RouteAnnouncerOptions,\n  ScrollRestorationOptions,\n  ScrollSpyOptions,\n} from \"./dom-utils\";\nimport type { Router } from \"@real-router/core\";\nimport type { FunctionComponent, ComponentChildren } from \"preact\";\n\nexport interface RouteProviderProps {\n  router: Router;\n  children: ComponentChildren;\n  announceNavigation?: boolean | RouteAnnouncerOptions;\n  scrollRestoration?: ScrollRestorationOptions;\n  scrollSpy?: ScrollSpyOptions;\n  viewTransitions?: boolean;\n}\n\nexport const RouterProvider: FunctionComponent<RouteProviderProps> = ({\n  router,\n  children,\n  announceNavigation,\n  scrollRestoration,\n  scrollSpy,\n  viewTransitions,\n}) => {\n  // `announceNavigation` accepts `true` (default announcer) or a\n  // `RouteAnnouncerOptions` object (`{ prefix, getAnnouncementText }`) for\n  // custom announcement text. `false` / `undefined` disables it.\n  const announceEnabled =\n    announceNavigation !== undefined && announceNavigation !== false;\n  const announceOptions =\n    typeof announceNavigation === \"object\" ? announceNavigation : undefined;\n  const announcePrefix = announceOptions?.prefix;\n\n  useEffect(() => {\n    if (!announceEnabled) {\n      return;\n    }\n\n    const announcer = createRouteAnnouncer(router, announceOptions);\n\n    return () => {\n      announcer.destroy();\n    };\n    // announceOptions (for getAnnouncementText) omitted — inline-object identity\n    // churn shouldn't re-create the announcer; the callback is captured once by\n    // the utility (same rationale as scrollContainer below).\n    // eslint-disable-next-line @eslint-react/exhaustive-deps\n  }, [router, announceEnabled, announcePrefix]);\n\n  // Primitive deps so inline `{ mode: \"restore\" }` doesn't thrash on every\n  // render. scrollContainer is a getter invoked lazily on every event inside\n  // the utility — swapping its reference doesn't change the resolved element,\n  // so we intentionally omit it from deps to keep inline getters stable.\n  const srMode = scrollRestoration?.mode;\n  const srAnchor = scrollRestoration?.anchorScrolling;\n  const srBehavior = scrollRestoration?.behavior;\n  const srStorageKey = scrollRestoration?.storageKey;\n  const srEnabled = scrollRestoration !== undefined;\n\n  useEffect(() => {\n    if (!srEnabled) {\n      return;\n    }\n\n    const sr = createScrollRestoration(router, {\n      mode: srMode,\n      anchorScrolling: srAnchor,\n      behavior: srBehavior,\n      storageKey: srStorageKey,\n      // srEnabled check above guarantees scrollRestoration is defined.\n      scrollContainer: scrollRestoration.scrollContainer,\n    });\n\n    return () => {\n      sr.destroy();\n    };\n    // scrollRestoration (for scrollContainer) omitted — see comment above.\n    // eslint-disable-next-line @eslint-react/exhaustive-deps\n  }, [router, srEnabled, srMode, srAnchor, srBehavior, srStorageKey]);\n\n  const spySelector = scrollSpy?.selector;\n  const spyRootMargin = scrollSpy?.rootMargin;\n  const spyEnabled =\n    scrollSpy !== undefined && spySelector !== undefined && spySelector !== \"\";\n\n  useEffect(() => {\n    if (!spyEnabled) {\n      return;\n    }\n\n    const spy = createScrollSpy(router, {\n      selector: spySelector,\n      rootMargin: spyRootMargin,\n      scrollContainer: scrollSpy.scrollContainer,\n    });\n\n    return () => {\n      spy.destroy();\n    };\n    // scrollSpy (for scrollContainer) omitted — same rationale as\n    // scrollRestoration above: getter is invoked lazily inside the utility.\n    // eslint-disable-next-line @eslint-react/exhaustive-deps\n  }, [router, spyEnabled, spySelector, spyRootMargin]);\n\n  useEffect(() => {\n    if (!viewTransitions) {\n      return;\n    }\n\n    const vt = createViewTransitions(router);\n\n    return () => {\n      vt.destroy();\n    };\n  }, [router, viewTransitions]);\n\n  // `getNavigator` is cached per-router in `@real-router/core` (WeakMap) —\n  // same router always returns the same Navigator ref. No `useMemo` needed.\n  const navigator = getNavigator(router);\n\n  // `createRouteSource` is NOT cached (per packages/sources/CLAUDE.md table).\n  // It must be stable across renders so `useSyncExternalStore`'s deps don't\n  // change identity and trigger an unsubscribe/resubscribe loop on every\n  // render. `useMemo([router])` gives one source per router-instance lifetime.\n  const store = useMemo(() => createRouteSource(router), [router]);\n\n  // #778 P2: eagerly create the per-router error source so a navigation error\n  // that fires BEFORE a RouterErrorBoundary mounts (a lazy app shell, a failed\n  // boot navigation) is still captured. The boundary's createDismissableError\n  // reuses this cached source and catches up (#765); without it the error source\n  // is created lazily on boundary mount — after the error — and never sees it.\n  useEffect(() => {\n    primeErrorSource(router);\n  }, [router]);\n\n  // useSyncExternalStore manages the router subscription lifecycle:\n  // subscribe connects to router on first listener, unsubscribes on last.\n  const { route, previousRoute } = useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot, // SSR: router returns same state on server and client\n  );\n\n  // Stable-ref against parent re-renders: when parent re-renders RouterProvider\n  // without a route change (e.g. consumer re-renders the root), navigator /\n  // route / previousRoute references stay identical (useSyncExternalStore +\n  // Object.is bail-out). Without `useMemo` the object literal is fresh every\n  // render, propagating spurious re-renders to every `useRoute()` consumer.\n  // The memo bails out whenever the three deps are referentially equal.\n  const routeContextValue = useMemo(\n    () => ({ navigator, route, previousRoute }),\n    [navigator, route, previousRoute],\n  );\n\n  return (\n    <RouterContext.Provider value={router}>\n      <NavigatorContext.Provider value={navigator}>\n        <RouteContext.Provider value={routeContextValue}>\n          {children}\n        </RouteContext.Provider>\n      </NavigatorContext.Provider>\n    </RouterContext.Provider>\n  );\n};\n"],"mappings":"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"}