@rangojs/router 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -22,26 +22,26 @@ function createRangoDebugger(namespace) {
22
22
  if (shadow.enabled) return shadow;
23
23
  return void 0;
24
24
  }
25
- async function timed(debug14, label, fn) {
26
- if (!debug14) return await fn();
25
+ async function timed(debug13, label, fn) {
26
+ if (!debug13) return await fn();
27
27
  const start = performance.now();
28
28
  try {
29
29
  return await fn();
30
30
  } finally {
31
- debug14("%s (%sms)", label, (performance.now() - start).toFixed(1));
31
+ debug13("%s (%sms)", label, (performance.now() - start).toFixed(1));
32
32
  }
33
33
  }
34
- function timedSync(debug14, label, fn) {
35
- if (!debug14) return fn();
34
+ function timedSync(debug13, label, fn) {
35
+ if (!debug13) return fn();
36
36
  const start = performance.now();
37
37
  try {
38
38
  return fn();
39
39
  } finally {
40
- debug14("%s (%sms)", label, (performance.now() - start).toFixed(1));
40
+ debug13("%s (%sms)", label, (performance.now() - start).toFixed(1));
41
41
  }
42
42
  }
43
- function createCounter(debug14, label) {
44
- if (!debug14) return void 0;
43
+ function createCounter(debug13, label) {
44
+ if (!debug13) return void 0;
45
45
  let n = 0;
46
46
  let totalMs = 0;
47
47
  let slowestMs = 0;
@@ -75,7 +75,7 @@ function createCounter(debug14, label) {
75
75
  },
76
76
  flush() {
77
77
  if (n === 0) return;
78
- debug14(
78
+ debug13(
79
79
  "%s: %d files, %sms total, slowest %sms %s",
80
80
  label,
81
81
  n,
@@ -158,7 +158,7 @@ function transformClientRefs(code, projectRoot) {
158
158
  return hasReplacement ? result : null;
159
159
  }
160
160
  function hashClientRefs(projectRoot) {
161
- const counter = createCounter(debug7, "hash-client-refs");
161
+ const counter = createCounter(debug6, "hash-client-refs");
162
162
  return {
163
163
  name: "@rangojs/router:hash-client-refs",
164
164
  enforce: "post",
@@ -180,12 +180,12 @@ function hashClientRefs(projectRoot) {
180
180
  }
181
181
  };
182
182
  }
183
- var debug7, CLIENT_PKG_PROXY_PREFIX, CLIENT_IN_SERVER_PKG_PROXY_PREFIX, FS_PREFIX, REGISTER_CLIENT_REF_RE;
183
+ var debug6, CLIENT_PKG_PROXY_PREFIX, CLIENT_IN_SERVER_PKG_PROXY_PREFIX, FS_PREFIX, REGISTER_CLIENT_REF_RE;
184
184
  var init_client_ref_hashing = __esm({
185
185
  "src/vite/plugins/client-ref-hashing.ts"() {
186
186
  "use strict";
187
187
  init_debug();
188
- debug7 = createRangoDebugger(NS.transform);
188
+ debug6 = createRangoDebugger(NS.transform);
189
189
  CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
190
190
  CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
191
191
  FS_PREFIX = "/@fs/";
@@ -193,6 +193,30 @@ var init_client_ref_hashing = __esm({
193
193
  }
194
194
  });
195
195
 
196
+ // src/internal-debug.ts
197
+ var INTERNAL_RANGO_DEBUG;
198
+ var init_internal_debug = __esm({
199
+ "src/internal-debug.ts"() {
200
+ "use strict";
201
+ INTERNAL_RANGO_DEBUG = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
202
+ }
203
+ });
204
+
205
+ // src/browser/logging.ts
206
+ function debugLog(msg, ...args) {
207
+ if (INTERNAL_RANGO_DEBUG) {
208
+ console.log(msg, ...args);
209
+ }
210
+ }
211
+ var IS_BROWSER_DEBUG;
212
+ var init_logging = __esm({
213
+ "src/browser/logging.ts"() {
214
+ "use strict";
215
+ init_internal_debug();
216
+ IS_BROWSER_DEBUG = INTERNAL_RANGO_DEBUG;
217
+ }
218
+ });
219
+
196
220
  // src/browser/notify-listeners.ts
197
221
  function notifyListeners(entries, notify, isActive, shouldContinue) {
198
222
  const errors = [];
@@ -627,30 +651,6 @@ var init_policy = __esm({
627
651
  }
628
652
  });
629
653
 
630
- // src/internal-debug.ts
631
- var INTERNAL_RANGO_DEBUG;
632
- var init_internal_debug = __esm({
633
- "src/internal-debug.ts"() {
634
- "use strict";
635
- INTERNAL_RANGO_DEBUG = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
636
- }
637
- });
638
-
639
- // src/browser/logging.ts
640
- function debugLog(msg, ...args) {
641
- if (INTERNAL_RANGO_DEBUG) {
642
- console.log(msg, ...args);
643
- }
644
- }
645
- var IS_BROWSER_DEBUG;
646
- var init_logging = __esm({
647
- "src/browser/logging.ts"() {
648
- "use strict";
649
- init_internal_debug();
650
- IS_BROWSER_DEBUG = INTERNAL_RANGO_DEBUG;
651
- }
652
- });
653
-
654
654
  // src/redirect-origin.ts
655
655
  var init_redirect_origin = __esm({
656
656
  "src/redirect-origin.ts"() {
@@ -3050,17 +3050,12 @@ ${lazyImports.join(",\n")}
3050
3050
 
3051
3051
  // src/vite/plugins/use-cache-transform.ts
3052
3052
  import path5 from "node:path";
3053
- import MagicString4 from "magic-string";
3054
- init_debug();
3055
- var debug4 = createRangoDebugger(NS.transform);
3056
3053
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
3057
3054
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
3058
3055
  var USE_CACHE_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
3059
- function useCacheTransform() {
3056
+ function useCacheTransform(counter) {
3060
3057
  let projectRoot = "";
3061
3058
  let isBuild = false;
3062
- let rscTransforms = null;
3063
- const counter = createCounter(debug4, "use-cache");
3064
3059
  return {
3065
3060
  name: "@rangojs/router:use-cache",
3066
3061
  enforce: "post",
@@ -3073,62 +3068,73 @@ function useCacheTransform() {
3073
3068
  },
3074
3069
  async transform(code, id) {
3075
3070
  if (this.environment?.name !== "rsc") return;
3076
- if (!code.includes("use cache")) return;
3077
- if (id.includes("/node_modules/") || id.startsWith("\0")) return;
3078
- if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
3071
+ if (!isUseCacheCandidate(code, id)) return;
3079
3072
  const start = counter ? performance.now() : 0;
3080
3073
  try {
3081
- if (!rscTransforms) {
3082
- try {
3083
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
3084
- } catch {
3085
- return;
3086
- }
3087
- }
3088
- const {
3089
- hasDirective: hasDirective2,
3090
- transformWrapExport,
3091
- transformHoistInlineDirective
3092
- } = rscTransforms;
3093
- let ast;
3094
- try {
3095
- const { parseAst: parseAst6 } = await import("vite");
3096
- ast = parseAst6(code, { lang: "tsx" });
3097
- } catch {
3098
- return;
3099
- }
3100
- stripNullDirectiveFields(ast);
3101
- const filePath = normalizePath(path5.relative(projectRoot, id));
3102
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
3103
- if (hasDirective2(ast.body, "use cache")) {
3104
- return transformFileLevelUseCache(
3105
- code,
3106
- ast,
3107
- filePath,
3108
- id,
3109
- isBuild,
3110
- isLayoutOrTemplate,
3111
- transformWrapExport,
3112
- hasDirective2
3113
- );
3114
- }
3115
- const functionResult = transformFunctionLevelUseCache(
3116
- code,
3117
- ast,
3118
- filePath,
3119
- id,
3074
+ return await transformUseCache(code, id, {
3075
+ root: projectRoot,
3120
3076
  isBuild,
3121
- transformHoistInlineDirective
3122
- );
3123
- warnOnNearMissDirectives(ast, id, this.warn.bind(this));
3124
- if (functionResult) return functionResult;
3077
+ warn: (message) => this.warn(message)
3078
+ });
3125
3079
  } finally {
3126
3080
  counter?.record(id, performance.now() - start);
3127
3081
  }
3128
3082
  }
3129
3083
  };
3130
3084
  }
3131
- function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport, hasDirective2) {
3085
+ function isUseCacheCandidate(code, id) {
3086
+ return (
3087
+ // Quick bail: no "use cache" in source
3088
+ code.includes("use cache") && // Skip node_modules and virtual modules
3089
+ !id.includes("/node_modules/") && !id.startsWith("\0") && // Only JS/TS files
3090
+ /\.(tsx?|jsx?|mjs)$/.test(id)
3091
+ );
3092
+ }
3093
+ var rscTransforms = null;
3094
+ async function transformUseCache(code, id, options) {
3095
+ if (!rscTransforms) {
3096
+ try {
3097
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
3098
+ } catch (error) {
3099
+ options.onTransformsError?.(error);
3100
+ return;
3101
+ }
3102
+ }
3103
+ const { hasDirective: hasDirective2, transformWrapExport, transformHoistInlineDirective } = rscTransforms;
3104
+ let ast;
3105
+ try {
3106
+ const { parseAst: parseAst6 } = await import("vite");
3107
+ ast = parseAst6(code, { lang: "tsx" });
3108
+ } catch {
3109
+ return;
3110
+ }
3111
+ stripNullDirectiveFields(ast);
3112
+ const filePath = normalizePath(path5.relative(options.root, id));
3113
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
3114
+ if (hasDirective2(ast.body, "use cache")) {
3115
+ return transformFileLevelUseCache(
3116
+ code,
3117
+ ast,
3118
+ filePath,
3119
+ id,
3120
+ options.isBuild,
3121
+ isLayoutOrTemplate,
3122
+ transformWrapExport,
3123
+ hasDirective2
3124
+ );
3125
+ }
3126
+ const functionResult = transformFunctionLevelUseCache(
3127
+ code,
3128
+ ast,
3129
+ filePath,
3130
+ id,
3131
+ options.isBuild,
3132
+ transformHoistInlineDirective
3133
+ );
3134
+ warnOnNearMissDirectives(ast, id, options.warn);
3135
+ if (functionResult) return functionResult;
3136
+ }
3137
+ async function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport, hasDirective2) {
3132
3138
  const unconfirmedExports = [];
3133
3139
  const { exportNames, output } = transformWrapExport(code, ast, {
3134
3140
  runtime: (value, name) => {
@@ -3157,6 +3163,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
3157
3163
  );
3158
3164
  }
3159
3165
  if (exportNames.length === 0) {
3166
+ const { default: MagicString4 } = await import("magic-string");
3160
3167
  const s = new MagicString4(code);
3161
3168
  const directive2 = findFileLevelDirective(ast);
3162
3169
  if (directive2) {
@@ -3285,7 +3292,7 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
3285
3292
  init_debug();
3286
3293
  import { existsSync, readFileSync } from "node:fs";
3287
3294
  import { isAbsolute, join, normalize, relative } from "node:path";
3288
- var debug5 = createRangoDebugger(NS.transform);
3295
+ var debug4 = createRangoDebugger(NS.transform);
3289
3296
  var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
3290
3297
  var DEDUP_PREFIX = "\0rango:dedup/";
3291
3298
  function extractPackageName(absolutePath) {
@@ -3462,8 +3469,8 @@ function clientRefDedup() {
3462
3469
  resolvePackage = config.createResolver({ scan: true });
3463
3470
  },
3464
3471
  buildEnd() {
3465
- if (debug5 && dedupedPackages.size > 0) {
3466
- debug5(
3472
+ if (debug4 && dedupedPackages.size > 0) {
3473
+ debug4(
3467
3474
  "client-ref-dedup: redirected %d package(s) (%s)",
3468
3475
  dedupedPackages.size,
3469
3476
  [...dedupedPackages].join(",")
@@ -3505,7 +3512,7 @@ function clientRefDedup() {
3505
3512
  }
3506
3513
  return specifierPromise.then((specifier) => {
3507
3514
  if (!specifier) return;
3508
- if (debug5) dedupedPackages.add(packageName);
3515
+ if (debug4) dedupedPackages.add(packageName);
3509
3516
  return `${DEDUP_PREFIX}${encodeURIComponent(specifier)}`;
3510
3517
  });
3511
3518
  },
@@ -3795,7 +3802,7 @@ import { resolve } from "node:path";
3795
3802
  // package.json
3796
3803
  var package_default = {
3797
3804
  name: "@rangojs/router",
3798
- version: "0.16.0",
3805
+ version: "0.17.0",
3799
3806
  description: "Django-inspired RSC router with composable URL patterns",
3800
3807
  keywords: [
3801
3808
  "react",
@@ -4006,7 +4013,7 @@ var package_default = {
4006
4013
  "@opentelemetry/api": "^1.9.0",
4007
4014
  "@opentelemetry/context-async-hooks": "^2.9.0",
4008
4015
  "@opentelemetry/sdk-trace-base": "^2.9.0",
4009
- "@playwright/test": "^1.49.1",
4016
+ "@playwright/test": "^1.62.0",
4010
4017
  "@shared/e2e": "workspace:*",
4011
4018
  "@testing-library/dom": "^10.4.1",
4012
4019
  "@testing-library/react": "^16.3.2",
@@ -5482,7 +5489,7 @@ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
5482
5489
  // src/vite/plugins/performance-tracks.ts
5483
5490
  init_debug();
5484
5491
  import { readFile } from "node:fs/promises";
5485
- var debug6 = createRangoDebugger(NS.transform);
5492
+ var debug5 = createRangoDebugger(NS.transform);
5486
5493
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
5487
5494
  function buildPatchReplacement(match, debugInfoVar) {
5488
5495
  return `${match}
@@ -5517,7 +5524,7 @@ function performanceTracksOptimizeDepsPlugin() {
5517
5524
  };
5518
5525
  }
5519
5526
  function performanceTracksPlugin() {
5520
- const counter = createCounter(debug6, "performance-tracks");
5527
+ const counter = createCounter(debug5, "performance-tracks");
5521
5528
  return {
5522
5529
  name: "@rangojs/router:performance-tracks",
5523
5530
  buildEnd() {
@@ -5529,7 +5536,7 @@ function performanceTracksPlugin() {
5529
5536
  try {
5530
5537
  const patched = patchRsdwClientDebugInfoRecovery(code);
5531
5538
  if (!patched.debugInfoVar) return;
5532
- debug6?.("patched RSDW client (var: %s)", patched.debugInfoVar);
5539
+ debug5?.("patched RSDW client (var: %s)", patched.debugInfoVar);
5533
5540
  return patched.code;
5534
5541
  } finally {
5535
5542
  counter?.record(id, performance.now() - start);
@@ -6060,7 +6067,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
6060
6067
 
6061
6068
  // src/vite/plugins/cjs-to-esm.ts
6062
6069
  init_debug();
6063
- var debug8 = createRangoDebugger(NS.transform);
6070
+ var debug7 = createRangoDebugger(NS.transform);
6064
6071
  function createCjsToEsmPlugin() {
6065
6072
  let isProduction = false;
6066
6073
  return {
@@ -6074,7 +6081,7 @@ function createCjsToEsmPlugin() {
6074
6081
  if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
6075
6082
  const isProd = isProduction;
6076
6083
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
6077
- debug8?.("cjs-to-esm entry redirect %s", id);
6084
+ debug7?.("cjs-to-esm entry redirect %s", id);
6078
6085
  return {
6079
6086
  code: `export * from "${cjsFile}";`,
6080
6087
  map: null
@@ -6110,7 +6117,7 @@ function createCjsToEsmPlugin() {
6110
6117
  "export const $1 ="
6111
6118
  );
6112
6119
  transformed = license + "\n" + transformed;
6113
- debug8?.("cjs-to-esm body rewrite %s", id);
6120
+ debug7?.("cjs-to-esm body rewrite %s", id);
6114
6121
  return {
6115
6122
  code: transformed,
6116
6123
  map: null
@@ -6138,17 +6145,35 @@ var DEV_DISCOVERY_EPOCH_HEADER = "x-rango-dev-discovery-epoch";
6138
6145
  import React from "react";
6139
6146
 
6140
6147
  // src/server/context.ts
6148
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
6149
+
6150
+ // src/cache/cache-exec-scope.ts
6141
6151
  import { AsyncLocalStorage } from "node:async_hooks";
6152
+
6153
+ // src/cache/taint.ts
6154
+ var cacheExecScopeProbe = () => false;
6155
+ function _setCacheExecScopeProbe(probe) {
6156
+ cacheExecScopeProbe = probe;
6157
+ }
6158
+
6159
+ // src/cache/cache-exec-scope.ts
6160
+ var cacheExecStorage = new AsyncLocalStorage();
6161
+ function isInsideCacheExecScope() {
6162
+ return cacheExecStorage.getStore() !== void 0;
6163
+ }
6164
+ _setCacheExecScopeProbe(isInsideCacheExecScope);
6165
+
6166
+ // src/server/context.ts
6142
6167
  var RSC_CONTEXT_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:rsc-context");
6143
- var RangoContext = globalThis[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage();
6168
+ var RangoContext = globalThis[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage2();
6144
6169
  var LOADER_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-scope");
6145
- var loaderScopeALS = globalThis[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage();
6170
+ var loaderScopeALS = globalThis[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage2();
6146
6171
  var LOADER_BODY_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-body-scope");
6147
- var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage();
6172
+ var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage2();
6148
6173
  var PUSH_CALLBACK_SCOPE_KEY = /* @__PURE__ */ Symbol.for(
6149
6174
  "rangojs-router:push-callback-scope"
6150
6175
  );
6151
- var pushCallbackScopeALS = globalThis[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage();
6176
+ var pushCallbackScopeALS = globalThis[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage2();
6152
6177
 
6153
6178
  // src/rsc/shell-capture-constants.ts
6154
6179
  var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
@@ -6325,7 +6350,7 @@ init_client_ref_hashing();
6325
6350
  // src/vite/plugins/server-ref-hashing.ts
6326
6351
  init_debug();
6327
6352
  init_client_ref_hashing();
6328
- var debug9 = createRangoDebugger(NS.transform);
6353
+ var debug8 = createRangoDebugger(NS.transform);
6329
6354
  function transformServerRefs(code, projectRoot) {
6330
6355
  if (!code.includes("registerServerReference")) return null;
6331
6356
  let hasReplacement = false;
@@ -6342,7 +6367,7 @@ function transformServerRefs(code, projectRoot) {
6342
6367
  return hasReplacement ? result : null;
6343
6368
  }
6344
6369
  function hashServerRefs(projectRoot) {
6345
- const counter = createCounter(debug9, "hash-server-refs");
6370
+ const counter = createCounter(debug8, "hash-server-refs");
6346
6371
  return {
6347
6372
  name: "@rangojs/router:hash-server-refs",
6348
6373
  enforce: "post",
@@ -7207,11 +7232,11 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
7207
7232
 
7208
7233
  // src/vite/discovery/prerender-collection.ts
7209
7234
  init_debug();
7210
- var debug10 = createRangoDebugger(NS.prerender);
7235
+ var debug9 = createRangoDebugger(NS.prerender);
7211
7236
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7212
7237
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
7213
- const overallStart = debug10 ? performance.now() : 0;
7214
- debug10?.(
7238
+ const overallStart = debug9 ? performance.now() : 0;
7239
+ debug9?.(
7215
7240
  "expandPrerenderRoutes: start (%d router manifest(s))",
7216
7241
  allManifests.length
7217
7242
  );
@@ -7264,7 +7289,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7264
7289
  });
7265
7290
  } else {
7266
7291
  if (def?.getParams) {
7267
- const getParamsStart = debug10 ? performance.now() : 0;
7292
+ const getParamsStart = debug9 ? performance.now() : 0;
7268
7293
  try {
7269
7294
  const buildVars = {};
7270
7295
  const buildEnv = state.resolvedBuildEnv;
@@ -7283,7 +7308,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7283
7308
  }
7284
7309
  };
7285
7310
  const paramsList = await def.getParams(getParamsCtx);
7286
- debug10?.(
7311
+ debug9?.(
7287
7312
  "getParams %s -> %d params (%sms)",
7288
7313
  routeName,
7289
7314
  paramsList.length,
@@ -7355,7 +7380,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7355
7380
  }
7356
7381
  }
7357
7382
  if (entries.length === 0) {
7358
- debug10?.(
7383
+ debug9?.(
7359
7384
  "no prerender entries (done in %sms)",
7360
7385
  (performance.now() - overallStart).toFixed(1)
7361
7386
  );
@@ -7366,7 +7391,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7366
7391
  console.log(
7367
7392
  `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
7368
7393
  );
7369
- debug10?.(
7394
+ debug9?.(
7370
7395
  "prerender loop: %d entries, max concurrency %d",
7371
7396
  entries.length,
7372
7397
  maxConcurrency
@@ -7474,7 +7499,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7474
7499
  console.log(
7475
7500
  `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
7476
7501
  );
7477
- debug10?.(
7502
+ debug9?.(
7478
7503
  "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
7479
7504
  doneCount,
7480
7505
  skipCount,
@@ -7485,8 +7510,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7485
7510
  async function renderStaticHandlers(state, rscEnv, registry) {
7486
7511
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
7487
7512
  return;
7488
- const overallStart = debug10 ? performance.now() : 0;
7489
- debug10?.(
7513
+ const overallStart = debug9 ? performance.now() : 0;
7514
+ debug9?.(
7490
7515
  "renderStaticHandlers: start (%d static module(s))",
7491
7516
  state.resolvedStaticModules.size
7492
7517
  );
@@ -7576,7 +7601,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
7576
7601
  console.log(
7577
7602
  `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
7578
7603
  );
7579
- debug10?.(
7604
+ debug9?.(
7580
7605
  "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
7581
7606
  staticDone,
7582
7607
  staticSkip,
@@ -7707,1313 +7732,1316 @@ import {
7707
7732
  Suspense as Suspense2
7708
7733
  } from "react";
7709
7734
 
7710
- // src/outlet-context.ts
7711
- import { createContext } from "react";
7712
- var OutletContext = createContext(null);
7713
-
7714
- // src/route-content-wrapper.tsx
7715
- import { Component, Suspense, use } from "react";
7716
-
7717
- // src/outlet-provider.tsx
7718
- import { useContext, useMemo } from "react";
7719
- import { jsx } from "react/jsx-runtime";
7720
-
7721
- // src/decode-loader-results.ts
7722
- import { createElement as createElement2 } from "react";
7723
-
7724
- // src/loader-redirect.tsx
7725
- import { useEffect } from "react";
7726
-
7727
- // src/browser/react/use-router.ts
7728
- import { useContext as useContext3, useMemo as useMemo2 } from "react";
7729
-
7730
- // src/browser/react/context.ts
7731
- import { createContext as createContext2 } from "react";
7732
- var NavigationStoreContext = createContext2(null);
7733
-
7734
- // src/browser/react/use-mount.ts
7735
- import { useContext as useContext2 } from "react";
7736
-
7737
- // src/browser/react/mount-context.ts
7738
- import {
7739
- createContext as createContext3,
7740
- createElement
7741
- } from "react";
7742
- var MountContext = createContext3("/");
7743
-
7744
- // src/browser/react/use-router.ts
7745
- init_loader();
7746
-
7747
- // src/browser/app-version.ts
7748
- var currentVersion2;
7749
- function getAppVersion() {
7750
- return currentVersion2;
7751
- }
7752
-
7753
- // src/loader-redirect.tsx
7754
- init_validate_redirect_origin();
7755
- init_redirect_origin();
7756
-
7757
- // src/route-content-wrapper.tsx
7758
- import { jsx as jsx2 } from "react/jsx-runtime";
7759
-
7760
- // src/use-loader.tsx
7735
+ // src/browser/react/use-handle.ts
7761
7736
  import {
7762
- isValidElement,
7763
- startTransition,
7764
- use as use2,
7765
- useCallback,
7766
- useContext as useContext4,
7767
- useEffect as useEffect2,
7768
- useMemo as useMemo3,
7769
- useOptimistic,
7737
+ useContext,
7738
+ useState,
7739
+ useEffect,
7770
7740
  useRef,
7771
- useState
7741
+ useOptimistic,
7742
+ startTransition
7772
7743
  } from "react";
7773
7744
 
7774
- // src/thenable-status.ts
7775
- function unwrapsSynchronously(stream) {
7776
- const status = stream.status;
7777
- return status === "fulfilled" || status === "resolved_model" || status === "resolved_module";
7745
+ // src/missing-id-error.ts
7746
+ var SELF_FILES = /* @__PURE__ */ new Set([
7747
+ "missing-id-error",
7748
+ "loader",
7749
+ "loader.rsc",
7750
+ "handle"
7751
+ ]);
7752
+ function findUserCallSite() {
7753
+ try {
7754
+ const stack = new Error().stack;
7755
+ if (!stack) return void 0;
7756
+ for (const frame of stack.split("\n").slice(1)) {
7757
+ const m = frame.match(
7758
+ /(?:\(|@|\s)(?:file:\/\/)?((?:\/|[A-Za-z]:[\\/])[^()\s]+?\.(?:ts|tsx|js|jsx|mts|cts)):(\d+):(\d+)\)?/
7759
+ );
7760
+ if (!m) continue;
7761
+ const path6 = m[1];
7762
+ if (path6.includes("node_modules") || path6.includes("@rangojs/router")) {
7763
+ continue;
7764
+ }
7765
+ const base = path6.split(/[\\/]/).pop().replace(/\.(?:ts|tsx|js|jsx|mts|cts)$/, "");
7766
+ if (SELF_FILES.has(base)) continue;
7767
+ return `${path6}:${m[2]}:${m[3]}`;
7768
+ }
7769
+ } catch {
7770
+ }
7771
+ return void 0;
7772
+ }
7773
+ function missingInjectedIdError(kind, fnName) {
7774
+ const site = findUserCallSite();
7775
+ const at = site ? ` (created at ${site})` : "";
7776
+ return new Error(
7777
+ `[rango] ${kind} is missing $$id${at}.
7778
+ The @rangojs/router:expose-internal-ids Vite transform injects ${fnName}()'s stable $$id from an EXPORTED const declaration only:
7779
+ export const X = ${fnName}(...)
7780
+ const X = ${fnName}(...); export { X }
7781
+ A non-exported const, an \`export let/var\`, or an inline ${fnName}(...) call gets no $$id \u2014 export it as \`export const\`. (A matching "Unsupported ${fnName} shape" warning names the exact file:line.)`
7782
+ );
7778
7783
  }
7779
7784
 
7780
- // src/loader-store.ts
7781
- var EMPTY_SNAPSHOT = Object.freeze({
7782
- value: void 0,
7783
- hasValue: false,
7784
- error: null,
7785
- isLoading: false,
7786
- requestId: 0
7787
- });
7788
- function normalizeGroups(group) {
7789
- if (group === void 0) return [];
7790
- if (typeof group === "string") return [group];
7791
- return [...new Set(group)];
7785
+ // src/handle.ts
7786
+ function defaultCollect(segments) {
7787
+ return segments;
7792
7788
  }
7793
- var LoaderStore = class {
7794
- constructor() {
7795
- this.entries = /* @__PURE__ */ new Map();
7796
- /** loader.$$id -> set of bucket keys, so clearFamily() can reach every bucket. */
7797
- this.families = /* @__PURE__ */ new Map();
7798
- /** refresh group name -> set of bucket keys, for refreshGroups(). */
7799
- this.groups = /* @__PURE__ */ new Map();
7800
- /**
7801
- * loader.$$id -> per-loader streams a committed navigation is still
7802
- * receiving. Registered and announced by announcePendingStreams(), which
7803
- * browser/partial-update.ts calls INSIDE every transition commit, so a
7804
- * reader whose content is held on screen by that transition pins
7805
- * `isLoading: true` for the data it is still showing until the transition
7806
- * lands (use-loader.tsx). The new tree's own read of the loader suspends at
7807
- * the read site and never observes the flag. A Set, not a flag: overlapping
7808
- * navigations (second click before the first stream lands) keep the family
7809
- * pending until the LAST stream settles.
7810
- */
7811
- this.pendingStreams = /* @__PURE__ */ new Map();
7812
- }
7813
- getOrCreate(bucketKey) {
7814
- let e = this.entries.get(bucketKey);
7815
- if (!e) {
7816
- e = {
7817
- snapshot: EMPTY_SNAPSHOT,
7818
- listeners: /* @__PURE__ */ new Set(),
7819
- latestRequestId: 0,
7820
- loaderId: bucketKey,
7821
- sticky: false,
7822
- pendingClear: false,
7823
- clearWhenSettled: false,
7824
- groups: /* @__PURE__ */ new Map(),
7825
- refetch: void 0,
7826
- streamPendingListeners: /* @__PURE__ */ new Set()
7827
- };
7828
- this.entries.set(bucketKey, e);
7789
+ var collectRegistry = /* @__PURE__ */ new Map();
7790
+ var runtimeHandleIdCounter = 0;
7791
+ function createHandle(collect, __injectedId) {
7792
+ let handleId = __injectedId ?? "";
7793
+ if (!handleId) {
7794
+ if (isUnderTestRunner()) {
7795
+ handleId = `__rango_runtime_handle_${runtimeHandleIdCounter++}`;
7796
+ } else if (process.env.NODE_ENV !== "production") {
7797
+ throw missingInjectedIdError("Handle", "createHandle");
7798
+ } else {
7799
+ throw new Error(
7800
+ "[rango] Handle is missing $$id \u2014 the build plugin did not inject one. Export it as `export const X = createHandle(...)`."
7801
+ );
7829
7802
  }
7830
- return e;
7831
7803
  }
7832
- registerFamily(loaderId, bucketKey) {
7833
- let fam = this.families.get(loaderId);
7834
- if (!fam) {
7835
- fam = /* @__PURE__ */ new Set();
7836
- this.families.set(loaderId, fam);
7837
- }
7838
- fam.add(bucketKey);
7804
+ const collectFn = collect ?? defaultCollect;
7805
+ collectRegistry.set(
7806
+ handleId,
7807
+ collectFn
7808
+ );
7809
+ return {
7810
+ __brand: "handle",
7811
+ $$id: handleId
7812
+ };
7813
+ }
7814
+
7815
+ // src/browser/react/context.ts
7816
+ import { createContext } from "react";
7817
+ var NavigationStoreContext = createContext(null);
7818
+
7819
+ // src/handles/meta.ts
7820
+ function isUnsetDescriptor(descriptor) {
7821
+ return typeof descriptor === "object" && descriptor !== null && "unset" in descriptor && typeof descriptor.unset === "string";
7822
+ }
7823
+ function isTitleDescriptor(descriptor) {
7824
+ return typeof descriptor === "object" && descriptor !== null && "title" in descriptor;
7825
+ }
7826
+ function isTitleTemplate(title) {
7827
+ return typeof title === "object" && title !== null && "template" in title && "default" in title;
7828
+ }
7829
+ function isAbsoluteTitle(title) {
7830
+ return typeof title === "object" && title !== null && "absolute" in title;
7831
+ }
7832
+ function getMetaKey(descriptor) {
7833
+ if (isUnsetDescriptor(descriptor)) {
7834
+ return void 0;
7839
7835
  }
7840
- /**
7841
- * Subscribe to entry changes for `bucketKey`.
7842
- * Returns an unsubscribe function. A sticky bucket's entry is kept around even
7843
- * after the last subscriber leaves so that an in-flight `load()` can still
7844
- * commit if the consumer remounts. An ephemeral bucket is dropped once its
7845
- * last subscriber leaves (deferred, see maybeScheduleRefcountClear).
7846
- */
7847
- subscribe(bucketKey, cb, options) {
7848
- const e = this.getOrCreate(bucketKey);
7849
- const loaderId = options?.loaderId ?? bucketKey;
7850
- e.loaderId = loaderId;
7851
- this.registerFamily(loaderId, bucketKey);
7852
- if (options?.ephemeral !== true) e.sticky = true;
7853
- const groups = normalizeGroups(options?.group);
7854
- for (const group of groups) {
7855
- this.addToGroup(group, bucketKey, e, options?.refetch);
7856
- }
7857
- e.pendingClear = false;
7858
- e.clearWhenSettled = false;
7859
- e.listeners.add(cb);
7860
- const onStreamPending = options?.onStreamPending;
7861
- if (onStreamPending) e.streamPendingListeners.add(onStreamPending);
7862
- return () => {
7863
- e.listeners.delete(cb);
7864
- if (onStreamPending) e.streamPendingListeners.delete(onStreamPending);
7865
- for (const group of groups) this.releaseGroup(group, bucketKey, e);
7866
- this.maybeScheduleRefcountClear(bucketKey, e);
7867
- };
7836
+ if ("charSet" in descriptor) {
7837
+ return "charSet";
7868
7838
  }
7869
- addToGroup(group, bucketKey, e, refetch) {
7870
- e.groups.set(group, (e.groups.get(group) ?? 0) + 1);
7871
- if (refetch) e.refetch = refetch;
7872
- let members = this.groups.get(group);
7873
- if (!members) {
7874
- members = /* @__PURE__ */ new Set();
7875
- this.groups.set(group, members);
7876
- }
7877
- members.add(bucketKey);
7839
+ if ("title" in descriptor) {
7840
+ return "title";
7878
7841
  }
7879
- releaseGroup(group, bucketKey, e) {
7880
- const next = (e.groups.get(group) ?? 0) - 1;
7881
- if (next > 0) {
7882
- e.groups.set(group, next);
7883
- return;
7884
- }
7885
- e.groups.delete(group);
7886
- const members = this.groups.get(group);
7887
- if (members) {
7888
- members.delete(bucketKey);
7889
- if (members.size === 0) this.groups.delete(group);
7890
- }
7842
+ if ("name" in descriptor && "content" in descriptor) {
7843
+ return `name:${descriptor.name}`;
7891
7844
  }
7892
- /** Remove a bucket from every group it belongs to (used when it is dropped). */
7893
- removeFromAllGroups(bucketKey, e) {
7894
- for (const group of e.groups.keys()) {
7895
- const members = this.groups.get(group);
7896
- if (members) {
7897
- members.delete(bucketKey);
7898
- if (members.size === 0) this.groups.delete(group);
7845
+ if ("property" in descriptor && "content" in descriptor) {
7846
+ return `property:${descriptor.property}`;
7847
+ }
7848
+ if ("httpEquiv" in descriptor && "content" in descriptor) {
7849
+ return `httpEquiv:${descriptor.httpEquiv}`;
7850
+ }
7851
+ if ("script:ld+json" in descriptor) {
7852
+ return void 0;
7853
+ }
7854
+ if ("tagName" in descriptor) {
7855
+ if (descriptor.tagName === "link" && "rel" in descriptor) {
7856
+ const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
7857
+ if (uniqueRels.includes(descriptor.rel)) {
7858
+ return `link:${descriptor.rel}`;
7899
7859
  }
7900
7860
  }
7901
- e.groups.clear();
7861
+ return void 0;
7902
7862
  }
7903
- /**
7904
- * Refresh every currently-mounted bucket tagged with ANY of the given group
7905
- * names, with a plain GET (no params/body). Accepts a single name or an array
7906
- * of names. A bucket that belongs to more than one of the named groups is
7907
- * refreshed exactly once (members are unioned and deduped by bucket key), as
7908
- * are multiple reads of one bucket. Resolves when all refreshes settle; rejects
7909
- * with an `AggregateError` of the failures if any member fails — each failing
7910
- * member also records its error on its own snapshot.
7911
- */
7912
- async refreshGroups(groups) {
7913
- const names = typeof groups === "string" ? [groups] : groups;
7914
- const buckets = /* @__PURE__ */ new Set();
7915
- for (const name of names) {
7916
- const members = this.groups.get(name);
7917
- if (!members) continue;
7918
- for (const bucketKey of members) buckets.add(bucketKey);
7919
- }
7920
- if (buckets.size === 0) return;
7921
- const thunks = [];
7922
- for (const bucketKey of buckets) {
7923
- const e = this.entries.get(bucketKey);
7924
- if (!e || e.listeners.size === 0 || !e.refetch) continue;
7925
- thunks.push(e.refetch);
7926
- }
7927
- if (thunks.length === 0) return;
7928
- const results = await Promise.allSettled(thunks.map((t) => t()));
7929
- const reasons = results.filter((r) => r.status === "rejected").map((r) => r.reason);
7930
- if (reasons.length > 0) {
7931
- const label = names.map((n) => `"${n}"`).join(", ");
7932
- throw new AggregateError(
7933
- reasons,
7934
- `refreshGroups(${label}) had ${reasons.length} failure(s)`
7935
- );
7863
+ return void 0;
7864
+ }
7865
+ var defaultMetaDescriptors = [
7866
+ { charSet: "utf-8" },
7867
+ { name: "viewport", content: "width=device-width, initial-scale=1" }
7868
+ ];
7869
+ function addOrReplace(result, keyToIndex, descriptor, key) {
7870
+ if (key !== void 0 && keyToIndex.has(key)) {
7871
+ result[keyToIndex.get(key)] = descriptor;
7872
+ } else {
7873
+ if (key !== void 0) {
7874
+ keyToIndex.set(key, result.length);
7936
7875
  }
7876
+ result.push(descriptor);
7937
7877
  }
7938
- /**
7939
- * Schedule a deferred drop for an ad-hoc (never-sticky) bucket whose last
7940
- * subscriber just left. Deferred to a microtask so a StrictMode / transition
7941
- * remount that resubscribes in the same tick cancels it.
7942
- */
7943
- maybeScheduleRefcountClear(bucketKey, e) {
7944
- if (e.listeners.size > 0) return;
7945
- if (e.sticky) return;
7946
- if (e.pendingClear) return;
7947
- e.pendingClear = true;
7948
- queueMicrotask(() => {
7949
- if (!e.pendingClear) return;
7950
- e.pendingClear = false;
7951
- if (e.listeners.size > 0) return;
7952
- if (e.snapshot.isLoading) {
7953
- e.clearWhenSettled = true;
7954
- return;
7955
- }
7956
- this.dropBucket(bucketKey);
7957
- });
7878
+ }
7879
+ function updateIndicesAfterRemoval(keyToIndex, removedIndex) {
7880
+ for (const [key, index] of keyToIndex) {
7881
+ if (index > removedIndex) {
7882
+ keyToIndex.set(key, index - 1);
7883
+ }
7958
7884
  }
7959
- /** Remove a bucket entry entirely and prune it from its family/group indexes. */
7960
- dropBucket(bucketKey) {
7961
- const e = this.entries.get(bucketKey);
7962
- if (!e) return;
7963
- this.removeFromAllGroups(bucketKey, e);
7964
- this.entries.delete(bucketKey);
7965
- const fam = this.families.get(e.loaderId);
7966
- if (fam) {
7967
- fam.delete(bucketKey);
7968
- if (fam.size === 0) this.families.delete(e.loaderId);
7885
+ }
7886
+ function collectMeta(segments) {
7887
+ const result = [];
7888
+ const keyToIndex = /* @__PURE__ */ new Map();
7889
+ let titleTemplate;
7890
+ for (const descriptor of defaultMetaDescriptors) {
7891
+ const key = getMetaKey(descriptor);
7892
+ if (key !== void 0) {
7893
+ keyToIndex.set(key, result.length);
7969
7894
  }
7895
+ result.push(descriptor);
7970
7896
  }
7971
- /** Drop an ephemeral bucket that settled with no subscribers left. */
7972
- dropIfSettled(bucketKey, e) {
7973
- if (e.clearWhenSettled && e.listeners.size === 0) {
7974
- e.clearWhenSettled = false;
7975
- this.dropBucket(bucketKey);
7897
+ for (const descriptors of segments) {
7898
+ for (const descriptor of descriptors) {
7899
+ if (isUnsetDescriptor(descriptor)) {
7900
+ const keyToRemove = descriptor.unset;
7901
+ if (keyToIndex.has(keyToRemove)) {
7902
+ const idx = keyToIndex.get(keyToRemove);
7903
+ result.splice(idx, 1);
7904
+ keyToIndex.delete(keyToRemove);
7905
+ updateIndicesAfterRemoval(keyToIndex, idx);
7906
+ }
7907
+ continue;
7908
+ }
7909
+ if (isTitleDescriptor(descriptor)) {
7910
+ const titleValue = descriptor.title;
7911
+ if (isTitleTemplate(titleValue)) {
7912
+ titleTemplate = titleValue.template;
7913
+ addOrReplace(
7914
+ result,
7915
+ keyToIndex,
7916
+ { title: titleValue.default },
7917
+ "title"
7918
+ );
7919
+ continue;
7920
+ }
7921
+ if (isAbsoluteTitle(titleValue)) {
7922
+ addOrReplace(
7923
+ result,
7924
+ keyToIndex,
7925
+ { title: titleValue.absolute },
7926
+ "title"
7927
+ );
7928
+ continue;
7929
+ }
7930
+ const finalTitle = titleTemplate ? titleTemplate.split("%s").join(titleValue) : titleValue;
7931
+ addOrReplace(
7932
+ result,
7933
+ keyToIndex,
7934
+ { title: finalTitle },
7935
+ "title"
7936
+ );
7937
+ continue;
7938
+ }
7939
+ const key = getMetaKey(descriptor);
7940
+ addOrReplace(result, keyToIndex, descriptor, key);
7976
7941
  }
7977
7942
  }
7978
- /**
7979
- * Returns the current snapshot for `bucketKey`. Stable reference between
7980
- * mutations — subscribers rely on this to avoid spurious re-renders.
7981
- * Returns `EMPTY_SNAPSHOT` (a singleton) when the entry has never been
7982
- * mutated or has been cleared.
7983
- */
7984
- getSnapshot(bucketKey) {
7985
- return this.entries.get(bucketKey)?.snapshot ?? EMPTY_SNAPSHOT;
7986
- }
7987
- /**
7988
- * Reserve a fresh request id for an upcoming `load()` call. The returned id
7989
- * is the new "latest"; any older in-flight requests will fail their gating
7990
- * check on `finishData` / `finishError` / `finishLoading` and be dropped.
7991
- *
7992
- * Callers should follow with `beginRequest(id, requestId)` to flip the
7993
- * loading flag on AND clear any leftover error from a previous attempt
7994
- * — the latter matters for `throwOnError: false` consumers, which would
7995
- * otherwise keep showing the stale error throughout the retry.
7996
- */
7997
- reserveRequestId(bucketKey) {
7998
- const e = this.getOrCreate(bucketKey);
7999
- e.latestRequestId++;
8000
- return e.latestRequestId;
8001
- }
8002
- /**
8003
- * Mark the request as in-flight: `isLoading = true`, `error = null`.
8004
- * Combines the two operations so a retry doesn't render the previous
8005
- * error during the new request. Gated on `requestId === latestRequestId`
8006
- * for symmetry with the other mutators.
8007
- */
8008
- beginRequest(bucketKey, requestId) {
8009
- const e = this.entries.get(bucketKey);
8010
- if (!e || requestId !== e.latestRequestId) return;
8011
- if (e.snapshot.isLoading && e.snapshot.error === null) return;
8012
- e.snapshot = Object.freeze({
8013
- value: e.snapshot.value,
8014
- hasValue: e.snapshot.hasValue,
8015
- error: null,
8016
- isLoading: true,
8017
- requestId
8018
- });
8019
- this.notify(e);
8020
- }
8021
- /**
8022
- * Commit a successful result. No-op if `requestId` is not the latest
8023
- * (a newer `load()` was issued or `clear()` ran). Clearing `error` is
8024
- * intentional: a successful refetch should hide the previous failure.
8025
- */
8026
- finishData(bucketKey, requestId, value) {
8027
- const e = this.entries.get(bucketKey);
8028
- if (!e || requestId !== e.latestRequestId) return;
8029
- e.snapshot = Object.freeze({
8030
- value,
8031
- hasValue: true,
8032
- error: null,
8033
- isLoading: false,
8034
- requestId
8035
- });
8036
- this.notify(e);
8037
- this.dropIfSettled(bucketKey, e);
8038
- }
8039
- /**
8040
- * Commit an error. Preserves the last good `value` so consumers can keep
8041
- * showing previous data while displaying the error if they choose. No-op
8042
- * if `requestId` is not the latest.
8043
- */
8044
- finishError(bucketKey, requestId, error) {
8045
- const e = this.entries.get(bucketKey);
8046
- if (!e || requestId !== e.latestRequestId) return;
8047
- e.snapshot = Object.freeze({
8048
- value: e.snapshot.value,
8049
- hasValue: e.snapshot.hasValue,
8050
- error,
8051
- isLoading: false,
8052
- requestId
8053
- });
8054
- this.notify(e);
8055
- this.dropIfSettled(bucketKey, e);
8056
- }
8057
- /**
8058
- * Update loading flag. Gated on `requestId` to fix the race where an old
8059
- * load() finishes after a new one started — its `setLoading(false)` would
8060
- * otherwise hide the new request's spinner.
8061
- */
8062
- setLoading(bucketKey, requestId, isLoading) {
8063
- const e = this.entries.get(bucketKey);
8064
- if (!e || requestId !== e.latestRequestId) return;
8065
- if (e.snapshot.isLoading === isLoading) return;
8066
- e.snapshot = Object.freeze({
8067
- ...e.snapshot,
8068
- isLoading
8069
- });
8070
- this.notify(e);
8071
- }
8072
- /**
8073
- * Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
8074
- * `load()` whose promise is still pending will fail its gate when it resolves
8075
- * and be dropped — prevents pre-navigation loads from clobbering the new
8076
- * route's context. The entry itself is kept (sticky-bucket semantics).
8077
- */
8078
- clear(bucketKey) {
8079
- const e = this.entries.get(bucketKey);
8080
- if (!e) return;
8081
- e.latestRequestId++;
8082
- if (e.snapshot === EMPTY_SNAPSHOT) return;
8083
- e.snapshot = EMPTY_SNAPSHOT;
8084
- this.notify(e);
8085
- }
8086
- /**
8087
- * Reset every sticky bucket belonging to `loaderId`. Called on navigation /
8088
- * route-context change: all route-registered reads of the loader (keyed or
8089
- * not) drop back to seeding from fresh `loaderData`. Ephemeral buckets are
8090
- * intentionally left alone — they are governed by subscriber refcount, so a
8091
- * persistent keyed reader outside the outlet keeps its value across a
8092
- * navigation rather than blanking out.
8093
- */
8094
- clearFamily(loaderId) {
8095
- const fam = this.families.get(loaderId);
8096
- if (!fam) return;
8097
- for (const bucketKey of fam) {
8098
- const e = this.entries.get(bucketKey);
8099
- if (!e || !e.sticky) continue;
8100
- this.clear(bucketKey);
8101
- }
8102
- }
8103
- notify(e) {
8104
- for (const cb of e.listeners) cb();
8105
- }
8106
- /**
8107
- * Register a still-streaming per-loader result for `loaderId`. No-op for a
8108
- * settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
8109
- * and action lanes commit settled chunks, and a { ssr: false } loader is
8110
- * awaited before flush. Released as the stream settles, either way.
8111
- */
8112
- trackPendingStream(loaderId, stream) {
8113
- if (unwrapsSynchronously(stream) || stream.status === "rejected") {
8114
- return;
8115
- }
8116
- let set = this.pendingStreams.get(loaderId);
8117
- if (set?.has(stream)) return;
8118
- if (!set) {
8119
- set = /* @__PURE__ */ new Set();
8120
- this.pendingStreams.set(loaderId, set);
8121
- }
8122
- set.add(stream);
8123
- const release = () => {
8124
- const current = this.pendingStreams.get(loaderId);
8125
- if (!current || !current.delete(stream)) return;
8126
- if (current.size === 0) this.pendingStreams.delete(loaderId);
8127
- };
8128
- stream.then(release, release);
7943
+ return result;
7944
+ }
7945
+ var Meta = createHandle(collectMeta, "__rsc_router_meta__");
7946
+
7947
+ // src/theme/theme-context.ts
7948
+ import { createContext as createContext2, useContext as useContext2 } from "react";
7949
+ var ThemeContext = createContext2(null);
7950
+
7951
+ // src/browser/react/nonce-context.ts
7952
+ import { createContext as createContext3, useContext as useContext3 } from "react";
7953
+ var NonceContext = createContext3(void 0);
7954
+
7955
+ // src/handles/MetaTags.tsx
7956
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7957
+
7958
+ // src/handles/Scripts.tsx
7959
+ import { useState as useState2 } from "react";
7960
+
7961
+ // src/handles/script.ts
7962
+ function validateConfigDev(config) {
7963
+ if (process.env.NODE_ENV === "production") return;
7964
+ const c = config;
7965
+ if (c.src != null && c.children != null) {
7966
+ console.warn(
7967
+ `[Script] A config has both "src" and "children"; they are mutually exclusive \u2014 "src" wins and the inline body is ignored.`
7968
+ );
7969
+ } else if (c.src == null && c.children == null) {
7970
+ console.warn(
7971
+ `[Script] A config has neither "src" nor "children"; it injects nothing.`
7972
+ );
7973
+ } else if (c.src == null && c.id == null) {
7974
+ console.warn(
7975
+ `[Script] An inline script was pushed without an "id" and cannot be deduplicated. Pass an "id" so a layout + child pushing the same script inject it only once.`
7976
+ );
8129
7977
  }
8130
- /** True while any navigation stream for `loaderId` is still pending. */
8131
- isStreamPending(loaderId) {
8132
- return this.pendingStreams.has(loaderId);
7978
+ if (c.async && c.defer) {
7979
+ console.warn(
7980
+ `[Script] A config has both "async" and "defer"; they are mutually exclusive.`
7981
+ );
8133
7982
  }
8134
- /**
8135
- * Register the loader segments of a tree being committed whose data is
8136
- * still a pending promise, then fire `onStreamPending` on every subscriber
8137
- * of every family with a pending stream. MUST run inside the startTransition
8138
- * that commits the tree: the hook answers with a useOptimistic update, which
8139
- * React reverts exactly when that transition commits — so the held reader
8140
- * reports isLoading:true for as long as its data stays on screen and not a
8141
- * frame longer. Outside a transition React reverts an optimistic update on
8142
- * the next render, pinning nothing useful. Registering here rather than in
8143
- * the tree build keeps register + announce in one call; settled chunks
8144
- * (cached/reused segments, forceAwait lanes) are skipped by
8145
- * trackPendingStream.
8146
- */
8147
- announcePendingStreams(segments) {
8148
- for (const segment of segments) {
8149
- if (segment.type === "loader" && segment.loaderId && segment.loaderData instanceof Promise) {
8150
- this.trackPendingStream(segment.loaderId, segment.loaderData);
7983
+ }
7984
+ function collectScripts(segments) {
7985
+ const result = [];
7986
+ const keyToIndex = /* @__PURE__ */ new Map();
7987
+ for (const configs of segments) {
7988
+ for (const config of configs) {
7989
+ validateConfigDev(config);
7990
+ const isAsyncResource = config.src != null && config.async === true;
7991
+ const key = isAsyncResource ? config.src : config.id ?? config.src;
7992
+ if (key === void 0) {
7993
+ result.push(config);
7994
+ continue;
8151
7995
  }
8152
- }
8153
- for (const loaderId of this.pendingStreams.keys()) {
8154
- const fam = this.families.get(loaderId);
8155
- if (!fam) continue;
8156
- for (const bucketKey of fam) {
8157
- const e = this.entries.get(bucketKey);
8158
- if (!e) continue;
8159
- for (const cb of e.streamPendingListeners) cb();
7996
+ const existing = keyToIndex.get(key);
7997
+ if (existing !== void 0) {
7998
+ result[existing] = config;
7999
+ } else {
8000
+ keyToIndex.set(key, result.length);
8001
+ result.push(config);
8160
8002
  }
8161
8003
  }
8162
8004
  }
8163
- /**
8164
- * Test-only escape hatch. Drops every entry. Production code should never
8165
- * call this; the store is process-scoped and lives for the tab's lifetime.
8166
- * @internal
8167
- */
8168
- reset() {
8169
- this.entries.clear();
8170
- this.families.clear();
8171
- this.groups.clear();
8172
- this.pendingStreams.clear();
8173
- }
8174
- };
8175
- var loaderStore = new LoaderStore();
8005
+ return result;
8006
+ }
8007
+ var Script = createHandle(collectScripts, "__rsc_router_script__");
8176
8008
 
8177
- // src/browser/react/use-navigation.ts
8178
- import {
8179
- useContext as useContext5,
8180
- useState as useState2,
8181
- useEffect as useEffect3,
8182
- useOptimistic as useOptimistic2,
8183
- startTransition as startTransition2,
8184
- useRef as useRef2
8185
- } from "react";
8009
+ // src/handles/Scripts.tsx
8010
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
8186
8011
 
8187
- // src/browser/react/use-pathname.ts
8188
- import { useContext as useContext6, useState as useState3, useEffect as useEffect4, useRef as useRef3 } from "react";
8012
+ // src/browser/react/ScrollRestoration.tsx
8013
+ import { useEffect as useEffect2 } from "react";
8189
8014
 
8190
- // src/client-urls/optimistic-location.ts
8015
+ // src/browser/scroll-restoration.ts
8016
+ init_logging();
8017
+
8018
+ // src/outlet-context.ts
8191
8019
  import { createContext as createContext4 } from "react";
8192
- var OptimisticLocationContext = createContext4(null);
8020
+ var OutletContext = createContext4(null);
8193
8021
 
8194
- // src/browser/react/use-search-params.ts
8195
- import {
8196
- useCallback as useCallback2,
8197
- useContext as useContext7,
8198
- useEffect as useEffect5,
8199
- useMemo as useMemo4,
8200
- useRef as useRef4,
8201
- useState as useState4
8202
- } from "react";
8203
-
8204
- // src/browser/react/use-params.ts
8205
- import { useContext as useContext8, useState as useState5, useEffect as useEffect6, useRef as useRef5 } from "react";
8206
- var EMPTY_PARAMS = Object.freeze({});
8022
+ // src/route-content-wrapper.tsx
8023
+ import { Component, Suspense, use } from "react";
8207
8024
 
8208
- // src/browser/react/nonce-context.ts
8209
- import { createContext as createContext5, useContext as useContext9 } from "react";
8210
- var NonceContext = createContext5(void 0);
8025
+ // src/outlet-provider.tsx
8026
+ import { useContext as useContext4, useMemo } from "react";
8027
+ import { jsx as jsx3 } from "react/jsx-runtime";
8211
8028
 
8212
- // src/browser/react/use-action.ts
8213
- import {
8214
- useContext as useContext10,
8215
- useState as useState6,
8216
- useEffect as useEffect7,
8217
- useRef as useRef6,
8218
- useOptimistic as useOptimistic3,
8219
- startTransition as startTransition3
8220
- } from "react";
8029
+ // src/decode-loader-results.ts
8030
+ import { createElement as createElement2 } from "react";
8221
8031
 
8222
- // src/browser/react/use-segments.ts
8223
- import { useContext as useContext11, useState as useState7, useEffect as useEffect8, useRef as useRef7 } from "react";
8032
+ // src/loader-redirect.tsx
8033
+ import { useEffect as useEffect3 } from "react";
8224
8034
 
8225
- // src/browser/react/NavigationProvider.tsx
8226
- import {
8227
- useState as useState10,
8228
- useEffect as useEffect10,
8229
- useLayoutEffect,
8230
- useCallback as useCallback4,
8231
- useMemo as useMemo6,
8232
- useRef as useRef9,
8233
- use as use3
8234
- } from "react";
8035
+ // src/browser/react/use-router.ts
8036
+ import { useContext as useContext6, useMemo as useMemo2 } from "react";
8235
8037
 
8236
- // src/root-error-boundary.tsx
8237
- import { Component as Component2, useState as useState8 } from "react";
8238
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
8038
+ // src/browser/react/use-mount.ts
8039
+ import { useContext as useContext5 } from "react";
8239
8040
 
8240
- // src/theme/ThemeProvider.tsx
8041
+ // src/browser/react/mount-context.ts
8241
8042
  import {
8242
- useCallback as useCallback3,
8243
- useEffect as useEffect9,
8244
- useMemo as useMemo5,
8245
- useState as useState9
8043
+ createContext as createContext5,
8044
+ createElement
8246
8045
  } from "react";
8046
+ var MountContext = createContext5("/");
8247
8047
 
8248
- // src/theme/theme-context.ts
8249
- import { createContext as createContext6, useContext as useContext12 } from "react";
8250
- var ThemeContext = createContext6(null);
8251
-
8252
- // src/theme/constants.ts
8253
- var THEME_COOKIE = {
8254
- maxAge: 60 * 60 * 24 * 365,
8255
- // 1 year
8256
- path: "/",
8257
- sameSite: "lax"
8258
- };
8259
-
8260
- // src/theme/ThemeProvider.tsx
8261
- import { jsx as jsx4 } from "react/jsx-runtime";
8262
-
8263
- // src/browser/react/NavigationProvider.tsx
8048
+ // src/browser/react/use-router.ts
8264
8049
  init_loader();
8265
8050
 
8266
- // src/browser/scroll-restoration.ts
8267
- init_logging();
8268
-
8269
- // src/browser/react/NavigationProvider.tsx
8270
- init_logging();
8271
-
8272
- // src/browser/navigation-store.ts
8273
- init_cache();
8274
- init_rango_state();
8051
+ // src/browser/app-version.ts
8052
+ var currentVersion2;
8053
+ function getAppVersion() {
8054
+ return currentVersion2;
8055
+ }
8275
8056
 
8276
- // src/browser/react/NavigationProvider.tsx
8277
- import { jsx as jsx5 } from "react/jsx-runtime";
8057
+ // src/loader-redirect.tsx
8058
+ init_validate_redirect_origin();
8059
+ init_redirect_origin();
8278
8060
 
8279
- // src/browser/react/Link.tsx
8280
- import {
8281
- forwardRef,
8282
- useCallback as useCallback5,
8283
- useContext as useContext14,
8284
- useEffect as useEffect13,
8285
- useMemo as useMemo7,
8286
- useRef as useRef12,
8287
- useSyncExternalStore
8288
- } from "react";
8061
+ // src/route-content-wrapper.tsx
8062
+ import { jsx as jsx4 } from "react/jsx-runtime";
8289
8063
 
8290
- // src/browser/react/use-link-status.ts
8064
+ // src/use-loader.tsx
8291
8065
  import {
8292
- createContext as createContext7,
8293
- useContext as useContext13,
8294
- useState as useState11,
8295
- useEffect as useEffect11,
8296
- useRef as useRef10,
8297
- useOptimistic as useOptimistic4,
8298
- startTransition as startTransition4
8066
+ isValidElement,
8067
+ startTransition as startTransition2,
8068
+ use as use2,
8069
+ useCallback,
8070
+ useContext as useContext7,
8071
+ useEffect as useEffect4,
8072
+ useMemo as useMemo3,
8073
+ useOptimistic as useOptimistic2,
8074
+ useRef as useRef2,
8075
+ useState as useState3
8299
8076
  } from "react";
8300
- var LinkContext = createContext7(
8301
- null
8302
- );
8303
8077
 
8304
- // src/browser/event-controller.ts
8305
- init_notify_listeners();
8306
- if (typeof Symbol.dispose === "undefined") {
8307
- Symbol.dispose = /* @__PURE__ */ Symbol("Symbol.dispose");
8078
+ // src/thenable-status.ts
8079
+ function unwrapsSynchronously(stream) {
8080
+ const status = stream.status;
8081
+ return status === "fulfilled" || status === "resolved_model" || status === "resolved_module";
8308
8082
  }
8309
- if (typeof Symbol.asyncDispose === "undefined") {
8310
- Symbol.asyncDispose = /* @__PURE__ */ Symbol("Symbol.asyncDispose");
8083
+
8084
+ // src/loader-store.ts
8085
+ var EMPTY_SNAPSHOT = Object.freeze({
8086
+ value: void 0,
8087
+ hasValue: false,
8088
+ error: null,
8089
+ isLoading: false,
8090
+ requestId: 0
8091
+ });
8092
+ function normalizeGroups(group) {
8093
+ if (group === void 0) return [];
8094
+ if (typeof group === "string") return [group];
8095
+ return [...new Set(group)];
8311
8096
  }
8312
- var locationChangeSubscriptions = /* @__PURE__ */ new WeakMap();
8313
- function subscribeToLocationChange(eventController, listener) {
8314
- let subscription = locationChangeSubscriptions.get(eventController);
8315
- if (!subscription) {
8316
- subscription = {
8317
- registrations: /* @__PURE__ */ new Map(),
8318
- notificationVersion: 0,
8319
- unsubscribe: () => {
8320
- }
8321
- };
8322
- locationChangeSubscriptions.set(eventController, subscription);
8323
- const currentSubscription = subscription;
8324
- subscription.unsubscribe = eventController.subscribe(() => {
8325
- const notificationVersion = ++currentSubscription.notificationVersion;
8326
- const nextHref = eventController.getState().location.href;
8327
- notifyListeners(
8328
- [...currentSubscription.registrations],
8329
- ([, registration]) => {
8330
- if (registration.href === nextHref) return;
8331
- registration.href = nextHref;
8332
- registration.listener(nextHref);
8333
- },
8334
- ([token2, registration]) => currentSubscription.registrations.get(token2) === registration,
8335
- () => notificationVersion === currentSubscription.notificationVersion
8336
- );
8337
- });
8097
+ var LoaderStore = class {
8098
+ constructor() {
8099
+ this.entries = /* @__PURE__ */ new Map();
8100
+ /** loader.$$id -> set of bucket keys, so clearFamily() can reach every bucket. */
8101
+ this.families = /* @__PURE__ */ new Map();
8102
+ /** refresh group name -> set of bucket keys, for refreshGroups(). */
8103
+ this.groups = /* @__PURE__ */ new Map();
8104
+ /**
8105
+ * loader.$$id -> per-loader streams a committed navigation is still
8106
+ * receiving. Registered and announced by announcePendingStreams(), which
8107
+ * browser/partial-update.ts calls INSIDE every transition commit, so a
8108
+ * reader whose content is held on screen by that transition pins
8109
+ * `isLoading: true` for the data it is still showing until the transition
8110
+ * lands (use-loader.tsx). Family-keyed on purpose: a persisting layout
8111
+ * reader of the same createLoader the child is re-running is pinned too
8112
+ * (the loader is in flight; not "this segment's copy is being replaced" —
8113
+ * issue #862, working as designed). The new tree's own read of the loader
8114
+ * suspends at the read site and never observes the flag. A Set, not a
8115
+ * flag: overlapping navigations (second click before the first stream
8116
+ * lands) keep the family pending until the LAST stream settles.
8117
+ */
8118
+ this.pendingStreams = /* @__PURE__ */ new Map();
8338
8119
  }
8339
- const token = /* @__PURE__ */ Symbol();
8340
- subscription.registrations.set(token, {
8341
- href: eventController.getState().location.href,
8342
- listener
8343
- });
8344
- let active2 = true;
8345
- return () => {
8346
- if (!active2) return;
8347
- active2 = false;
8348
- subscription.registrations.delete(token);
8349
- if (subscription.registrations.size > 0) return;
8350
- if (locationChangeSubscriptions.get(eventController) === subscription) {
8351
- locationChangeSubscriptions.delete(eventController);
8120
+ getOrCreate(bucketKey) {
8121
+ let e = this.entries.get(bucketKey);
8122
+ if (!e) {
8123
+ e = {
8124
+ snapshot: EMPTY_SNAPSHOT,
8125
+ listeners: /* @__PURE__ */ new Set(),
8126
+ latestRequestId: 0,
8127
+ loaderId: bucketKey,
8128
+ sticky: false,
8129
+ pendingClear: false,
8130
+ clearWhenSettled: false,
8131
+ groups: /* @__PURE__ */ new Map(),
8132
+ refetch: void 0,
8133
+ streamPendingListeners: /* @__PURE__ */ new Set()
8134
+ };
8135
+ this.entries.set(bucketKey, e);
8352
8136
  }
8353
- subscription.unsubscribe();
8354
- };
8355
- }
8356
-
8357
- // src/browser/prefetch/default-strategy.ts
8358
- var hoverNoneQuery = null;
8359
- var defaultStrategy = process.env.NODE_ENV === "production" ? "viewport" : "none";
8360
- function getHoverNoneQuery() {
8361
- if (typeof window === "undefined") return null;
8362
- if (!hoverNoneQuery) {
8363
- hoverNoneQuery = window.matchMedia("(hover: none)");
8137
+ return e;
8364
8138
  }
8365
- return hoverNoneQuery;
8366
- }
8367
- function getDefaultPrefetchStrategy() {
8368
- return defaultStrategy;
8369
- }
8370
- function resolveAdaptiveStrategy(strategy) {
8371
- if (strategy !== "adaptive") return strategy;
8372
- return getHoverNoneQuery()?.matches ? "viewport" : "hover";
8373
- }
8374
- function subscribeToAdaptiveStrategyChange(listener) {
8375
- const query = getHoverNoneQuery();
8376
- if (!query) return () => {
8377
- };
8378
- if (typeof query.addEventListener === "function") {
8379
- query.addEventListener("change", listener);
8380
- return () => query.removeEventListener("change", listener);
8139
+ registerFamily(loaderId, bucketKey) {
8140
+ let fam = this.families.get(loaderId);
8141
+ if (!fam) {
8142
+ fam = /* @__PURE__ */ new Set();
8143
+ this.families.set(loaderId, fam);
8144
+ }
8145
+ fam.add(bucketKey);
8381
8146
  }
8382
- query.addListener(listener);
8383
- return () => query.removeListener(listener);
8384
- }
8385
-
8386
- // src/browser/prefetch/observer.ts
8387
- init_notify_listeners();
8388
- var callbacks = /* @__PURE__ */ new Map();
8389
- var observer = null;
8390
- function getObserver() {
8391
- if (!observer) {
8392
- observer = new IntersectionObserver(
8393
- (entries, currentObserver) => {
8394
- function* intersectingCallbacks() {
8395
- for (const entry of entries) {
8396
- if (entry.isIntersecting) {
8397
- const subscriptions = callbacks.get(entry.target);
8398
- if (subscriptions) {
8399
- currentObserver.unobserve(entry.target);
8400
- callbacks.delete(entry.target);
8401
- for (const [subscription, callback] of subscriptions) {
8402
- yield [subscriptions, subscription, callback];
8403
- }
8404
- }
8405
- }
8406
- }
8407
- }
8408
- notifyListeners(
8409
- intersectingCallbacks(),
8410
- ([, , callback]) => callback(),
8411
- ([subscriptions, subscription]) => subscriptions.has(subscription)
8412
- );
8413
- },
8414
- { rootMargin: "200px" }
8415
- );
8416
- for (const element of callbacks.keys()) observer.observe(element);
8417
- }
8418
- return observer;
8419
- }
8420
- function observeForPrefetch(element, onVisible) {
8421
- if (typeof IntersectionObserver === "undefined") return () => {
8422
- };
8423
- const currentObserver = getObserver();
8424
- let subscriptions = callbacks.get(element);
8425
- if (!subscriptions) {
8426
- subscriptions = /* @__PURE__ */ new Map();
8427
- callbacks.set(element, subscriptions);
8428
- }
8429
- const subscription = /* @__PURE__ */ Symbol();
8430
- subscriptions.set(subscription, onVisible);
8431
- if (subscriptions.size === 1) currentObserver.observe(element);
8432
- return () => {
8433
- const current = callbacks.get(element);
8434
- subscriptions.delete(subscription);
8435
- if (current !== subscriptions) return;
8436
- if (current.size > 0) return;
8437
- callbacks.delete(element);
8438
- observer?.unobserve(element);
8439
- };
8440
- }
8441
-
8442
- // src/browser/link-interceptor.ts
8443
- init_invalidation();
8444
- init_notify_listeners();
8445
- var ELIGIBILITY_ATTRIBUTES = {
8446
- href: "href",
8447
- target: "target",
8448
- download: "download",
8449
- noIntercept: "data-no-intercept",
8450
- linkComponent: "data-link-component",
8451
- external: "data-external",
8452
- prefetch: "data-prefetch",
8453
- prefetchScope: "data-prefetch-scope"
8454
- };
8455
- var ELIGIBILITY_ATTRIBUTE_FILTER = Object.values(ELIGIBILITY_ATTRIBUTES);
8456
- var ELIGIBLE_ANCHOR_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.href}]`;
8457
- var LINK_COMPONENT_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.linkComponent}]`;
8458
- var PREFETCH_SCOPE_OPT_OUT_SELECTOR = `[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="false"],[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="none"]`;
8459
- function isElementNode(value) {
8460
- return typeof value === "object" && value !== null && value.nodeType === 1 && typeof value.matches === "function";
8461
- }
8462
- var prefetchScopeObservers = /* @__PURE__ */ new WeakMap();
8463
- function createPrefetchScopeObserverState(ownerDocument) {
8464
- if (typeof MutationObserver === "undefined" || !ownerDocument.documentElement) {
8465
- return void 0;
8466
- }
8467
- const subscriptions = /* @__PURE__ */ new Map();
8468
- const observer2 = new MutationObserver((mutations) => {
8469
- const affected = /* @__PURE__ */ new Set();
8470
- const collect = (element) => {
8471
- subscriptions.get(element)?.forEach((listener) => affected.add(listener));
8472
- };
8473
- for (const mutation of mutations) {
8474
- if (!isElementNode(mutation.target)) continue;
8475
- collect(mutation.target);
8476
- mutation.target.querySelectorAll(LINK_COMPONENT_SELECTOR).forEach(collect);
8147
+ /**
8148
+ * Subscribe to entry changes for `bucketKey`.
8149
+ * Returns an unsubscribe function. A sticky bucket's entry is kept around even
8150
+ * after the last subscriber leaves so that an in-flight `load()` can still
8151
+ * commit if the consumer remounts. An ephemeral bucket is dropped once its
8152
+ * last subscriber leaves (deferred, see maybeScheduleRefcountClear).
8153
+ */
8154
+ subscribe(bucketKey, cb, options) {
8155
+ const e = this.getOrCreate(bucketKey);
8156
+ const loaderId = options?.loaderId ?? bucketKey;
8157
+ e.loaderId = loaderId;
8158
+ this.registerFamily(loaderId, bucketKey);
8159
+ if (options?.ephemeral !== true) e.sticky = true;
8160
+ const groups = normalizeGroups(options?.group);
8161
+ for (const group of groups) {
8162
+ this.addToGroup(group, bucketKey, e, options?.refetch);
8477
8163
  }
8478
- notifyListeners(affected, (listener) => listener());
8479
- });
8480
- observer2.observe(ownerDocument.documentElement, {
8481
- subtree: true,
8482
- attributes: true,
8483
- attributeFilter: [ELIGIBILITY_ATTRIBUTES.prefetchScope]
8484
- });
8485
- return { observer: observer2, subscriptions };
8486
- }
8487
- function subscribeToPrefetchScopeChange(element, listener) {
8488
- const ownerDocument = element.ownerDocument;
8489
- let state = prefetchScopeObservers.get(ownerDocument);
8490
- if (!state) {
8491
- state = createPrefetchScopeObserverState(ownerDocument);
8492
- if (!state) return () => {
8164
+ e.pendingClear = false;
8165
+ e.clearWhenSettled = false;
8166
+ e.listeners.add(cb);
8167
+ const onStreamPending = options?.onStreamPending;
8168
+ if (onStreamPending) e.streamPendingListeners.add(onStreamPending);
8169
+ return () => {
8170
+ e.listeners.delete(cb);
8171
+ if (onStreamPending) e.streamPendingListeners.delete(onStreamPending);
8172
+ for (const group of groups) this.releaseGroup(group, bucketKey, e);
8173
+ this.maybeScheduleRefcountClear(bucketKey, e);
8493
8174
  };
8494
- prefetchScopeObservers.set(ownerDocument, state);
8495
8175
  }
8496
- let listeners = state.subscriptions.get(element);
8497
- if (!listeners) {
8498
- listeners = /* @__PURE__ */ new Set();
8499
- state.subscriptions.set(element, listeners);
8176
+ addToGroup(group, bucketKey, e, refetch) {
8177
+ e.groups.set(group, (e.groups.get(group) ?? 0) + 1);
8178
+ if (refetch) e.refetch = refetch;
8179
+ let members = this.groups.get(group);
8180
+ if (!members) {
8181
+ members = /* @__PURE__ */ new Set();
8182
+ this.groups.set(group, members);
8183
+ }
8184
+ members.add(bucketKey);
8500
8185
  }
8501
- listeners.add(listener);
8502
- let active2 = true;
8503
- return () => {
8504
- if (!active2) return;
8505
- active2 = false;
8506
- listeners.delete(listener);
8507
- if (listeners.size === 0 && state.subscriptions.get(element) === listeners) {
8508
- state.subscriptions.delete(element);
8186
+ releaseGroup(group, bucketKey, e) {
8187
+ const next = (e.groups.get(group) ?? 0) - 1;
8188
+ if (next > 0) {
8189
+ e.groups.set(group, next);
8190
+ return;
8509
8191
  }
8510
- if (state.subscriptions.size === 0 && prefetchScopeObservers.get(ownerDocument) === state) {
8511
- state.observer.disconnect();
8512
- prefetchScopeObservers.delete(ownerDocument);
8192
+ e.groups.delete(group);
8193
+ const members = this.groups.get(group);
8194
+ if (members) {
8195
+ members.delete(bucketKey);
8196
+ if (members.size === 0) this.groups.delete(group);
8513
8197
  }
8514
- };
8515
- }
8516
- function isHashOnlyNavigationToPath(anchor, pathname) {
8517
- return pathname === window.location.pathname && anchor.search === window.location.search && !!anchor.hash;
8518
- }
8519
- function isHashOnlyNavigation(anchor) {
8520
- return isHashOnlyNavigationToPath(anchor, anchor.pathname);
8521
- }
8522
- function isPrefetchScopeDisabled(element) {
8523
- return typeof element.closest === "function" && element.closest(PREFETCH_SCOPE_OPT_OUT_SELECTOR) !== null;
8524
- }
8525
-
8526
- // src/browser/react/location-state.ts
8527
- import { useState as useState12, useEffect as useEffect12, useRef as useRef11 } from "react";
8528
-
8529
- // src/browser/react/location-state-shared.ts
8530
- function isLocationStateEntry(value) {
8531
- return value !== null && typeof value === "object" && "__rsc_ls_key" in value && "__rsc_ls_value" in value && typeof value.__rsc_ls_key === "string";
8532
- }
8533
- function resolveLocationStateEntries(entries) {
8534
- const result = {};
8535
- for (const entry of entries) {
8536
- result[entry.__rsc_ls_key] = entry.__rsc_ls_lazy ? entry.__rsc_ls_value() : entry.__rsc_ls_value;
8537
8198
  }
8538
- return result;
8539
- }
8540
-
8541
- // src/browser/react/Link.tsx
8542
- init_loader();
8543
- import { jsx as jsx6 } from "react/jsx-runtime";
8544
- var IGNORE_STRATEGY_CHANGES = (_listener) => () => {
8545
- };
8546
- function isExternalUrl(href2, origin) {
8547
- if (href2.startsWith("//")) return true;
8548
- if (href2.startsWith("http://") || href2.startsWith("https://")) {
8549
- if (!origin) return false;
8550
- try {
8551
- return new URL(href2).origin !== origin;
8552
- } catch {
8553
- return false;
8199
+ /** Remove a bucket from every group it belongs to (used when it is dropped). */
8200
+ removeFromAllGroups(bucketKey, e) {
8201
+ for (const group of e.groups.keys()) {
8202
+ const members = this.groups.get(group);
8203
+ if (members) {
8204
+ members.delete(bucketKey);
8205
+ if (members.size === 0) this.groups.delete(group);
8206
+ }
8554
8207
  }
8208
+ e.groups.clear();
8555
8209
  }
8556
- if (/^[a-z][a-z0-9+.-]*:/i.test(href2)) {
8557
- return true;
8210
+ /**
8211
+ * Refresh every currently-mounted bucket tagged with ANY of the given group
8212
+ * names, with a plain GET (no params/body). Accepts a single name or an array
8213
+ * of names. A bucket that belongs to more than one of the named groups is
8214
+ * refreshed exactly once (members are unioned and deduped by bucket key), as
8215
+ * are multiple reads of one bucket. Resolves when all refreshes settle; rejects
8216
+ * with an `AggregateError` of the failures if any member fails — each failing
8217
+ * member also records its error on its own snapshot.
8218
+ */
8219
+ async refreshGroups(groups) {
8220
+ const names = typeof groups === "string" ? [groups] : groups;
8221
+ const buckets = /* @__PURE__ */ new Set();
8222
+ for (const name of names) {
8223
+ const members = this.groups.get(name);
8224
+ if (!members) continue;
8225
+ for (const bucketKey of members) buckets.add(bucketKey);
8226
+ }
8227
+ if (buckets.size === 0) return;
8228
+ const thunks = [];
8229
+ for (const bucketKey of buckets) {
8230
+ const e = this.entries.get(bucketKey);
8231
+ if (!e || e.listeners.size === 0 || !e.refetch) continue;
8232
+ thunks.push(e.refetch);
8233
+ }
8234
+ if (thunks.length === 0) return;
8235
+ const results = await Promise.allSettled(thunks.map((t) => t()));
8236
+ const reasons = results.filter((r) => r.status === "rejected").map((r) => r.reason);
8237
+ if (reasons.length > 0) {
8238
+ const label = names.map((n) => `"${n}"`).join(", ");
8239
+ throw new AggregateError(
8240
+ reasons,
8241
+ `refreshGroups(${label}) had ${reasons.length} failure(s)`
8242
+ );
8243
+ }
8558
8244
  }
8559
- return false;
8560
- }
8561
- var Link = forwardRef(function Link2({
8562
- to,
8563
- replace = false,
8564
- scroll = true,
8565
- reloadDocument = false,
8566
- revalidate: revalidate2,
8567
- prefetch,
8568
- prefetchKey,
8569
- state,
8570
- children,
8571
- onClick,
8572
- ...props
8573
- }, ref) {
8574
- const ctx = useContext14(NavigationStoreContext);
8575
- const storeLocation = ctx?.eventController.getState().location;
8576
- const origin = storeLocation?.origin ?? (typeof window !== "undefined" ? window.location.origin : void 0);
8577
- const isExternal = isExternalUrl(to, origin);
8578
- const resolvedTo = useMemo7(() => {
8579
- if (isExternal) return to;
8580
- const bn = ctx?.basename;
8581
- if (!bn || !to.startsWith("/") || to.startsWith(bn + "/") || to === bn)
8582
- return to;
8583
- return to === "/" ? bn : bn + to;
8584
- }, [to, isExternal, ctx?.basename]);
8585
- const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? getDefaultPrefetchStrategy();
8586
- const resolvedStrategy = useSyncExternalStore(
8587
- configuredStrategy === "adaptive" ? subscribeToAdaptiveStrategyChange : IGNORE_STRATEGY_CHANGES,
8588
- () => resolveAdaptiveStrategy(configuredStrategy),
8589
- () => configuredStrategy === "adaptive" ? "hover" : configuredStrategy
8590
- );
8591
- const internalRef = useRef12(null);
8592
- const setRef = useCallback5(
8593
- (node) => {
8594
- internalRef.current = node;
8595
- if (typeof ref === "function") {
8596
- ref(node);
8597
- } else if (ref) {
8598
- ref.current = node;
8599
- }
8600
- },
8601
- [ref]
8602
- );
8603
- const stateRef = useRef12(state);
8604
- stateRef.current = state;
8605
- const handleClick = useCallback5(
8606
- (e) => {
8607
- onClick?.(e);
8608
- if (e.defaultPrevented) return;
8609
- if (isExternal) return;
8610
- if (reloadDocument) return;
8611
- if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
8612
- if (e.currentTarget.hasAttribute("download"))
8613
- return;
8614
- const target = e.currentTarget.target;
8615
- if (target && target !== "_self") return;
8616
- if (isHashOnlyNavigation(e.currentTarget)) {
8617
- return;
8618
- }
8619
- if (!ctx?.navigate) {
8245
+ /**
8246
+ * Schedule a deferred drop for an ad-hoc (never-sticky) bucket whose last
8247
+ * subscriber just left. Deferred to a microtask so a StrictMode / transition
8248
+ * remount that resubscribes in the same tick cancels it.
8249
+ */
8250
+ maybeScheduleRefcountClear(bucketKey, e) {
8251
+ if (e.listeners.size > 0) return;
8252
+ if (e.sticky) return;
8253
+ if (e.pendingClear) return;
8254
+ e.pendingClear = true;
8255
+ queueMicrotask(() => {
8256
+ if (!e.pendingClear) return;
8257
+ e.pendingClear = false;
8258
+ if (e.listeners.size > 0) return;
8259
+ if (e.snapshot.isLoading) {
8260
+ e.clearWhenSettled = true;
8620
8261
  return;
8621
8262
  }
8622
- e.preventDefault();
8623
- e.stopPropagation();
8624
- const currentState = stateRef.current;
8625
- let resolvedState;
8626
- if (Array.isArray(currentState) && currentState.length > 0 && isLocationStateEntry(currentState[0])) {
8627
- resolvedState = resolveLocationStateEntries(
8628
- currentState
8629
- );
8630
- } else if (typeof currentState === "function") {
8631
- resolvedState = currentState();
8632
- } else if (currentState != null) {
8633
- resolvedState = currentState;
8634
- }
8635
- ctx.navigate(resolvedTo, {
8636
- replace,
8637
- scroll,
8638
- state: resolvedState,
8639
- revalidate: revalidate2
8640
- });
8641
- },
8642
- [
8643
- resolvedTo,
8644
- isExternal,
8645
- reloadDocument,
8646
- replace,
8647
- scroll,
8648
- revalidate2,
8649
- ctx,
8650
- onClick
8651
- ]
8652
- );
8653
- const handleMouseEnter = useCallback5(() => {
8654
- const element = internalRef.current;
8655
- if ((resolvedStrategy === "hover" || resolvedStrategy === "viewport") && !isExternal && ctx?.store && (!element || !isHashOnlyNavigation(element) && !isPrefetchScopeDisabled(element))) {
8656
- const segmentState = ctx.store.getSegmentState();
8657
- prefetchDirect2(
8658
- resolvedTo,
8659
- segmentState.currentSegmentIds,
8660
- getAppVersion(),
8661
- ctx.store.getRouterId?.(),
8662
- prefetchKey
8663
- );
8263
+ this.dropBucket(bucketKey);
8264
+ });
8265
+ }
8266
+ /** Remove a bucket entry entirely and prune it from its family/group indexes. */
8267
+ dropBucket(bucketKey) {
8268
+ const e = this.entries.get(bucketKey);
8269
+ if (!e) return;
8270
+ this.removeFromAllGroups(bucketKey, e);
8271
+ this.entries.delete(bucketKey);
8272
+ const fam = this.families.get(e.loaderId);
8273
+ if (fam) {
8274
+ fam.delete(bucketKey);
8275
+ if (fam.size === 0) this.families.delete(e.loaderId);
8664
8276
  }
8665
- }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
8666
- useEffect13(() => {
8667
- if (isExternal || !ctx?.store) return;
8668
- const isViewport = resolvedStrategy === "viewport";
8669
- const isRender = resolvedStrategy === "render";
8670
- if (!isViewport && !isRender) return;
8671
- const armPrefetch = () => {
8672
- const element2 = internalRef.current;
8673
- if (element2 && (isHashOnlyNavigation(element2) || isPrefetchScopeDisabled(element2))) {
8674
- return () => {
8675
- };
8676
- }
8677
- let cancelled = false;
8678
- let unsubIdle;
8679
- let stopObserving;
8680
- const triggerPrefetch = () => {
8681
- if (cancelled) return;
8682
- const currentElement = internalRef.current;
8683
- if (currentElement && isPrefetchScopeDisabled(currentElement)) return;
8684
- const segmentState = ctx.store.getSegmentState();
8685
- prefetchQueued2(
8686
- resolvedTo,
8687
- segmentState.currentSegmentIds,
8688
- getAppVersion(),
8689
- ctx.store.getRouterId?.(),
8690
- prefetchKey
8691
- );
8692
- };
8693
- if (isRender) {
8694
- unsubIdle = schedulePrefetchWhenRouterIdle(
8695
- ctx.eventController,
8696
- triggerPrefetch
8697
- );
8698
- } else {
8699
- const viewportElement = internalRef.current;
8700
- if (viewportElement) {
8701
- stopObserving = observeForPrefetch(viewportElement, () => {
8702
- unsubIdle = schedulePrefetchWhenRouterIdle(
8703
- ctx.eventController,
8704
- triggerPrefetch
8705
- );
8706
- });
8707
- }
8708
- }
8709
- return () => {
8710
- cancelled = true;
8711
- unsubIdle?.();
8712
- stopObserving?.();
8713
- };
8277
+ }
8278
+ /** Drop an ephemeral bucket that settled with no subscribers left. */
8279
+ dropIfSettled(bucketKey, e) {
8280
+ if (e.clearWhenSettled && e.listeners.size === 0) {
8281
+ e.clearWhenSettled = false;
8282
+ this.dropBucket(bucketKey);
8283
+ }
8284
+ }
8285
+ /**
8286
+ * Returns the current snapshot for `bucketKey`. Stable reference between
8287
+ * mutations — subscribers rely on this to avoid spurious re-renders.
8288
+ * Returns `EMPTY_SNAPSHOT` (a singleton) when the entry has never been
8289
+ * mutated or has been cleared.
8290
+ */
8291
+ getSnapshot(bucketKey) {
8292
+ return this.entries.get(bucketKey)?.snapshot ?? EMPTY_SNAPSHOT;
8293
+ }
8294
+ /**
8295
+ * Reserve a fresh request id for an upcoming `load()` call. The returned id
8296
+ * is the new "latest"; any older in-flight requests will fail their gating
8297
+ * check on `finishData` / `finishError` / `finishLoading` and be dropped.
8298
+ *
8299
+ * Callers should follow with `beginRequest(id, requestId)` to flip the
8300
+ * loading flag on AND clear any leftover error from a previous attempt
8301
+ * — the latter matters for `throwOnError: false` consumers, which would
8302
+ * otherwise keep showing the stale error throughout the retry.
8303
+ */
8304
+ reserveRequestId(bucketKey) {
8305
+ const e = this.getOrCreate(bucketKey);
8306
+ e.latestRequestId++;
8307
+ return e.latestRequestId;
8308
+ }
8309
+ /**
8310
+ * Mark the request as in-flight: `isLoading = true`, `error = null`.
8311
+ * Combines the two operations so a retry doesn't render the previous
8312
+ * error during the new request. Gated on `requestId === latestRequestId`
8313
+ * for symmetry with the other mutators.
8314
+ */
8315
+ beginRequest(bucketKey, requestId) {
8316
+ const e = this.entries.get(bucketKey);
8317
+ if (!e || requestId !== e.latestRequestId) return;
8318
+ if (e.snapshot.isLoading && e.snapshot.error === null) return;
8319
+ e.snapshot = Object.freeze({
8320
+ value: e.snapshot.value,
8321
+ hasValue: e.snapshot.hasValue,
8322
+ error: null,
8323
+ isLoading: true,
8324
+ requestId
8325
+ });
8326
+ this.notify(e);
8327
+ }
8328
+ /**
8329
+ * Commit a successful result. No-op if `requestId` is not the latest
8330
+ * (a newer `load()` was issued or `clear()` ran). Clearing `error` is
8331
+ * intentional: a successful refetch should hide the previous failure.
8332
+ */
8333
+ finishData(bucketKey, requestId, value) {
8334
+ const e = this.entries.get(bucketKey);
8335
+ if (!e || requestId !== e.latestRequestId) return;
8336
+ e.snapshot = Object.freeze({
8337
+ value,
8338
+ hasValue: true,
8339
+ error: null,
8340
+ isLoading: false,
8341
+ requestId
8342
+ });
8343
+ this.notify(e);
8344
+ this.dropIfSettled(bucketKey, e);
8345
+ }
8346
+ /**
8347
+ * Commit an error. Preserves the last good `value` so consumers can keep
8348
+ * showing previous data while displaying the error if they choose. No-op
8349
+ * if `requestId` is not the latest.
8350
+ */
8351
+ finishError(bucketKey, requestId, error) {
8352
+ const e = this.entries.get(bucketKey);
8353
+ if (!e || requestId !== e.latestRequestId) return;
8354
+ e.snapshot = Object.freeze({
8355
+ value: e.snapshot.value,
8356
+ hasValue: e.snapshot.hasValue,
8357
+ error,
8358
+ isLoading: false,
8359
+ requestId
8360
+ });
8361
+ this.notify(e);
8362
+ this.dropIfSettled(bucketKey, e);
8363
+ }
8364
+ /**
8365
+ * Update loading flag. Gated on `requestId` to fix the race where an old
8366
+ * load() finishes after a new one started — its `setLoading(false)` would
8367
+ * otherwise hide the new request's spinner.
8368
+ */
8369
+ setLoading(bucketKey, requestId, isLoading) {
8370
+ const e = this.entries.get(bucketKey);
8371
+ if (!e || requestId !== e.latestRequestId) return;
8372
+ if (e.snapshot.isLoading === isLoading) return;
8373
+ e.snapshot = Object.freeze({
8374
+ ...e.snapshot,
8375
+ isLoading
8376
+ });
8377
+ this.notify(e);
8378
+ }
8379
+ /**
8380
+ * Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
8381
+ * `load()` whose promise is still pending will fail its gate when it resolves
8382
+ * and be dropped — prevents pre-navigation loads from clobbering the new
8383
+ * route's context. The entry itself is kept (sticky-bucket semantics).
8384
+ */
8385
+ clear(bucketKey) {
8386
+ const e = this.entries.get(bucketKey);
8387
+ if (!e) return;
8388
+ e.latestRequestId++;
8389
+ if (e.snapshot === EMPTY_SNAPSHOT) return;
8390
+ e.snapshot = EMPTY_SNAPSHOT;
8391
+ this.notify(e);
8392
+ }
8393
+ /**
8394
+ * Reset every sticky bucket belonging to `loaderId`. Called on navigation /
8395
+ * route-context change: all route-registered reads of the loader (keyed or
8396
+ * not) drop back to seeding from fresh `loaderData`. Ephemeral buckets are
8397
+ * intentionally left alone — they are governed by subscriber refcount, so a
8398
+ * persistent keyed reader outside the outlet keeps its value across a
8399
+ * navigation rather than blanking out.
8400
+ */
8401
+ clearFamily(loaderId) {
8402
+ const fam = this.families.get(loaderId);
8403
+ if (!fam) return;
8404
+ for (const bucketKey of fam) {
8405
+ const e = this.entries.get(bucketKey);
8406
+ if (!e || !e.sticky) continue;
8407
+ this.clear(bucketKey);
8408
+ }
8409
+ }
8410
+ notify(e) {
8411
+ for (const cb of e.listeners) cb();
8412
+ }
8413
+ /**
8414
+ * Register a still-streaming per-loader result for `loaderId`. No-op for a
8415
+ * settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
8416
+ * and action lanes commit settled chunks, and a { ssr: false } loader is
8417
+ * awaited before flush. Released as the stream settles, either way.
8418
+ */
8419
+ trackPendingStream(loaderId, stream) {
8420
+ if (unwrapsSynchronously(stream) || stream.status === "rejected") {
8421
+ return;
8422
+ }
8423
+ let set = this.pendingStreams.get(loaderId);
8424
+ if (set?.has(stream)) return;
8425
+ if (!set) {
8426
+ set = /* @__PURE__ */ new Set();
8427
+ this.pendingStreams.set(loaderId, set);
8428
+ }
8429
+ set.add(stream);
8430
+ const release = () => {
8431
+ const current = this.pendingStreams.get(loaderId);
8432
+ if (!current || !current.delete(stream)) return;
8433
+ if (current.size === 0) this.pendingStreams.delete(loaderId);
8714
8434
  };
8715
- let disarmPrefetch = armPrefetch();
8716
- const element = internalRef.current;
8717
- const unsubscribeScope = element ? subscribeToPrefetchScopeChange(element, () => {
8718
- disarmPrefetch();
8719
- disarmPrefetch = armPrefetch();
8720
- }) : void 0;
8721
- const unsubscribeLocation = subscribeToLocationChange(
8722
- ctx.eventController,
8723
- () => {
8724
- disarmPrefetch();
8725
- disarmPrefetch = armPrefetch();
8435
+ stream.then(release, release);
8436
+ }
8437
+ /** True while any navigation stream for `loaderId` is still pending. */
8438
+ isStreamPending(loaderId) {
8439
+ return this.pendingStreams.has(loaderId);
8440
+ }
8441
+ /**
8442
+ * Register the loader segments of a tree being committed whose data is
8443
+ * still a pending promise, then fire `onStreamPending` on every subscriber
8444
+ * of every family with a pending stream. MUST run inside the startTransition
8445
+ * that commits the tree: the hook answers with a useOptimistic update, which
8446
+ * React reverts exactly when that transition commits — so the held reader
8447
+ * reports isLoading:true for as long as its data stays on screen and not a
8448
+ * frame longer. Outside a transition React reverts an optimistic update on
8449
+ * the next render, pinning nothing useful. Registering here rather than in
8450
+ * the tree build keeps register + announce in one call; settled chunks
8451
+ * (cached/reused segments, forceAwait lanes) are skipped by
8452
+ * trackPendingStream.
8453
+ */
8454
+ announcePendingStreams(segments) {
8455
+ for (const segment of segments) {
8456
+ if (segment.type === "loader" && segment.loaderId && segment.loaderData instanceof Promise) {
8457
+ this.trackPendingStream(segment.loaderId, segment.loaderData);
8458
+ }
8459
+ }
8460
+ for (const loaderId of this.pendingStreams.keys()) {
8461
+ const fam = this.families.get(loaderId);
8462
+ if (!fam) continue;
8463
+ for (const bucketKey of fam) {
8464
+ const e = this.entries.get(bucketKey);
8465
+ if (!e) continue;
8466
+ for (const cb of e.streamPendingListeners) cb();
8726
8467
  }
8727
- );
8728
- return () => {
8729
- unsubscribeScope?.();
8730
- unsubscribeLocation();
8731
- disarmPrefetch();
8732
- };
8733
- }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
8734
- return /* @__PURE__ */ jsx6(
8735
- "a",
8736
- {
8737
- ref: setRef,
8738
- href: resolvedTo,
8739
- onClick: handleClick,
8740
- onMouseEnter: handleMouseEnter,
8741
- "data-link-component": true,
8742
- "data-external": isExternal ? "" : void 0,
8743
- "data-scroll": scroll === false ? "false" : void 0,
8744
- "data-replace": replace ? "true" : void 0,
8745
- "data-revalidate": revalidate2 === false ? "false" : void 0,
8746
- ...props,
8747
- children: /* @__PURE__ */ jsx6(LinkContext.Provider, { value: resolvedTo, children })
8748
8468
  }
8749
- );
8750
- });
8469
+ }
8470
+ /**
8471
+ * Test-only escape hatch. Drops every entry. Production code should never
8472
+ * call this; the store is process-scoped and lives for the tab's lifetime.
8473
+ * @internal
8474
+ */
8475
+ reset() {
8476
+ this.entries.clear();
8477
+ this.families.clear();
8478
+ this.groups.clear();
8479
+ this.pendingStreams.clear();
8480
+ }
8481
+ };
8482
+ var loaderStore = new LoaderStore();
8751
8483
 
8752
- // src/browser/react/ScrollRestoration.tsx
8753
- import { useEffect as useEffect14 } from "react";
8484
+ // src/browser/react/use-navigation.ts
8485
+ import {
8486
+ useContext as useContext8,
8487
+ useState as useState4,
8488
+ useEffect as useEffect5,
8489
+ useOptimistic as useOptimistic3,
8490
+ startTransition as startTransition3,
8491
+ useRef as useRef3
8492
+ } from "react";
8754
8493
 
8755
- // src/browser/react/use-handle.ts
8494
+ // src/browser/react/use-pathname.ts
8495
+ import { useContext as useContext9, useState as useState5, useEffect as useEffect6, useRef as useRef4 } from "react";
8496
+
8497
+ // src/client-urls/optimistic-location.ts
8498
+ import { createContext as createContext6 } from "react";
8499
+ var OptimisticLocationContext = createContext6(null);
8500
+
8501
+ // src/browser/react/use-search-params.ts
8502
+ import {
8503
+ useCallback as useCallback2,
8504
+ useContext as useContext10,
8505
+ useEffect as useEffect7,
8506
+ useMemo as useMemo4,
8507
+ useRef as useRef5,
8508
+ useState as useState6
8509
+ } from "react";
8510
+
8511
+ // src/browser/react/use-params.ts
8512
+ import { useContext as useContext11, useState as useState7, useEffect as useEffect8, useRef as useRef6 } from "react";
8513
+ var EMPTY_PARAMS = Object.freeze({});
8514
+
8515
+ // src/browser/react/use-action.ts
8516
+ import {
8517
+ useContext as useContext12,
8518
+ useState as useState8,
8519
+ useEffect as useEffect9,
8520
+ useRef as useRef7,
8521
+ useOptimistic as useOptimistic4,
8522
+ startTransition as startTransition4
8523
+ } from "react";
8524
+
8525
+ // src/browser/react/use-segments.ts
8526
+ import { useContext as useContext13, useState as useState9, useEffect as useEffect10, useRef as useRef8 } from "react";
8527
+
8528
+ // src/browser/react/NavigationProvider.tsx
8529
+ import {
8530
+ useState as useState12,
8531
+ useEffect as useEffect12,
8532
+ useLayoutEffect,
8533
+ useCallback as useCallback4,
8534
+ useMemo as useMemo6,
8535
+ useRef as useRef10,
8536
+ use as use3
8537
+ } from "react";
8538
+
8539
+ // src/root-error-boundary.tsx
8540
+ import { Component as Component2, useState as useState10 } from "react";
8541
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
8542
+
8543
+ // src/theme/ThemeProvider.tsx
8544
+ import {
8545
+ useCallback as useCallback3,
8546
+ useEffect as useEffect11,
8547
+ useMemo as useMemo5,
8548
+ useState as useState11
8549
+ } from "react";
8550
+
8551
+ // src/theme/constants.ts
8552
+ var THEME_COOKIE = {
8553
+ maxAge: 60 * 60 * 24 * 365,
8554
+ // 1 year
8555
+ path: "/",
8556
+ sameSite: "lax"
8557
+ };
8558
+
8559
+ // src/theme/ThemeProvider.tsx
8560
+ import { jsx as jsx6 } from "react/jsx-runtime";
8561
+
8562
+ // src/browser/react/NavigationProvider.tsx
8563
+ init_loader();
8564
+ init_logging();
8565
+
8566
+ // src/browser/navigation-store.ts
8567
+ init_cache();
8568
+ init_rango_state();
8569
+
8570
+ // src/browser/react/NavigationProvider.tsx
8571
+ import { jsx as jsx7 } from "react/jsx-runtime";
8572
+
8573
+ // src/browser/react/Link.tsx
8756
8574
  import {
8575
+ forwardRef,
8576
+ useCallback as useCallback5,
8757
8577
  useContext as useContext15,
8758
- useState as useState13,
8759
8578
  useEffect as useEffect15,
8579
+ useMemo as useMemo7,
8760
8580
  useRef as useRef13,
8581
+ useSyncExternalStore
8582
+ } from "react";
8583
+
8584
+ // src/browser/react/use-link-status.ts
8585
+ import {
8586
+ createContext as createContext7,
8587
+ useContext as useContext14,
8588
+ useState as useState13,
8589
+ useEffect as useEffect13,
8590
+ useRef as useRef11,
8761
8591
  useOptimistic as useOptimistic5,
8762
8592
  startTransition as startTransition5
8763
8593
  } from "react";
8594
+ var LinkContext = createContext7(
8595
+ null
8596
+ );
8764
8597
 
8765
- // src/missing-id-error.ts
8766
- var SELF_FILES = /* @__PURE__ */ new Set([
8767
- "missing-id-error",
8768
- "loader",
8769
- "loader.rsc",
8770
- "handle"
8771
- ]);
8772
- function findUserCallSite() {
8773
- try {
8774
- const stack = new Error().stack;
8775
- if (!stack) return void 0;
8776
- for (const frame of stack.split("\n").slice(1)) {
8777
- const m = frame.match(
8778
- /(?:\(|@|\s)(?:file:\/\/)?((?:\/|[A-Za-z]:[\\/])[^()\s]+?\.(?:ts|tsx|js|jsx|mts|cts)):(\d+):(\d+)\)?/
8779
- );
8780
- if (!m) continue;
8781
- const path6 = m[1];
8782
- if (path6.includes("node_modules") || path6.includes("@rangojs/router")) {
8783
- continue;
8598
+ // src/browser/event-controller.ts
8599
+ init_notify_listeners();
8600
+ if (typeof Symbol.dispose === "undefined") {
8601
+ Symbol.dispose = /* @__PURE__ */ Symbol("Symbol.dispose");
8602
+ }
8603
+ if (typeof Symbol.asyncDispose === "undefined") {
8604
+ Symbol.asyncDispose = /* @__PURE__ */ Symbol("Symbol.asyncDispose");
8605
+ }
8606
+ var locationChangeSubscriptions = /* @__PURE__ */ new WeakMap();
8607
+ function subscribeToLocationChange(eventController, listener) {
8608
+ let subscription = locationChangeSubscriptions.get(eventController);
8609
+ if (!subscription) {
8610
+ subscription = {
8611
+ registrations: /* @__PURE__ */ new Map(),
8612
+ notificationVersion: 0,
8613
+ unsubscribe: () => {
8784
8614
  }
8785
- const base = path6.split(/[\\/]/).pop().replace(/\.(?:ts|tsx|js|jsx|mts|cts)$/, "");
8786
- if (SELF_FILES.has(base)) continue;
8787
- return `${path6}:${m[2]}:${m[3]}`;
8615
+ };
8616
+ locationChangeSubscriptions.set(eventController, subscription);
8617
+ const currentSubscription = subscription;
8618
+ subscription.unsubscribe = eventController.subscribe(() => {
8619
+ const notificationVersion = ++currentSubscription.notificationVersion;
8620
+ const nextHref = eventController.getState().location.href;
8621
+ notifyListeners(
8622
+ [...currentSubscription.registrations],
8623
+ ([, registration]) => {
8624
+ if (registration.href === nextHref) return;
8625
+ registration.href = nextHref;
8626
+ registration.listener(nextHref);
8627
+ },
8628
+ ([token2, registration]) => currentSubscription.registrations.get(token2) === registration,
8629
+ () => notificationVersion === currentSubscription.notificationVersion
8630
+ );
8631
+ });
8632
+ }
8633
+ const token = /* @__PURE__ */ Symbol();
8634
+ subscription.registrations.set(token, {
8635
+ href: eventController.getState().location.href,
8636
+ listener
8637
+ });
8638
+ let active2 = true;
8639
+ return () => {
8640
+ if (!active2) return;
8641
+ active2 = false;
8642
+ subscription.registrations.delete(token);
8643
+ if (subscription.registrations.size > 0) return;
8644
+ if (locationChangeSubscriptions.get(eventController) === subscription) {
8645
+ locationChangeSubscriptions.delete(eventController);
8788
8646
  }
8789
- } catch {
8647
+ subscription.unsubscribe();
8648
+ };
8649
+ }
8650
+
8651
+ // src/browser/prefetch/default-strategy.ts
8652
+ var hoverNoneQuery = null;
8653
+ var defaultStrategy = process.env.NODE_ENV === "production" ? "viewport" : "none";
8654
+ function getHoverNoneQuery() {
8655
+ if (typeof window === "undefined") return null;
8656
+ if (!hoverNoneQuery) {
8657
+ hoverNoneQuery = window.matchMedia("(hover: none)");
8658
+ }
8659
+ return hoverNoneQuery;
8660
+ }
8661
+ function getDefaultPrefetchStrategy() {
8662
+ return defaultStrategy;
8663
+ }
8664
+ function resolveAdaptiveStrategy(strategy) {
8665
+ if (strategy !== "adaptive") return strategy;
8666
+ return getHoverNoneQuery()?.matches ? "viewport" : "hover";
8667
+ }
8668
+ function subscribeToAdaptiveStrategyChange(listener) {
8669
+ const query = getHoverNoneQuery();
8670
+ if (!query) return () => {
8671
+ };
8672
+ if (typeof query.addEventListener === "function") {
8673
+ query.addEventListener("change", listener);
8674
+ return () => query.removeEventListener("change", listener);
8675
+ }
8676
+ query.addListener(listener);
8677
+ return () => query.removeListener(listener);
8678
+ }
8679
+
8680
+ // src/browser/prefetch/observer.ts
8681
+ init_notify_listeners();
8682
+ var callbacks = /* @__PURE__ */ new Map();
8683
+ var observer = null;
8684
+ function getObserver() {
8685
+ if (!observer) {
8686
+ observer = new IntersectionObserver(
8687
+ (entries, currentObserver) => {
8688
+ function* intersectingCallbacks() {
8689
+ for (const entry of entries) {
8690
+ if (entry.isIntersecting) {
8691
+ const subscriptions = callbacks.get(entry.target);
8692
+ if (subscriptions) {
8693
+ currentObserver.unobserve(entry.target);
8694
+ callbacks.delete(entry.target);
8695
+ for (const [subscription, callback] of subscriptions) {
8696
+ yield [subscriptions, subscription, callback];
8697
+ }
8698
+ }
8699
+ }
8700
+ }
8701
+ }
8702
+ notifyListeners(
8703
+ intersectingCallbacks(),
8704
+ ([, , callback]) => callback(),
8705
+ ([subscriptions, subscription]) => subscriptions.has(subscription)
8706
+ );
8707
+ },
8708
+ { rootMargin: "200px" }
8709
+ );
8710
+ for (const element of callbacks.keys()) observer.observe(element);
8790
8711
  }
8791
- return void 0;
8792
- }
8793
- function missingInjectedIdError(kind, fnName) {
8794
- const site = findUserCallSite();
8795
- const at = site ? ` (created at ${site})` : "";
8796
- return new Error(
8797
- `[rango] ${kind} is missing $$id${at}.
8798
- The @rangojs/router:expose-internal-ids Vite transform injects ${fnName}()'s stable $$id from an EXPORTED const declaration only:
8799
- export const X = ${fnName}(...)
8800
- const X = ${fnName}(...); export { X }
8801
- A non-exported const, an \`export let/var\`, or an inline ${fnName}(...) call gets no $$id \u2014 export it as \`export const\`. (A matching "Unsupported ${fnName} shape" warning names the exact file:line.)`
8802
- );
8803
- }
8804
-
8805
- // src/handle.ts
8806
- function defaultCollect(segments) {
8807
- return segments;
8712
+ return observer;
8808
8713
  }
8809
- var collectRegistry = /* @__PURE__ */ new Map();
8810
- var runtimeHandleIdCounter = 0;
8811
- function createHandle(collect, __injectedId) {
8812
- let handleId = __injectedId ?? "";
8813
- if (!handleId) {
8814
- if (isUnderTestRunner()) {
8815
- handleId = `__rango_runtime_handle_${runtimeHandleIdCounter++}`;
8816
- } else if (process.env.NODE_ENV !== "production") {
8817
- throw missingInjectedIdError("Handle", "createHandle");
8818
- } else {
8819
- throw new Error(
8820
- "[rango] Handle is missing $$id \u2014 the build plugin did not inject one. Export it as `export const X = createHandle(...)`."
8821
- );
8822
- }
8714
+ function observeForPrefetch(element, onVisible) {
8715
+ if (typeof IntersectionObserver === "undefined") return () => {
8716
+ };
8717
+ const currentObserver = getObserver();
8718
+ let subscriptions = callbacks.get(element);
8719
+ if (!subscriptions) {
8720
+ subscriptions = /* @__PURE__ */ new Map();
8721
+ callbacks.set(element, subscriptions);
8823
8722
  }
8824
- const collectFn = collect ?? defaultCollect;
8825
- collectRegistry.set(
8826
- handleId,
8827
- collectFn
8828
- );
8829
- return {
8830
- __brand: "handle",
8831
- $$id: handleId
8723
+ const subscription = /* @__PURE__ */ Symbol();
8724
+ subscriptions.set(subscription, onVisible);
8725
+ if (subscriptions.size === 1) currentObserver.observe(element);
8726
+ return () => {
8727
+ const current = callbacks.get(element);
8728
+ subscriptions.delete(subscription);
8729
+ if (current !== subscriptions) return;
8730
+ if (current.size > 0) return;
8731
+ callbacks.delete(element);
8732
+ observer?.unobserve(element);
8832
8733
  };
8833
8734
  }
8834
8735
 
8835
- // src/handles/meta.ts
8836
- function isUnsetDescriptor(descriptor) {
8837
- return typeof descriptor === "object" && descriptor !== null && "unset" in descriptor && typeof descriptor.unset === "string";
8838
- }
8839
- function isTitleDescriptor(descriptor) {
8840
- return typeof descriptor === "object" && descriptor !== null && "title" in descriptor;
8841
- }
8842
- function isTitleTemplate(title) {
8843
- return typeof title === "object" && title !== null && "template" in title && "default" in title;
8844
- }
8845
- function isAbsoluteTitle(title) {
8846
- return typeof title === "object" && title !== null && "absolute" in title;
8736
+ // src/browser/link-interceptor.ts
8737
+ init_invalidation();
8738
+ init_notify_listeners();
8739
+ var ELIGIBILITY_ATTRIBUTES = {
8740
+ href: "href",
8741
+ target: "target",
8742
+ download: "download",
8743
+ noIntercept: "data-no-intercept",
8744
+ linkComponent: "data-link-component",
8745
+ external: "data-external",
8746
+ prefetch: "data-prefetch",
8747
+ prefetchScope: "data-prefetch-scope"
8748
+ };
8749
+ var ELIGIBILITY_ATTRIBUTE_FILTER = Object.values(ELIGIBILITY_ATTRIBUTES);
8750
+ var ELIGIBLE_ANCHOR_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.href}]`;
8751
+ var LINK_COMPONENT_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.linkComponent}]`;
8752
+ var PREFETCH_SCOPE_OPT_OUT_SELECTOR = `[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="false"],[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="none"]`;
8753
+ function isElementNode(value) {
8754
+ return typeof value === "object" && value !== null && value.nodeType === 1 && typeof value.matches === "function";
8847
8755
  }
8848
- function getMetaKey(descriptor) {
8849
- if (isUnsetDescriptor(descriptor)) {
8850
- return void 0;
8851
- }
8852
- if ("charSet" in descriptor) {
8853
- return "charSet";
8854
- }
8855
- if ("title" in descriptor) {
8856
- return "title";
8857
- }
8858
- if ("name" in descriptor && "content" in descriptor) {
8859
- return `name:${descriptor.name}`;
8860
- }
8861
- if ("property" in descriptor && "content" in descriptor) {
8862
- return `property:${descriptor.property}`;
8863
- }
8864
- if ("httpEquiv" in descriptor && "content" in descriptor) {
8865
- return `httpEquiv:${descriptor.httpEquiv}`;
8866
- }
8867
- if ("script:ld+json" in descriptor) {
8756
+ var prefetchScopeObservers = /* @__PURE__ */ new WeakMap();
8757
+ function createPrefetchScopeObserverState(ownerDocument) {
8758
+ if (typeof MutationObserver === "undefined" || !ownerDocument.documentElement) {
8868
8759
  return void 0;
8869
8760
  }
8870
- if ("tagName" in descriptor) {
8871
- if (descriptor.tagName === "link" && "rel" in descriptor) {
8872
- const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
8873
- if (uniqueRels.includes(descriptor.rel)) {
8874
- return `link:${descriptor.rel}`;
8875
- }
8761
+ const subscriptions = /* @__PURE__ */ new Map();
8762
+ const observer2 = new MutationObserver((mutations) => {
8763
+ const affected = /* @__PURE__ */ new Set();
8764
+ const collect = (element) => {
8765
+ subscriptions.get(element)?.forEach((listener) => affected.add(listener));
8766
+ };
8767
+ for (const mutation of mutations) {
8768
+ if (!isElementNode(mutation.target)) continue;
8769
+ collect(mutation.target);
8770
+ mutation.target.querySelectorAll(LINK_COMPONENT_SELECTOR).forEach(collect);
8876
8771
  }
8877
- return void 0;
8878
- }
8879
- return void 0;
8772
+ notifyListeners(affected, (listener) => listener());
8773
+ });
8774
+ observer2.observe(ownerDocument.documentElement, {
8775
+ subtree: true,
8776
+ attributes: true,
8777
+ attributeFilter: [ELIGIBILITY_ATTRIBUTES.prefetchScope]
8778
+ });
8779
+ return { observer: observer2, subscriptions };
8880
8780
  }
8881
- var defaultMetaDescriptors = [
8882
- { charSet: "utf-8" },
8883
- { name: "viewport", content: "width=device-width, initial-scale=1" }
8884
- ];
8885
- function addOrReplace(result, keyToIndex, descriptor, key) {
8886
- if (key !== void 0 && keyToIndex.has(key)) {
8887
- result[keyToIndex.get(key)] = descriptor;
8888
- } else {
8889
- if (key !== void 0) {
8890
- keyToIndex.set(key, result.length);
8891
- }
8892
- result.push(descriptor);
8781
+ function subscribeToPrefetchScopeChange(element, listener) {
8782
+ const ownerDocument = element.ownerDocument;
8783
+ let state = prefetchScopeObservers.get(ownerDocument);
8784
+ if (!state) {
8785
+ state = createPrefetchScopeObserverState(ownerDocument);
8786
+ if (!state) return () => {
8787
+ };
8788
+ prefetchScopeObservers.set(ownerDocument, state);
8893
8789
  }
8894
- }
8895
- function updateIndicesAfterRemoval(keyToIndex, removedIndex) {
8896
- for (const [key, index] of keyToIndex) {
8897
- if (index > removedIndex) {
8898
- keyToIndex.set(key, index - 1);
8899
- }
8790
+ let listeners = state.subscriptions.get(element);
8791
+ if (!listeners) {
8792
+ listeners = /* @__PURE__ */ new Set();
8793
+ state.subscriptions.set(element, listeners);
8900
8794
  }
8901
- }
8902
- function collectMeta(segments) {
8903
- const result = [];
8904
- const keyToIndex = /* @__PURE__ */ new Map();
8905
- let titleTemplate;
8906
- for (const descriptor of defaultMetaDescriptors) {
8907
- const key = getMetaKey(descriptor);
8908
- if (key !== void 0) {
8909
- keyToIndex.set(key, result.length);
8795
+ listeners.add(listener);
8796
+ let active2 = true;
8797
+ return () => {
8798
+ if (!active2) return;
8799
+ active2 = false;
8800
+ listeners.delete(listener);
8801
+ if (listeners.size === 0 && state.subscriptions.get(element) === listeners) {
8802
+ state.subscriptions.delete(element);
8910
8803
  }
8911
- result.push(descriptor);
8912
- }
8913
- for (const descriptors of segments) {
8914
- for (const descriptor of descriptors) {
8915
- if (isUnsetDescriptor(descriptor)) {
8916
- const keyToRemove = descriptor.unset;
8917
- if (keyToIndex.has(keyToRemove)) {
8918
- const idx = keyToIndex.get(keyToRemove);
8919
- result.splice(idx, 1);
8920
- keyToIndex.delete(keyToRemove);
8921
- updateIndicesAfterRemoval(keyToIndex, idx);
8922
- }
8923
- continue;
8924
- }
8925
- if (isTitleDescriptor(descriptor)) {
8926
- const titleValue = descriptor.title;
8927
- if (isTitleTemplate(titleValue)) {
8928
- titleTemplate = titleValue.template;
8929
- addOrReplace(
8930
- result,
8931
- keyToIndex,
8932
- { title: titleValue.default },
8933
- "title"
8934
- );
8935
- continue;
8936
- }
8937
- if (isAbsoluteTitle(titleValue)) {
8938
- addOrReplace(
8939
- result,
8940
- keyToIndex,
8941
- { title: titleValue.absolute },
8942
- "title"
8943
- );
8944
- continue;
8945
- }
8946
- const finalTitle = titleTemplate ? titleTemplate.split("%s").join(titleValue) : titleValue;
8947
- addOrReplace(
8948
- result,
8949
- keyToIndex,
8950
- { title: finalTitle },
8951
- "title"
8952
- );
8953
- continue;
8954
- }
8955
- const key = getMetaKey(descriptor);
8956
- addOrReplace(result, keyToIndex, descriptor, key);
8804
+ if (state.subscriptions.size === 0 && prefetchScopeObservers.get(ownerDocument) === state) {
8805
+ state.observer.disconnect();
8806
+ prefetchScopeObservers.delete(ownerDocument);
8957
8807
  }
8808
+ };
8809
+ }
8810
+ function isHashOnlyNavigationToPath(anchor, pathname) {
8811
+ return pathname === window.location.pathname && anchor.search === window.location.search && !!anchor.hash;
8812
+ }
8813
+ function isHashOnlyNavigation(anchor) {
8814
+ return isHashOnlyNavigationToPath(anchor, anchor.pathname);
8815
+ }
8816
+ function isPrefetchScopeDisabled(element) {
8817
+ return typeof element.closest === "function" && element.closest(PREFETCH_SCOPE_OPT_OUT_SELECTOR) !== null;
8818
+ }
8819
+
8820
+ // src/browser/react/location-state.ts
8821
+ import { useState as useState14, useEffect as useEffect14, useRef as useRef12 } from "react";
8822
+
8823
+ // src/browser/react/location-state-shared.ts
8824
+ function isLocationStateEntry(value) {
8825
+ return value !== null && typeof value === "object" && "__rsc_ls_key" in value && "__rsc_ls_value" in value && typeof value.__rsc_ls_key === "string";
8826
+ }
8827
+ function resolveLocationStateEntries(entries) {
8828
+ const result = {};
8829
+ for (const entry of entries) {
8830
+ result[entry.__rsc_ls_key] = entry.__rsc_ls_lazy ? entry.__rsc_ls_value() : entry.__rsc_ls_value;
8958
8831
  }
8959
8832
  return result;
8960
8833
  }
8961
- var Meta = createHandle(collectMeta, "__rsc_router_meta__");
8962
-
8963
- // src/handles/MetaTags.tsx
8964
- import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
8965
8834
 
8966
- // src/handles/script.ts
8967
- function validateConfigDev(config) {
8968
- if (process.env.NODE_ENV === "production") return;
8969
- const c = config;
8970
- if (c.src != null && c.children != null) {
8971
- console.warn(
8972
- `[Script] A config has both "src" and "children"; they are mutually exclusive \u2014 "src" wins and the inline body is ignored.`
8973
- );
8974
- } else if (c.src == null && c.children == null) {
8975
- console.warn(
8976
- `[Script] A config has neither "src" nor "children"; it injects nothing.`
8977
- );
8978
- } else if (c.src == null && c.id == null) {
8979
- console.warn(
8980
- `[Script] An inline script was pushed without an "id" and cannot be deduplicated. Pass an "id" so a layout + child pushing the same script inject it only once.`
8981
- );
8835
+ // src/browser/react/Link.tsx
8836
+ init_loader();
8837
+ import { jsx as jsx8 } from "react/jsx-runtime";
8838
+ var IGNORE_STRATEGY_CHANGES = (_listener) => () => {
8839
+ };
8840
+ function isExternalUrl(href2, origin) {
8841
+ if (href2.startsWith("//")) return true;
8842
+ if (href2.startsWith("http://") || href2.startsWith("https://")) {
8843
+ if (!origin) return false;
8844
+ try {
8845
+ return new URL(href2).origin !== origin;
8846
+ } catch {
8847
+ return false;
8848
+ }
8982
8849
  }
8983
- if (c.async && c.defer) {
8984
- console.warn(
8985
- `[Script] A config has both "async" and "defer"; they are mutually exclusive.`
8986
- );
8850
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href2)) {
8851
+ return true;
8987
8852
  }
8853
+ return false;
8988
8854
  }
8989
- function collectScripts(segments) {
8990
- const result = [];
8991
- const keyToIndex = /* @__PURE__ */ new Map();
8992
- for (const configs of segments) {
8993
- for (const config of configs) {
8994
- validateConfigDev(config);
8995
- const isAsyncResource = config.src != null && config.async === true;
8996
- const key = isAsyncResource ? config.src : config.id ?? config.src;
8997
- if (key === void 0) {
8998
- result.push(config);
8999
- continue;
8855
+ var Link = forwardRef(function Link2({
8856
+ to,
8857
+ replace = false,
8858
+ scroll = true,
8859
+ reloadDocument = false,
8860
+ revalidate: revalidate2,
8861
+ prefetch,
8862
+ prefetchKey,
8863
+ state,
8864
+ children,
8865
+ onClick,
8866
+ ...props
8867
+ }, ref) {
8868
+ const ctx = useContext15(NavigationStoreContext);
8869
+ const storeLocation = ctx?.eventController.getState().location;
8870
+ const origin = storeLocation?.origin ?? (typeof window !== "undefined" ? window.location.origin : void 0);
8871
+ const isExternal = isExternalUrl(to, origin);
8872
+ const resolvedTo = useMemo7(() => {
8873
+ if (isExternal) return to;
8874
+ const bn = ctx?.basename;
8875
+ if (!bn || !to.startsWith("/") || to.startsWith(bn + "/") || to === bn)
8876
+ return to;
8877
+ return to === "/" ? bn : bn + to;
8878
+ }, [to, isExternal, ctx?.basename]);
8879
+ const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? getDefaultPrefetchStrategy();
8880
+ const resolvedStrategy = useSyncExternalStore(
8881
+ configuredStrategy === "adaptive" ? subscribeToAdaptiveStrategyChange : IGNORE_STRATEGY_CHANGES,
8882
+ () => resolveAdaptiveStrategy(configuredStrategy),
8883
+ () => configuredStrategy === "adaptive" ? "hover" : configuredStrategy
8884
+ );
8885
+ const internalRef = useRef13(null);
8886
+ const setRef = useCallback5(
8887
+ (node) => {
8888
+ internalRef.current = node;
8889
+ if (typeof ref === "function") {
8890
+ ref(node);
8891
+ } else if (ref) {
8892
+ ref.current = node;
9000
8893
  }
9001
- const existing = keyToIndex.get(key);
9002
- if (existing !== void 0) {
9003
- result[existing] = config;
8894
+ },
8895
+ [ref]
8896
+ );
8897
+ const stateRef = useRef13(state);
8898
+ stateRef.current = state;
8899
+ const handleClick = useCallback5(
8900
+ (e) => {
8901
+ onClick?.(e);
8902
+ if (e.defaultPrevented) return;
8903
+ if (isExternal) return;
8904
+ if (reloadDocument) return;
8905
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
8906
+ if (e.currentTarget.hasAttribute("download"))
8907
+ return;
8908
+ const target = e.currentTarget.target;
8909
+ if (target && target !== "_self") return;
8910
+ if (isHashOnlyNavigation(e.currentTarget)) {
8911
+ return;
8912
+ }
8913
+ if (!ctx?.navigate) {
8914
+ return;
8915
+ }
8916
+ e.preventDefault();
8917
+ e.stopPropagation();
8918
+ const currentState = stateRef.current;
8919
+ let resolvedState;
8920
+ if (Array.isArray(currentState) && currentState.length > 0 && isLocationStateEntry(currentState[0])) {
8921
+ resolvedState = resolveLocationStateEntries(
8922
+ currentState
8923
+ );
8924
+ } else if (typeof currentState === "function") {
8925
+ resolvedState = currentState();
8926
+ } else if (currentState != null) {
8927
+ resolvedState = currentState;
8928
+ }
8929
+ ctx.navigate(resolvedTo, {
8930
+ replace,
8931
+ scroll,
8932
+ state: resolvedState,
8933
+ revalidate: revalidate2
8934
+ });
8935
+ },
8936
+ [
8937
+ resolvedTo,
8938
+ isExternal,
8939
+ reloadDocument,
8940
+ replace,
8941
+ scroll,
8942
+ revalidate2,
8943
+ ctx,
8944
+ onClick
8945
+ ]
8946
+ );
8947
+ const handleMouseEnter = useCallback5(() => {
8948
+ const element = internalRef.current;
8949
+ if ((resolvedStrategy === "hover" || resolvedStrategy === "viewport") && !isExternal && ctx?.store && (!element || !isHashOnlyNavigation(element) && !isPrefetchScopeDisabled(element))) {
8950
+ const segmentState = ctx.store.getSegmentState();
8951
+ prefetchDirect2(
8952
+ resolvedTo,
8953
+ segmentState.currentSegmentIds,
8954
+ getAppVersion(),
8955
+ ctx.store.getRouterId?.(),
8956
+ prefetchKey
8957
+ );
8958
+ }
8959
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
8960
+ useEffect15(() => {
8961
+ if (isExternal || !ctx?.store) return;
8962
+ const isViewport = resolvedStrategy === "viewport";
8963
+ const isRender = resolvedStrategy === "render";
8964
+ if (!isViewport && !isRender) return;
8965
+ const armPrefetch = () => {
8966
+ const element2 = internalRef.current;
8967
+ if (element2 && (isHashOnlyNavigation(element2) || isPrefetchScopeDisabled(element2))) {
8968
+ return () => {
8969
+ };
8970
+ }
8971
+ let cancelled = false;
8972
+ let unsubIdle;
8973
+ let stopObserving;
8974
+ const triggerPrefetch = () => {
8975
+ if (cancelled) return;
8976
+ const currentElement = internalRef.current;
8977
+ if (currentElement && isPrefetchScopeDisabled(currentElement)) return;
8978
+ const segmentState = ctx.store.getSegmentState();
8979
+ prefetchQueued2(
8980
+ resolvedTo,
8981
+ segmentState.currentSegmentIds,
8982
+ getAppVersion(),
8983
+ ctx.store.getRouterId?.(),
8984
+ prefetchKey
8985
+ );
8986
+ };
8987
+ if (isRender) {
8988
+ unsubIdle = schedulePrefetchWhenRouterIdle(
8989
+ ctx.eventController,
8990
+ triggerPrefetch
8991
+ );
9004
8992
  } else {
9005
- keyToIndex.set(key, result.length);
9006
- result.push(config);
8993
+ const viewportElement = internalRef.current;
8994
+ if (viewportElement) {
8995
+ stopObserving = observeForPrefetch(viewportElement, () => {
8996
+ unsubIdle = schedulePrefetchWhenRouterIdle(
8997
+ ctx.eventController,
8998
+ triggerPrefetch
8999
+ );
9000
+ });
9001
+ }
9002
+ }
9003
+ return () => {
9004
+ cancelled = true;
9005
+ unsubIdle?.();
9006
+ stopObserving?.();
9007
+ };
9008
+ };
9009
+ let disarmPrefetch = armPrefetch();
9010
+ const element = internalRef.current;
9011
+ const unsubscribeScope = element ? subscribeToPrefetchScopeChange(element, () => {
9012
+ disarmPrefetch();
9013
+ disarmPrefetch = armPrefetch();
9014
+ }) : void 0;
9015
+ const unsubscribeLocation = subscribeToLocationChange(
9016
+ ctx.eventController,
9017
+ () => {
9018
+ disarmPrefetch();
9019
+ disarmPrefetch = armPrefetch();
9007
9020
  }
9021
+ );
9022
+ return () => {
9023
+ unsubscribeScope?.();
9024
+ unsubscribeLocation();
9025
+ disarmPrefetch();
9026
+ };
9027
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
9028
+ return /* @__PURE__ */ jsx8(
9029
+ "a",
9030
+ {
9031
+ ref: setRef,
9032
+ href: resolvedTo,
9033
+ onClick: handleClick,
9034
+ onMouseEnter: handleMouseEnter,
9035
+ "data-link-component": true,
9036
+ "data-external": isExternal ? "" : void 0,
9037
+ "data-scroll": scroll === false ? "false" : void 0,
9038
+ "data-replace": replace ? "true" : void 0,
9039
+ "data-revalidate": revalidate2 === false ? "false" : void 0,
9040
+ ...props,
9041
+ children: /* @__PURE__ */ jsx8(LinkContext.Provider, { value: resolvedTo, children })
9008
9042
  }
9009
- }
9010
- return result;
9011
- }
9012
- var Script = createHandle(collectScripts, "__rsc_router_script__");
9013
-
9014
- // src/handles/Scripts.tsx
9015
- import { useState as useState14 } from "react";
9016
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
9043
+ );
9044
+ });
9017
9045
 
9018
9046
  // src/handles/breadcrumbs.ts
9019
9047
  function collectBreadcrumbs(segments) {
@@ -9105,15 +9133,18 @@ var ALL_USE_ITEM_TYPES = new Set(
9105
9133
  );
9106
9134
 
9107
9135
  // src/server/request-context.ts
9108
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
9136
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
9109
9137
  init_cookie_name();
9110
9138
 
9139
+ // src/server/handle-store.ts
9140
+ var LOADER_SLOT = Object.freeze({ loader: true });
9141
+
9111
9142
  // src/router/router-context.ts
9112
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
9113
- var routerContext = new AsyncLocalStorage2();
9143
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
9144
+ var routerContext = new AsyncLocalStorage3();
9114
9145
 
9115
9146
  // src/server/request-context.ts
9116
- var requestContextStorage = new AsyncLocalStorage3();
9147
+ var requestContextStorage = new AsyncLocalStorage4();
9117
9148
 
9118
9149
  // src/route-definition/redirect.ts
9119
9150
  init_redirect_origin();
@@ -9307,7 +9338,7 @@ function serializeClientUrlPatterns(patterns) {
9307
9338
  // src/vite/discovery/client-urls-projection.ts
9308
9339
  init_client_ref_hashing();
9309
9340
  init_debug();
9310
- var debug11 = createRangoDebugger(NS.discovery);
9341
+ var debug10 = createRangoDebugger(NS.discovery);
9311
9342
  function hasDefaultExport(code) {
9312
9343
  let program;
9313
9344
  try {
@@ -9396,7 +9427,7 @@ async function refreshRecordedClientUrlProjections(state, ssrEnv, serverMod) {
9396
9427
  const projection = serializeClientUrlPatterns(
9397
9428
  definition
9398
9429
  );
9399
- debug11?.(
9430
+ debug10?.(
9400
9431
  "pre-entry refresh %s -> [%s]",
9401
9432
  source,
9402
9433
  projection.routes.map((route2) => route2.pattern).join(", ")
@@ -9478,21 +9509,21 @@ async function discoverClientUrlProjections(state, ssrEnv, serverMod) {
9478
9509
  }
9479
9510
 
9480
9511
  // src/vite/discovery/discover-routers.ts
9481
- var debug12 = createRangoDebugger(NS.discovery);
9512
+ var debug11 = createRangoDebugger(NS.discovery);
9482
9513
  async function discoverRouters(state, rscEnv, ssrEnv) {
9483
9514
  if (!state.resolvedEntryPath) return;
9484
9515
  const serverMod = await timed(
9485
- debug12,
9516
+ debug11,
9486
9517
  "inner: import @rangojs/router/server",
9487
9518
  () => rscEnv.runner.import("@rangojs/router/server")
9488
9519
  );
9489
9520
  await timed(
9490
- debug12,
9521
+ debug11,
9491
9522
  "inner: refresh recorded client URL projections",
9492
9523
  () => refreshRecordedClientUrlProjections(state, ssrEnv, serverMod)
9493
9524
  );
9494
9525
  await timed(
9495
- debug12,
9526
+ debug11,
9496
9527
  "inner: import entry",
9497
9528
  () => rscEnv.runner.import(state.resolvedEntryPath)
9498
9529
  );
@@ -9508,7 +9539,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9508
9539
  const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
9509
9540
  discoveryErrors.push(...handlerErrors);
9510
9541
  for (const { context, error } of handlerErrors) {
9511
- debug12?.("caught error while resolving %s: %O", context, error);
9542
+ debug11?.("caught error while resolving %s: %O", context, error);
9512
9543
  }
9513
9544
  const freshServerMod = await rscEnv.runner.import(
9514
9545
  "@rangojs/router/server"
@@ -9527,17 +9558,17 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9527
9558
  }
9528
9559
  }
9529
9560
  await timed(
9530
- debug12,
9561
+ debug11,
9531
9562
  "inner: discover client URL projections",
9532
9563
  () => discoverClientUrlProjections(state, ssrEnv, serverMod)
9533
9564
  );
9534
9565
  const buildMod = await timed(
9535
- debug12,
9566
+ debug11,
9536
9567
  "inner: import @rangojs/router/build",
9537
9568
  () => rscEnv.runner.import("@rangojs/router/build")
9538
9569
  );
9539
9570
  const generateManifestFull = buildMod.generateManifestFull;
9540
- debug12?.("inner: found %d router(s) in registry", registry.size);
9571
+ debug11?.("inner: found %d router(s) in registry", registry.size);
9541
9572
  const nestedRouterConflict = findNestedRouterConflict(
9542
9573
  [...registry.values()].map((router) => router.__sourceFile).filter(
9543
9574
  (sourceFile) => typeof sourceFile === "string"
@@ -9562,7 +9593,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9562
9593
  collectFallbackClientRefs(node, collectClientFallbackRef);
9563
9594
  }
9564
9595
  };
9565
- const manifestGenStart = debug12 ? performance.now() : 0;
9596
+ const manifestGenStart = debug11 ? performance.now() : 0;
9566
9597
  for (const [id, router] of registry) {
9567
9598
  if (!generateManifestFull) {
9568
9599
  continue;
@@ -9640,12 +9671,12 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9640
9671
  );
9641
9672
  }
9642
9673
  }
9643
- debug12?.(
9674
+ debug11?.(
9644
9675
  "inner: generated manifests for %d router(s) (%sms)",
9645
9676
  allManifests.length,
9646
9677
  (performance.now() - manifestGenStart).toFixed(1)
9647
9678
  );
9648
- const trieStart = debug12 ? performance.now() : 0;
9679
+ const trieStart = debug11 ? performance.now() : 0;
9649
9680
  if (Object.keys(newMergedRouteManifest).length > 0) {
9650
9681
  for (const { id, manifest } of allManifests) {
9651
9682
  const perRouterTrie = buildPerRouterTrie(manifest);
@@ -9654,7 +9685,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9654
9685
  }
9655
9686
  }
9656
9687
  }
9657
- debug12?.(
9688
+ debug11?.(
9658
9689
  "inner: trie build done (%sms)",
9659
9690
  (performance.now() - trieStart).toFixed(1)
9660
9691
  );
@@ -9689,7 +9720,7 @@ function buildShellManifestKey(pathname) {
9689
9720
 
9690
9721
  // src/vite/discovery/shell-prerender-phase.ts
9691
9722
  init_debug();
9692
- var debug13 = createRangoDebugger(NS.prerender);
9723
+ var debug12 = createRangoDebugger(NS.prerender);
9693
9724
  async function runShellPrerenderPhase(s, builder) {
9694
9725
  const tempServer = s.shellPhaseTempServer;
9695
9726
  if (!s.isBuildMode || !s.shellCandidates?.length || !tempServer) return;
@@ -9745,7 +9776,7 @@ async function runShellPrerenderPhase(s, builder) {
9745
9776
  const base = builder?.config?.base ?? "/";
9746
9777
  const normalizedBase = base.endsWith("/") ? base : `${base}/`;
9747
9778
  const bootstrapContent = `import("${normalizedBase}assets/${entryFile}")`;
9748
- debug13?.("shell prerender bootstrap: %s", bootstrapContent);
9779
+ debug12?.("shell prerender bootstrap: %s", bootstrapContent);
9749
9780
  const minimalPlugin = (tempServer.config?.plugins ?? []).find(
9750
9781
  (p) => p?.name === "rsc:minimal"
9751
9782
  );
@@ -9832,7 +9863,7 @@ async function runShellPrerenderPhase(s, builder) {
9832
9863
  buildEnv: s.resolvedBuildEnv,
9833
9864
  buildVersion,
9834
9865
  captureShellHTML,
9835
- debug: !!debug13
9866
+ debug: !!debug12
9836
9867
  });
9837
9868
  if (res.outcome === "route-mismatch") {
9838
9869
  mismatches.push(res.matchedRouteName ?? "(no match)");
@@ -10414,7 +10445,7 @@ function postprocessBundle(state) {
10414
10445
  }
10415
10446
 
10416
10447
  // src/vite/discovery/gate-state.ts
10417
- function createDiscoveryGate(s, debug14) {
10448
+ function createDiscoveryGate(s, debug13) {
10418
10449
  let gatePending = false;
10419
10450
  let gateResolver = () => {
10420
10451
  };
@@ -10431,7 +10462,7 @@ function createDiscoveryGate(s, debug14) {
10431
10462
  const resolveGate = () => {
10432
10463
  if (!gatePending) return;
10433
10464
  if (inProgress || queued2 || pendingEvents) {
10434
- debug14?.(
10465
+ debug13?.(
10435
10466
  "hmr: resolveGate deferred \u2014 work in flight (inProgress=%s queued=%s pendingEvents=%s)",
10436
10467
  inProgress,
10437
10468
  queued2,
@@ -10440,7 +10471,7 @@ function createDiscoveryGate(s, debug14) {
10440
10471
  return;
10441
10472
  }
10442
10473
  gatePending = false;
10443
- debug14?.("hmr: discoveryDone resolved");
10474
+ debug13?.("hmr: discoveryDone resolved");
10444
10475
  gateResolver();
10445
10476
  };
10446
10477
  const noteRouteEvent = () => {
@@ -10450,7 +10481,7 @@ function createDiscoveryGate(s, debug14) {
10450
10481
  const runRefreshCycle = async (work) => {
10451
10482
  if (inProgress) {
10452
10483
  queued2 = true;
10453
- debug14?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
10484
+ debug13?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
10454
10485
  return;
10455
10486
  }
10456
10487
  pendingEvents = false;
@@ -10461,16 +10492,16 @@ function createDiscoveryGate(s, debug14) {
10461
10492
  inProgress = false;
10462
10493
  if (queued2) {
10463
10494
  queued2 = false;
10464
- debug14?.("hmr: consuming queued rediscovery");
10495
+ debug13?.("hmr: consuming queued rediscovery");
10465
10496
  runRefreshCycle(work).catch((err) => {
10466
- debug14?.(
10497
+ debug13?.(
10467
10498
  "hmr: queued cycle rejected \u2014 releasing gate (%s)",
10468
10499
  err instanceof Error ? err.message : String(err)
10469
10500
  );
10470
10501
  resolveGate();
10471
10502
  });
10472
10503
  } else if (pendingEvents) {
10473
- debug14?.(
10504
+ debug13?.(
10474
10505
  "hmr: holding gate for pending events (debounce not yet fired)"
10475
10506
  );
10476
10507
  } else {
@@ -12437,7 +12468,11 @@ If this is a multi-app host router, export a createHostRouter() instance and set
12437
12468
  plugins.push(createLoaderDirectiveGuardPlugin());
12438
12469
  plugins.push(createLoaderScanStubPlugin());
12439
12470
  plugins.push(exposeActionId());
12440
- plugins.push(useCacheTransform());
12471
+ plugins.push(
12472
+ useCacheTransform(
12473
+ createCounter(createRangoDebugger(NS.transform), "use-cache")
12474
+ )
12475
+ );
12441
12476
  plugins.push(exposeInternalIds());
12442
12477
  plugins.push(exposeRouterId());
12443
12478
  plugins.push(createVersionPlugin());
@@ -12481,7 +12516,7 @@ function poke() {
12481
12516
  apply: "serve",
12482
12517
  configureServer(server) {
12483
12518
  const stdin = process.stdin;
12484
- const debug14 = process.env.RANGO_POKE_DEBUG === "1";
12519
+ const debug13 = process.env.RANGO_POKE_DEBUG === "1";
12485
12520
  const triggerReload = (source) => {
12486
12521
  server.hot.send({ type: "full-reload", path: "*" });
12487
12522
  server.config.logger.info(` browser reload (${source})`, {
@@ -12514,7 +12549,7 @@ function poke() {
12514
12549
  lines.pop();
12515
12550
  return lines;
12516
12551
  };
12517
- if (debug14) {
12552
+ if (debug13) {
12518
12553
  server.config.logger.info(
12519
12554
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
12520
12555
  { timestamp: true }
@@ -12527,7 +12562,7 @@ function poke() {
12527
12562
  );
12528
12563
  }
12529
12564
  const onData = (data) => {
12530
- if (debug14) {
12565
+ if (debug13) {
12531
12566
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
12532
12567
  timestamp: true
12533
12568
  });