@rangojs/router 0.16.0 → 0.18.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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -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.18.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,26 +6145,51 @@ 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/rsc/shell-capture-constants.ts
6151
+ var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
6152
+
6153
+ // src/cache/cache-exec-scope.ts
6141
6154
  import { AsyncLocalStorage } from "node:async_hooks";
6155
+
6156
+ // src/cache/taint.ts
6157
+ var cacheExecScopeProbe = () => false;
6158
+ function _setCacheExecScopeProbe(probe) {
6159
+ cacheExecScopeProbe = probe;
6160
+ }
6161
+
6162
+ // src/cache/cache-exec-scope.ts
6163
+ var cacheExecStorage = new AsyncLocalStorage();
6164
+ function isInsideCacheExecScope() {
6165
+ return cacheExecStorage.getStore() !== void 0;
6166
+ }
6167
+ _setCacheExecScopeProbe(isInsideCacheExecScope);
6168
+
6169
+ // src/server/context.ts
6142
6170
  var RSC_CONTEXT_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:rsc-context");
6143
- var RangoContext = globalThis[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage();
6171
+ var RangoContext = globalThis[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage2();
6144
6172
  var LOADER_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-scope");
6145
- var loaderScopeALS = globalThis[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage();
6173
+ var loaderScopeALS = globalThis[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage2();
6146
6174
  var LOADER_BODY_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-body-scope");
6147
- var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage();
6175
+ var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage2();
6176
+ function getLoaderBodyTags() {
6177
+ return loaderBodyScopeALS.getStore()?.tags;
6178
+ }
6179
+ function getCurrentLoaderBodyId() {
6180
+ return loaderBodyScopeALS.getStore()?.loaderId;
6181
+ }
6148
6182
  var PUSH_CALLBACK_SCOPE_KEY = /* @__PURE__ */ Symbol.for(
6149
6183
  "rangojs-router:push-callback-scope"
6150
6184
  );
6151
- var pushCallbackScopeALS = globalThis[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage();
6152
-
6153
- // src/rsc/shell-capture-constants.ts
6154
- var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
6185
+ var pushCallbackScopeALS = globalThis[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage2();
6155
6186
 
6156
6187
  // src/rsc/shell-serve.ts
6157
6188
  var DEV_SHELL_PROBE_TIMEOUT_MS = 1e4;
6158
6189
  function normalizeCaptureTimeout(value) {
6159
6190
  return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.min(value, SHELL_CAPTURE_MAX_WAIT_MS) : void 0;
6160
6191
  }
6192
+ var SHELL_PRELUDE_CHUNK_BYTES = 32 * 1024;
6161
6193
 
6162
6194
  // src/vite/inject-client-debug.ts
6163
6195
  function isRouterInternalDebugId(id) {
@@ -6325,7 +6357,7 @@ init_client_ref_hashing();
6325
6357
  // src/vite/plugins/server-ref-hashing.ts
6326
6358
  init_debug();
6327
6359
  init_client_ref_hashing();
6328
- var debug9 = createRangoDebugger(NS.transform);
6360
+ var debug8 = createRangoDebugger(NS.transform);
6329
6361
  function transformServerRefs(code, projectRoot) {
6330
6362
  if (!code.includes("registerServerReference")) return null;
6331
6363
  let hasReplacement = false;
@@ -6342,7 +6374,7 @@ function transformServerRefs(code, projectRoot) {
6342
6374
  return hasReplacement ? result : null;
6343
6375
  }
6344
6376
  function hashServerRefs(projectRoot) {
6345
- const counter = createCounter(debug9, "hash-server-refs");
6377
+ const counter = createCounter(debug8, "hash-server-refs");
6346
6378
  return {
6347
6379
  name: "@rangojs/router:hash-server-refs",
6348
6380
  enforce: "post",
@@ -7207,11 +7239,11 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
7207
7239
 
7208
7240
  // src/vite/discovery/prerender-collection.ts
7209
7241
  init_debug();
7210
- var debug10 = createRangoDebugger(NS.prerender);
7242
+ var debug9 = createRangoDebugger(NS.prerender);
7211
7243
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7212
7244
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
7213
- const overallStart = debug10 ? performance.now() : 0;
7214
- debug10?.(
7245
+ const overallStart = debug9 ? performance.now() : 0;
7246
+ debug9?.(
7215
7247
  "expandPrerenderRoutes: start (%d router manifest(s))",
7216
7248
  allManifests.length
7217
7249
  );
@@ -7264,7 +7296,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7264
7296
  });
7265
7297
  } else {
7266
7298
  if (def?.getParams) {
7267
- const getParamsStart = debug10 ? performance.now() : 0;
7299
+ const getParamsStart = debug9 ? performance.now() : 0;
7268
7300
  try {
7269
7301
  const buildVars = {};
7270
7302
  const buildEnv = state.resolvedBuildEnv;
@@ -7283,7 +7315,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7283
7315
  }
7284
7316
  };
7285
7317
  const paramsList = await def.getParams(getParamsCtx);
7286
- debug10?.(
7318
+ debug9?.(
7287
7319
  "getParams %s -> %d params (%sms)",
7288
7320
  routeName,
7289
7321
  paramsList.length,
@@ -7355,7 +7387,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7355
7387
  }
7356
7388
  }
7357
7389
  if (entries.length === 0) {
7358
- debug10?.(
7390
+ debug9?.(
7359
7391
  "no prerender entries (done in %sms)",
7360
7392
  (performance.now() - overallStart).toFixed(1)
7361
7393
  );
@@ -7366,7 +7398,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7366
7398
  console.log(
7367
7399
  `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
7368
7400
  );
7369
- debug10?.(
7401
+ debug9?.(
7370
7402
  "prerender loop: %d entries, max concurrency %d",
7371
7403
  entries.length,
7372
7404
  maxConcurrency
@@ -7474,7 +7506,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7474
7506
  console.log(
7475
7507
  `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
7476
7508
  );
7477
- debug10?.(
7509
+ debug9?.(
7478
7510
  "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
7479
7511
  doneCount,
7480
7512
  skipCount,
@@ -7485,8 +7517,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
7485
7517
  async function renderStaticHandlers(state, rscEnv, registry) {
7486
7518
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
7487
7519
  return;
7488
- const overallStart = debug10 ? performance.now() : 0;
7489
- debug10?.(
7520
+ const overallStart = debug9 ? performance.now() : 0;
7521
+ debug9?.(
7490
7522
  "renderStaticHandlers: start (%d static module(s))",
7491
7523
  state.resolvedStaticModules.size
7492
7524
  );
@@ -7576,7 +7608,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
7576
7608
  console.log(
7577
7609
  `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
7578
7610
  );
7579
- debug10?.(
7611
+ debug9?.(
7580
7612
  "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
7581
7613
  staticDone,
7582
7614
  staticSkip,
@@ -7707,1313 +7739,1316 @@ import {
7707
7739
  Suspense as Suspense2
7708
7740
  } from "react";
7709
7741
 
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
7742
+ // src/browser/react/use-handle.ts
7738
7743
  import {
7739
- createContext as createContext3,
7740
- createElement
7744
+ useContext,
7745
+ useState,
7746
+ useEffect,
7747
+ useRef,
7748
+ useOptimistic,
7749
+ startTransition
7741
7750
  } from "react";
7742
- var MountContext = createContext3("/");
7743
7751
 
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;
7752
+ // src/missing-id-error.ts
7753
+ var SELF_FILES = /* @__PURE__ */ new Set([
7754
+ "missing-id-error",
7755
+ "loader",
7756
+ "loader.rsc",
7757
+ "handle"
7758
+ ]);
7759
+ function findUserCallSite() {
7760
+ try {
7761
+ const stack = new Error().stack;
7762
+ if (!stack) return void 0;
7763
+ for (const frame of stack.split("\n").slice(1)) {
7764
+ const m = frame.match(
7765
+ /(?:\(|@|\s)(?:file:\/\/)?((?:\/|[A-Za-z]:[\\/])[^()\s]+?\.(?:ts|tsx|js|jsx|mts|cts)):(\d+):(\d+)\)?/
7766
+ );
7767
+ if (!m) continue;
7768
+ const path6 = m[1];
7769
+ if (path6.includes("node_modules") || path6.includes("@rangojs/router")) {
7770
+ continue;
7771
+ }
7772
+ const base = path6.split(/[\\/]/).pop().replace(/\.(?:ts|tsx|js|jsx|mts|cts)$/, "");
7773
+ if (SELF_FILES.has(base)) continue;
7774
+ return `${path6}:${m[2]}:${m[3]}`;
7775
+ }
7776
+ } catch {
7777
+ }
7778
+ return void 0;
7779
+ }
7780
+ function missingInjectedIdError(kind, fnName) {
7781
+ const site = findUserCallSite();
7782
+ const at = site ? ` (created at ${site})` : "";
7783
+ return new Error(
7784
+ `[rango] ${kind} is missing $$id${at}.
7785
+ The @rangojs/router:expose-internal-ids Vite transform injects ${fnName}()'s stable $$id from an EXPORTED const declaration only:
7786
+ export const X = ${fnName}(...)
7787
+ const X = ${fnName}(...); export { X }
7788
+ 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.)`
7789
+ );
7751
7790
  }
7752
7791
 
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";
7792
+ // src/handle.ts
7793
+ function defaultCollect(segments) {
7794
+ return segments;
7795
+ }
7796
+ var collectRegistry = /* @__PURE__ */ new Map();
7797
+ var runtimeHandleIdCounter = 0;
7798
+ function createHandle(collect, __injectedId) {
7799
+ let handleId = __injectedId ?? "";
7800
+ if (!handleId) {
7801
+ if (isUnderTestRunner()) {
7802
+ handleId = `__rango_runtime_handle_${runtimeHandleIdCounter++}`;
7803
+ } else if (process.env.NODE_ENV !== "production") {
7804
+ throw missingInjectedIdError("Handle", "createHandle");
7805
+ } else {
7806
+ throw new Error(
7807
+ "[rango] Handle is missing $$id \u2014 the build plugin did not inject one. Export it as `export const X = createHandle(...)`."
7808
+ );
7809
+ }
7810
+ }
7811
+ const collectFn = collect ?? defaultCollect;
7812
+ collectRegistry.set(
7813
+ handleId,
7814
+ collectFn
7815
+ );
7816
+ return {
7817
+ __brand: "handle",
7818
+ $$id: handleId
7819
+ };
7820
+ }
7759
7821
 
7760
- // src/use-loader.tsx
7761
- 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,
7770
- useRef,
7771
- useState
7772
- } from "react";
7822
+ // src/browser/react/context.ts
7823
+ import { createContext } from "react";
7824
+ var NavigationStoreContext = createContext(null);
7773
7825
 
7774
- // src/thenable-status.ts
7775
- function unwrapsSynchronously(stream) {
7776
- const status = stream.status;
7777
- return status === "fulfilled" || status === "resolved_model" || status === "resolved_module";
7826
+ // src/handles/meta.ts
7827
+ function isUnsetDescriptor(descriptor) {
7828
+ return typeof descriptor === "object" && descriptor !== null && "unset" in descriptor && typeof descriptor.unset === "string";
7778
7829
  }
7779
-
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)];
7830
+ function isTitleDescriptor(descriptor) {
7831
+ return typeof descriptor === "object" && descriptor !== null && "title" in descriptor;
7792
7832
  }
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();
7833
+ function isTitleTemplate(title) {
7834
+ return typeof title === "object" && title !== null && "template" in title && "default" in title;
7835
+ }
7836
+ function isAbsoluteTitle(title) {
7837
+ return typeof title === "object" && title !== null && "absolute" in title;
7838
+ }
7839
+ function getMetaKey(descriptor) {
7840
+ if (isUnsetDescriptor(descriptor)) {
7841
+ return void 0;
7812
7842
  }
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);
7829
- }
7830
- return e;
7843
+ if ("charSet" in descriptor) {
7844
+ return "charSet";
7831
7845
  }
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);
7846
+ if ("title" in descriptor) {
7847
+ return "title";
7839
7848
  }
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
- };
7849
+ if ("name" in descriptor && "content" in descriptor) {
7850
+ return `name:${descriptor.name}`;
7868
7851
  }
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);
7852
+ if ("property" in descriptor && "content" in descriptor) {
7853
+ return `property:${descriptor.property}`;
7878
7854
  }
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
- }
7855
+ if ("httpEquiv" in descriptor && "content" in descriptor) {
7856
+ return `httpEquiv:${descriptor.httpEquiv}`;
7891
7857
  }
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);
7858
+ if ("script:ld+json" in descriptor) {
7859
+ return void 0;
7860
+ }
7861
+ if ("tagName" in descriptor) {
7862
+ if (descriptor.tagName === "link" && "rel" in descriptor) {
7863
+ const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
7864
+ if (uniqueRels.includes(descriptor.rel)) {
7865
+ return `link:${descriptor.rel}`;
7899
7866
  }
7900
7867
  }
7901
- e.groups.clear();
7868
+ return void 0;
7902
7869
  }
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
- );
7870
+ return void 0;
7871
+ }
7872
+ var defaultMetaDescriptors = [
7873
+ { charSet: "utf-8" },
7874
+ { name: "viewport", content: "width=device-width, initial-scale=1" }
7875
+ ];
7876
+ function addOrReplace(result, keyToIndex, descriptor, key) {
7877
+ if (key !== void 0 && keyToIndex.has(key)) {
7878
+ result[keyToIndex.get(key)] = descriptor;
7879
+ } else {
7880
+ if (key !== void 0) {
7881
+ keyToIndex.set(key, result.length);
7936
7882
  }
7883
+ result.push(descriptor);
7937
7884
  }
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
- });
7885
+ }
7886
+ function updateIndicesAfterRemoval(keyToIndex, removedIndex) {
7887
+ for (const [key, index] of keyToIndex) {
7888
+ if (index > removedIndex) {
7889
+ keyToIndex.set(key, index - 1);
7890
+ }
7958
7891
  }
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);
7892
+ }
7893
+ function collectMeta(segments) {
7894
+ const result = [];
7895
+ const keyToIndex = /* @__PURE__ */ new Map();
7896
+ let titleTemplate;
7897
+ for (const descriptor of defaultMetaDescriptors) {
7898
+ const key = getMetaKey(descriptor);
7899
+ if (key !== void 0) {
7900
+ keyToIndex.set(key, result.length);
7969
7901
  }
7902
+ result.push(descriptor);
7970
7903
  }
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);
7904
+ for (const descriptors of segments) {
7905
+ for (const descriptor of descriptors) {
7906
+ if (isUnsetDescriptor(descriptor)) {
7907
+ const keyToRemove = descriptor.unset;
7908
+ if (keyToIndex.has(keyToRemove)) {
7909
+ const idx = keyToIndex.get(keyToRemove);
7910
+ result.splice(idx, 1);
7911
+ keyToIndex.delete(keyToRemove);
7912
+ updateIndicesAfterRemoval(keyToIndex, idx);
7913
+ }
7914
+ continue;
7915
+ }
7916
+ if (isTitleDescriptor(descriptor)) {
7917
+ const titleValue = descriptor.title;
7918
+ if (isTitleTemplate(titleValue)) {
7919
+ titleTemplate = titleValue.template;
7920
+ addOrReplace(
7921
+ result,
7922
+ keyToIndex,
7923
+ { title: titleValue.default },
7924
+ "title"
7925
+ );
7926
+ continue;
7927
+ }
7928
+ if (isAbsoluteTitle(titleValue)) {
7929
+ addOrReplace(
7930
+ result,
7931
+ keyToIndex,
7932
+ { title: titleValue.absolute },
7933
+ "title"
7934
+ );
7935
+ continue;
7936
+ }
7937
+ const finalTitle = titleTemplate ? titleTemplate.split("%s").join(titleValue) : titleValue;
7938
+ addOrReplace(
7939
+ result,
7940
+ keyToIndex,
7941
+ { title: finalTitle },
7942
+ "title"
7943
+ );
7944
+ continue;
7945
+ }
7946
+ const key = getMetaKey(descriptor);
7947
+ addOrReplace(result, keyToIndex, descriptor, key);
7976
7948
  }
7977
7949
  }
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);
7950
+ return result;
7951
+ }
7952
+ var Meta = createHandle(collectMeta, "__rsc_router_meta__");
7953
+
7954
+ // src/theme/theme-context.ts
7955
+ import { createContext as createContext2, useContext as useContext2 } from "react";
7956
+ var ThemeContext = createContext2(null);
7957
+
7958
+ // src/browser/react/nonce-context.ts
7959
+ import { createContext as createContext3, useContext as useContext3 } from "react";
7960
+ var NonceContext = createContext3(void 0);
7961
+
7962
+ // src/handles/MetaTags.tsx
7963
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7964
+
7965
+ // src/handles/Scripts.tsx
7966
+ import { useState as useState2 } from "react";
7967
+
7968
+ // src/handles/script.ts
7969
+ function validateConfigDev(config) {
7970
+ if (process.env.NODE_ENV === "production") return;
7971
+ const c = config;
7972
+ if (c.src != null && c.children != null) {
7973
+ console.warn(
7974
+ `[Script] A config has both "src" and "children"; they are mutually exclusive \u2014 "src" wins and the inline body is ignored.`
7975
+ );
7976
+ } else if (c.src == null && c.children == null) {
7977
+ console.warn(
7978
+ `[Script] A config has neither "src" nor "children"; it injects nothing.`
7979
+ );
7980
+ } else if (c.src == null && c.id == null) {
7981
+ console.warn(
7982
+ `[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.`
7983
+ );
8129
7984
  }
8130
- /** True while any navigation stream for `loaderId` is still pending. */
8131
- isStreamPending(loaderId) {
8132
- return this.pendingStreams.has(loaderId);
7985
+ if (c.async && c.defer) {
7986
+ console.warn(
7987
+ `[Script] A config has both "async" and "defer"; they are mutually exclusive.`
7988
+ );
8133
7989
  }
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);
7990
+ }
7991
+ function collectScripts(segments) {
7992
+ const result = [];
7993
+ const keyToIndex = /* @__PURE__ */ new Map();
7994
+ for (const configs of segments) {
7995
+ for (const config of configs) {
7996
+ validateConfigDev(config);
7997
+ const isAsyncResource = config.src != null && config.async === true;
7998
+ const key = isAsyncResource ? config.src : config.id ?? config.src;
7999
+ if (key === void 0) {
8000
+ result.push(config);
8001
+ continue;
8151
8002
  }
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();
8003
+ const existing = keyToIndex.get(key);
8004
+ if (existing !== void 0) {
8005
+ result[existing] = config;
8006
+ } else {
8007
+ keyToIndex.set(key, result.length);
8008
+ result.push(config);
8160
8009
  }
8161
8010
  }
8162
8011
  }
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();
8012
+ return result;
8013
+ }
8014
+ var Script = createHandle(collectScripts, "__rsc_router_script__");
8176
8015
 
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";
8016
+ // src/handles/Scripts.tsx
8017
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
8186
8018
 
8187
- // src/browser/react/use-pathname.ts
8188
- import { useContext as useContext6, useState as useState3, useEffect as useEffect4, useRef as useRef3 } from "react";
8019
+ // src/browser/react/ScrollRestoration.tsx
8020
+ import { useEffect as useEffect2 } from "react";
8189
8021
 
8190
- // src/client-urls/optimistic-location.ts
8022
+ // src/browser/scroll-restoration.ts
8023
+ init_logging();
8024
+
8025
+ // src/outlet-context.ts
8191
8026
  import { createContext as createContext4 } from "react";
8192
- var OptimisticLocationContext = createContext4(null);
8027
+ var OutletContext = createContext4(null);
8193
8028
 
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({});
8029
+ // src/route-content-wrapper.tsx
8030
+ import { Component, Suspense, use } from "react";
8207
8031
 
8208
- // src/browser/react/nonce-context.ts
8209
- import { createContext as createContext5, useContext as useContext9 } from "react";
8210
- var NonceContext = createContext5(void 0);
8032
+ // src/outlet-provider.tsx
8033
+ import { useContext as useContext4, useMemo } from "react";
8034
+ import { jsx as jsx3 } from "react/jsx-runtime";
8211
8035
 
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";
8036
+ // src/decode-loader-results.ts
8037
+ import { createElement as createElement2 } from "react";
8221
8038
 
8222
- // src/browser/react/use-segments.ts
8223
- import { useContext as useContext11, useState as useState7, useEffect as useEffect8, useRef as useRef7 } from "react";
8039
+ // src/loader-redirect.tsx
8040
+ import { useEffect as useEffect3 } from "react";
8224
8041
 
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";
8042
+ // src/browser/react/use-router.ts
8043
+ import { useContext as useContext6, useMemo as useMemo2 } from "react";
8235
8044
 
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";
8045
+ // src/browser/react/use-mount.ts
8046
+ import { useContext as useContext5 } from "react";
8239
8047
 
8240
- // src/theme/ThemeProvider.tsx
8048
+ // src/browser/react/mount-context.ts
8241
8049
  import {
8242
- useCallback as useCallback3,
8243
- useEffect as useEffect9,
8244
- useMemo as useMemo5,
8245
- useState as useState9
8050
+ createContext as createContext5,
8051
+ createElement
8246
8052
  } from "react";
8053
+ var MountContext = createContext5("/");
8247
8054
 
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
8055
+ // src/browser/react/use-router.ts
8264
8056
  init_loader();
8265
8057
 
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();
8058
+ // src/browser/app-version.ts
8059
+ var currentVersion2;
8060
+ function getAppVersion() {
8061
+ return currentVersion2;
8062
+ }
8275
8063
 
8276
- // src/browser/react/NavigationProvider.tsx
8277
- import { jsx as jsx5 } from "react/jsx-runtime";
8064
+ // src/loader-redirect.tsx
8065
+ init_validate_redirect_origin();
8066
+ init_redirect_origin();
8278
8067
 
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";
8068
+ // src/route-content-wrapper.tsx
8069
+ import { jsx as jsx4 } from "react/jsx-runtime";
8289
8070
 
8290
- // src/browser/react/use-link-status.ts
8071
+ // src/use-loader.tsx
8291
8072
  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
8073
+ isValidElement,
8074
+ startTransition as startTransition2,
8075
+ use as use2,
8076
+ useCallback,
8077
+ useContext as useContext7,
8078
+ useEffect as useEffect4,
8079
+ useMemo as useMemo3,
8080
+ useOptimistic as useOptimistic2,
8081
+ useRef as useRef2,
8082
+ useState as useState3
8299
8083
  } from "react";
8300
- var LinkContext = createContext7(
8301
- null
8302
- );
8303
8084
 
8304
- // src/browser/event-controller.ts
8305
- init_notify_listeners();
8306
- if (typeof Symbol.dispose === "undefined") {
8307
- Symbol.dispose = /* @__PURE__ */ Symbol("Symbol.dispose");
8085
+ // src/thenable-status.ts
8086
+ function unwrapsSynchronously(stream) {
8087
+ const status = stream.status;
8088
+ return status === "fulfilled" || status === "resolved_model" || status === "resolved_module";
8308
8089
  }
8309
- if (typeof Symbol.asyncDispose === "undefined") {
8310
- Symbol.asyncDispose = /* @__PURE__ */ Symbol("Symbol.asyncDispose");
8090
+
8091
+ // src/loader-store.ts
8092
+ var EMPTY_SNAPSHOT = Object.freeze({
8093
+ value: void 0,
8094
+ hasValue: false,
8095
+ error: null,
8096
+ isLoading: false,
8097
+ requestId: 0
8098
+ });
8099
+ function normalizeGroups(group) {
8100
+ if (group === void 0) return [];
8101
+ if (typeof group === "string") return [group];
8102
+ return [...new Set(group)];
8311
8103
  }
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
- });
8104
+ var LoaderStore = class {
8105
+ constructor() {
8106
+ this.entries = /* @__PURE__ */ new Map();
8107
+ /** loader.$$id -> set of bucket keys, so clearFamily() can reach every bucket. */
8108
+ this.families = /* @__PURE__ */ new Map();
8109
+ /** refresh group name -> set of bucket keys, for refreshGroups(). */
8110
+ this.groups = /* @__PURE__ */ new Map();
8111
+ /**
8112
+ * loader.$$id -> per-loader streams a committed navigation is still
8113
+ * receiving. Registered and announced by announcePendingStreams(), which
8114
+ * browser/partial-update.ts calls INSIDE every transition commit, so a
8115
+ * reader whose content is held on screen by that transition pins
8116
+ * `isLoading: true` for the data it is still showing until the transition
8117
+ * lands (use-loader.tsx). Family-keyed on purpose: a persisting layout
8118
+ * reader of the same createLoader the child is re-running is pinned too
8119
+ * (the loader is in flight; not "this segment's copy is being replaced" —
8120
+ * issue #862, working as designed). The new tree's own read of the loader
8121
+ * suspends at the read site and never observes the flag. A Set, not a
8122
+ * flag: overlapping navigations (second click before the first stream
8123
+ * lands) keep the family pending until the LAST stream settles.
8124
+ */
8125
+ this.pendingStreams = /* @__PURE__ */ new Map();
8338
8126
  }
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);
8127
+ getOrCreate(bucketKey) {
8128
+ let e = this.entries.get(bucketKey);
8129
+ if (!e) {
8130
+ e = {
8131
+ snapshot: EMPTY_SNAPSHOT,
8132
+ listeners: /* @__PURE__ */ new Set(),
8133
+ latestRequestId: 0,
8134
+ loaderId: bucketKey,
8135
+ sticky: false,
8136
+ pendingClear: false,
8137
+ clearWhenSettled: false,
8138
+ groups: /* @__PURE__ */ new Map(),
8139
+ refetch: void 0,
8140
+ streamPendingListeners: /* @__PURE__ */ new Set()
8141
+ };
8142
+ this.entries.set(bucketKey, e);
8352
8143
  }
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)");
8144
+ return e;
8364
8145
  }
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);
8146
+ registerFamily(loaderId, bucketKey) {
8147
+ let fam = this.families.get(loaderId);
8148
+ if (!fam) {
8149
+ fam = /* @__PURE__ */ new Set();
8150
+ this.families.set(loaderId, fam);
8151
+ }
8152
+ fam.add(bucketKey);
8381
8153
  }
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);
8154
+ /**
8155
+ * Subscribe to entry changes for `bucketKey`.
8156
+ * Returns an unsubscribe function. A sticky bucket's entry is kept around even
8157
+ * after the last subscriber leaves so that an in-flight `load()` can still
8158
+ * commit if the consumer remounts. An ephemeral bucket is dropped once its
8159
+ * last subscriber leaves (deferred, see maybeScheduleRefcountClear).
8160
+ */
8161
+ subscribe(bucketKey, cb, options) {
8162
+ const e = this.getOrCreate(bucketKey);
8163
+ const loaderId = options?.loaderId ?? bucketKey;
8164
+ e.loaderId = loaderId;
8165
+ this.registerFamily(loaderId, bucketKey);
8166
+ if (options?.ephemeral !== true) e.sticky = true;
8167
+ const groups = normalizeGroups(options?.group);
8168
+ for (const group of groups) {
8169
+ this.addToGroup(group, bucketKey, e, options?.refetch);
8477
8170
  }
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 () => {
8171
+ e.pendingClear = false;
8172
+ e.clearWhenSettled = false;
8173
+ e.listeners.add(cb);
8174
+ const onStreamPending = options?.onStreamPending;
8175
+ if (onStreamPending) e.streamPendingListeners.add(onStreamPending);
8176
+ return () => {
8177
+ e.listeners.delete(cb);
8178
+ if (onStreamPending) e.streamPendingListeners.delete(onStreamPending);
8179
+ for (const group of groups) this.releaseGroup(group, bucketKey, e);
8180
+ this.maybeScheduleRefcountClear(bucketKey, e);
8493
8181
  };
8494
- prefetchScopeObservers.set(ownerDocument, state);
8495
8182
  }
8496
- let listeners = state.subscriptions.get(element);
8497
- if (!listeners) {
8498
- listeners = /* @__PURE__ */ new Set();
8499
- state.subscriptions.set(element, listeners);
8183
+ addToGroup(group, bucketKey, e, refetch) {
8184
+ e.groups.set(group, (e.groups.get(group) ?? 0) + 1);
8185
+ if (refetch) e.refetch = refetch;
8186
+ let members = this.groups.get(group);
8187
+ if (!members) {
8188
+ members = /* @__PURE__ */ new Set();
8189
+ this.groups.set(group, members);
8190
+ }
8191
+ members.add(bucketKey);
8500
8192
  }
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);
8193
+ releaseGroup(group, bucketKey, e) {
8194
+ const next = (e.groups.get(group) ?? 0) - 1;
8195
+ if (next > 0) {
8196
+ e.groups.set(group, next);
8197
+ return;
8509
8198
  }
8510
- if (state.subscriptions.size === 0 && prefetchScopeObservers.get(ownerDocument) === state) {
8511
- state.observer.disconnect();
8512
- prefetchScopeObservers.delete(ownerDocument);
8199
+ e.groups.delete(group);
8200
+ const members = this.groups.get(group);
8201
+ if (members) {
8202
+ members.delete(bucketKey);
8203
+ if (members.size === 0) this.groups.delete(group);
8513
8204
  }
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
8205
  }
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;
8206
+ /** Remove a bucket from every group it belongs to (used when it is dropped). */
8207
+ removeFromAllGroups(bucketKey, e) {
8208
+ for (const group of e.groups.keys()) {
8209
+ const members = this.groups.get(group);
8210
+ if (members) {
8211
+ members.delete(bucketKey);
8212
+ if (members.size === 0) this.groups.delete(group);
8213
+ }
8554
8214
  }
8215
+ e.groups.clear();
8555
8216
  }
8556
- if (/^[a-z][a-z0-9+.-]*:/i.test(href2)) {
8557
- return true;
8217
+ /**
8218
+ * Refresh every currently-mounted bucket tagged with ANY of the given group
8219
+ * names, with a plain GET (no params/body). Accepts a single name or an array
8220
+ * of names. A bucket that belongs to more than one of the named groups is
8221
+ * refreshed exactly once (members are unioned and deduped by bucket key), as
8222
+ * are multiple reads of one bucket. Resolves when all refreshes settle; rejects
8223
+ * with an `AggregateError` of the failures if any member fails — each failing
8224
+ * member also records its error on its own snapshot.
8225
+ */
8226
+ async refreshGroups(groups) {
8227
+ const names = typeof groups === "string" ? [groups] : groups;
8228
+ const buckets = /* @__PURE__ */ new Set();
8229
+ for (const name of names) {
8230
+ const members = this.groups.get(name);
8231
+ if (!members) continue;
8232
+ for (const bucketKey of members) buckets.add(bucketKey);
8233
+ }
8234
+ if (buckets.size === 0) return;
8235
+ const thunks = [];
8236
+ for (const bucketKey of buckets) {
8237
+ const e = this.entries.get(bucketKey);
8238
+ if (!e || e.listeners.size === 0 || !e.refetch) continue;
8239
+ thunks.push(e.refetch);
8240
+ }
8241
+ if (thunks.length === 0) return;
8242
+ const results = await Promise.allSettled(thunks.map((t) => t()));
8243
+ const reasons = results.filter((r) => r.status === "rejected").map((r) => r.reason);
8244
+ if (reasons.length > 0) {
8245
+ const label = names.map((n) => `"${n}"`).join(", ");
8246
+ throw new AggregateError(
8247
+ reasons,
8248
+ `refreshGroups(${label}) had ${reasons.length} failure(s)`
8249
+ );
8250
+ }
8558
8251
  }
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) {
8252
+ /**
8253
+ * Schedule a deferred drop for an ad-hoc (never-sticky) bucket whose last
8254
+ * subscriber just left. Deferred to a microtask so a StrictMode / transition
8255
+ * remount that resubscribes in the same tick cancels it.
8256
+ */
8257
+ maybeScheduleRefcountClear(bucketKey, e) {
8258
+ if (e.listeners.size > 0) return;
8259
+ if (e.sticky) return;
8260
+ if (e.pendingClear) return;
8261
+ e.pendingClear = true;
8262
+ queueMicrotask(() => {
8263
+ if (!e.pendingClear) return;
8264
+ e.pendingClear = false;
8265
+ if (e.listeners.size > 0) return;
8266
+ if (e.snapshot.isLoading) {
8267
+ e.clearWhenSettled = true;
8620
8268
  return;
8621
8269
  }
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
- );
8270
+ this.dropBucket(bucketKey);
8271
+ });
8272
+ }
8273
+ /** Remove a bucket entry entirely and prune it from its family/group indexes. */
8274
+ dropBucket(bucketKey) {
8275
+ const e = this.entries.get(bucketKey);
8276
+ if (!e) return;
8277
+ this.removeFromAllGroups(bucketKey, e);
8278
+ this.entries.delete(bucketKey);
8279
+ const fam = this.families.get(e.loaderId);
8280
+ if (fam) {
8281
+ fam.delete(bucketKey);
8282
+ if (fam.size === 0) this.families.delete(e.loaderId);
8664
8283
  }
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
- };
8284
+ }
8285
+ /** Drop an ephemeral bucket that settled with no subscribers left. */
8286
+ dropIfSettled(bucketKey, e) {
8287
+ if (e.clearWhenSettled && e.listeners.size === 0) {
8288
+ e.clearWhenSettled = false;
8289
+ this.dropBucket(bucketKey);
8290
+ }
8291
+ }
8292
+ /**
8293
+ * Returns the current snapshot for `bucketKey`. Stable reference between
8294
+ * mutations — subscribers rely on this to avoid spurious re-renders.
8295
+ * Returns `EMPTY_SNAPSHOT` (a singleton) when the entry has never been
8296
+ * mutated or has been cleared.
8297
+ */
8298
+ getSnapshot(bucketKey) {
8299
+ return this.entries.get(bucketKey)?.snapshot ?? EMPTY_SNAPSHOT;
8300
+ }
8301
+ /**
8302
+ * Reserve a fresh request id for an upcoming `load()` call. The returned id
8303
+ * is the new "latest"; any older in-flight requests will fail their gating
8304
+ * check on `finishData` / `finishError` / `finishLoading` and be dropped.
8305
+ *
8306
+ * Callers should follow with `beginRequest(id, requestId)` to flip the
8307
+ * loading flag on AND clear any leftover error from a previous attempt
8308
+ * — the latter matters for `throwOnError: false` consumers, which would
8309
+ * otherwise keep showing the stale error throughout the retry.
8310
+ */
8311
+ reserveRequestId(bucketKey) {
8312
+ const e = this.getOrCreate(bucketKey);
8313
+ e.latestRequestId++;
8314
+ return e.latestRequestId;
8315
+ }
8316
+ /**
8317
+ * Mark the request as in-flight: `isLoading = true`, `error = null`.
8318
+ * Combines the two operations so a retry doesn't render the previous
8319
+ * error during the new request. Gated on `requestId === latestRequestId`
8320
+ * for symmetry with the other mutators.
8321
+ */
8322
+ beginRequest(bucketKey, requestId) {
8323
+ const e = this.entries.get(bucketKey);
8324
+ if (!e || requestId !== e.latestRequestId) return;
8325
+ if (e.snapshot.isLoading && e.snapshot.error === null) return;
8326
+ e.snapshot = Object.freeze({
8327
+ value: e.snapshot.value,
8328
+ hasValue: e.snapshot.hasValue,
8329
+ error: null,
8330
+ isLoading: true,
8331
+ requestId
8332
+ });
8333
+ this.notify(e);
8334
+ }
8335
+ /**
8336
+ * Commit a successful result. No-op if `requestId` is not the latest
8337
+ * (a newer `load()` was issued or `clear()` ran). Clearing `error` is
8338
+ * intentional: a successful refetch should hide the previous failure.
8339
+ */
8340
+ finishData(bucketKey, requestId, value) {
8341
+ const e = this.entries.get(bucketKey);
8342
+ if (!e || requestId !== e.latestRequestId) return;
8343
+ e.snapshot = Object.freeze({
8344
+ value,
8345
+ hasValue: true,
8346
+ error: null,
8347
+ isLoading: false,
8348
+ requestId
8349
+ });
8350
+ this.notify(e);
8351
+ this.dropIfSettled(bucketKey, e);
8352
+ }
8353
+ /**
8354
+ * Commit an error. Preserves the last good `value` so consumers can keep
8355
+ * showing previous data while displaying the error if they choose. No-op
8356
+ * if `requestId` is not the latest.
8357
+ */
8358
+ finishError(bucketKey, requestId, error) {
8359
+ const e = this.entries.get(bucketKey);
8360
+ if (!e || requestId !== e.latestRequestId) return;
8361
+ e.snapshot = Object.freeze({
8362
+ value: e.snapshot.value,
8363
+ hasValue: e.snapshot.hasValue,
8364
+ error,
8365
+ isLoading: false,
8366
+ requestId
8367
+ });
8368
+ this.notify(e);
8369
+ this.dropIfSettled(bucketKey, e);
8370
+ }
8371
+ /**
8372
+ * Update loading flag. Gated on `requestId` to fix the race where an old
8373
+ * load() finishes after a new one started — its `setLoading(false)` would
8374
+ * otherwise hide the new request's spinner.
8375
+ */
8376
+ setLoading(bucketKey, requestId, isLoading) {
8377
+ const e = this.entries.get(bucketKey);
8378
+ if (!e || requestId !== e.latestRequestId) return;
8379
+ if (e.snapshot.isLoading === isLoading) return;
8380
+ e.snapshot = Object.freeze({
8381
+ ...e.snapshot,
8382
+ isLoading
8383
+ });
8384
+ this.notify(e);
8385
+ }
8386
+ /**
8387
+ * Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
8388
+ * `load()` whose promise is still pending will fail its gate when it resolves
8389
+ * and be dropped — prevents pre-navigation loads from clobbering the new
8390
+ * route's context. The entry itself is kept (sticky-bucket semantics).
8391
+ */
8392
+ clear(bucketKey) {
8393
+ const e = this.entries.get(bucketKey);
8394
+ if (!e) return;
8395
+ e.latestRequestId++;
8396
+ if (e.snapshot === EMPTY_SNAPSHOT) return;
8397
+ e.snapshot = EMPTY_SNAPSHOT;
8398
+ this.notify(e);
8399
+ }
8400
+ /**
8401
+ * Reset every sticky bucket belonging to `loaderId`. Called on navigation /
8402
+ * route-context change: all route-registered reads of the loader (keyed or
8403
+ * not) drop back to seeding from fresh `loaderData`. Ephemeral buckets are
8404
+ * intentionally left alone — they are governed by subscriber refcount, so a
8405
+ * persistent keyed reader outside the outlet keeps its value across a
8406
+ * navigation rather than blanking out.
8407
+ */
8408
+ clearFamily(loaderId) {
8409
+ const fam = this.families.get(loaderId);
8410
+ if (!fam) return;
8411
+ for (const bucketKey of fam) {
8412
+ const e = this.entries.get(bucketKey);
8413
+ if (!e || !e.sticky) continue;
8414
+ this.clear(bucketKey);
8415
+ }
8416
+ }
8417
+ notify(e) {
8418
+ for (const cb of e.listeners) cb();
8419
+ }
8420
+ /**
8421
+ * Register a still-streaming per-loader result for `loaderId`. No-op for a
8422
+ * settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
8423
+ * and action lanes commit settled chunks, and a { ssr: false } loader is
8424
+ * awaited before flush. Released as the stream settles, either way.
8425
+ */
8426
+ trackPendingStream(loaderId, stream) {
8427
+ if (unwrapsSynchronously(stream) || stream.status === "rejected") {
8428
+ return;
8429
+ }
8430
+ let set = this.pendingStreams.get(loaderId);
8431
+ if (set?.has(stream)) return;
8432
+ if (!set) {
8433
+ set = /* @__PURE__ */ new Set();
8434
+ this.pendingStreams.set(loaderId, set);
8435
+ }
8436
+ set.add(stream);
8437
+ const release = () => {
8438
+ const current = this.pendingStreams.get(loaderId);
8439
+ if (!current || !current.delete(stream)) return;
8440
+ if (current.size === 0) this.pendingStreams.delete(loaderId);
8714
8441
  };
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();
8442
+ stream.then(release, release);
8443
+ }
8444
+ /** True while any navigation stream for `loaderId` is still pending. */
8445
+ isStreamPending(loaderId) {
8446
+ return this.pendingStreams.has(loaderId);
8447
+ }
8448
+ /**
8449
+ * Register the loader segments of a tree being committed whose data is
8450
+ * still a pending promise, then fire `onStreamPending` on every subscriber
8451
+ * of every family with a pending stream. MUST run inside the startTransition
8452
+ * that commits the tree: the hook answers with a useOptimistic update, which
8453
+ * React reverts exactly when that transition commits — so the held reader
8454
+ * reports isLoading:true for as long as its data stays on screen and not a
8455
+ * frame longer. Outside a transition React reverts an optimistic update on
8456
+ * the next render, pinning nothing useful. Registering here rather than in
8457
+ * the tree build keeps register + announce in one call; settled chunks
8458
+ * (cached/reused segments, forceAwait lanes) are skipped by
8459
+ * trackPendingStream.
8460
+ */
8461
+ announcePendingStreams(segments) {
8462
+ for (const segment of segments) {
8463
+ if (segment.type === "loader" && segment.loaderId && segment.loaderData instanceof Promise) {
8464
+ this.trackPendingStream(segment.loaderId, segment.loaderData);
8465
+ }
8466
+ }
8467
+ for (const loaderId of this.pendingStreams.keys()) {
8468
+ const fam = this.families.get(loaderId);
8469
+ if (!fam) continue;
8470
+ for (const bucketKey of fam) {
8471
+ const e = this.entries.get(bucketKey);
8472
+ if (!e) continue;
8473
+ for (const cb of e.streamPendingListeners) cb();
8726
8474
  }
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
8475
  }
8749
- );
8750
- });
8476
+ }
8477
+ /**
8478
+ * Test-only escape hatch. Drops every entry. Production code should never
8479
+ * call this; the store is process-scoped and lives for the tab's lifetime.
8480
+ * @internal
8481
+ */
8482
+ reset() {
8483
+ this.entries.clear();
8484
+ this.families.clear();
8485
+ this.groups.clear();
8486
+ this.pendingStreams.clear();
8487
+ }
8488
+ };
8489
+ var loaderStore = new LoaderStore();
8751
8490
 
8752
- // src/browser/react/ScrollRestoration.tsx
8753
- import { useEffect as useEffect14 } from "react";
8491
+ // src/browser/react/use-navigation.ts
8492
+ import {
8493
+ useContext as useContext8,
8494
+ useState as useState4,
8495
+ useEffect as useEffect5,
8496
+ useOptimistic as useOptimistic3,
8497
+ startTransition as startTransition3,
8498
+ useRef as useRef3
8499
+ } from "react";
8754
8500
 
8755
- // src/browser/react/use-handle.ts
8501
+ // src/browser/react/use-pathname.ts
8502
+ import { useContext as useContext9, useState as useState5, useEffect as useEffect6, useRef as useRef4 } from "react";
8503
+
8504
+ // src/client-urls/optimistic-location.ts
8505
+ import { createContext as createContext6 } from "react";
8506
+ var OptimisticLocationContext = createContext6(null);
8507
+
8508
+ // src/browser/react/use-search-params.ts
8509
+ import {
8510
+ useCallback as useCallback2,
8511
+ useContext as useContext10,
8512
+ useEffect as useEffect7,
8513
+ useMemo as useMemo4,
8514
+ useRef as useRef5,
8515
+ useState as useState6
8516
+ } from "react";
8517
+
8518
+ // src/browser/react/use-params.ts
8519
+ import { useContext as useContext11, useState as useState7, useEffect as useEffect8, useRef as useRef6 } from "react";
8520
+ var EMPTY_PARAMS = Object.freeze({});
8521
+
8522
+ // src/browser/react/use-action.ts
8523
+ import {
8524
+ useContext as useContext12,
8525
+ useState as useState8,
8526
+ useEffect as useEffect9,
8527
+ useRef as useRef7,
8528
+ useOptimistic as useOptimistic4,
8529
+ startTransition as startTransition4
8530
+ } from "react";
8531
+
8532
+ // src/browser/react/use-segments.ts
8533
+ import { useContext as useContext13, useState as useState9, useEffect as useEffect10, useRef as useRef8 } from "react";
8534
+
8535
+ // src/browser/react/NavigationProvider.tsx
8536
+ import {
8537
+ useState as useState12,
8538
+ useEffect as useEffect12,
8539
+ useLayoutEffect,
8540
+ useCallback as useCallback4,
8541
+ useMemo as useMemo6,
8542
+ useRef as useRef10,
8543
+ use as use3
8544
+ } from "react";
8545
+
8546
+ // src/root-error-boundary.tsx
8547
+ import { Component as Component2, useState as useState10 } from "react";
8548
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
8549
+
8550
+ // src/theme/ThemeProvider.tsx
8551
+ import {
8552
+ useCallback as useCallback3,
8553
+ useEffect as useEffect11,
8554
+ useMemo as useMemo5,
8555
+ useState as useState11
8556
+ } from "react";
8557
+
8558
+ // src/theme/constants.ts
8559
+ var THEME_COOKIE = {
8560
+ maxAge: 60 * 60 * 24 * 365,
8561
+ // 1 year
8562
+ path: "/",
8563
+ sameSite: "lax"
8564
+ };
8565
+
8566
+ // src/theme/ThemeProvider.tsx
8567
+ import { jsx as jsx6 } from "react/jsx-runtime";
8568
+
8569
+ // src/browser/react/NavigationProvider.tsx
8570
+ init_loader();
8571
+ init_logging();
8572
+
8573
+ // src/browser/navigation-store.ts
8574
+ init_cache();
8575
+ init_rango_state();
8576
+
8577
+ // src/browser/react/NavigationProvider.tsx
8578
+ import { jsx as jsx7 } from "react/jsx-runtime";
8579
+
8580
+ // src/browser/react/Link.tsx
8756
8581
  import {
8582
+ forwardRef,
8583
+ useCallback as useCallback5,
8757
8584
  useContext as useContext15,
8758
- useState as useState13,
8759
8585
  useEffect as useEffect15,
8586
+ useMemo as useMemo7,
8760
8587
  useRef as useRef13,
8588
+ useSyncExternalStore
8589
+ } from "react";
8590
+
8591
+ // src/browser/react/use-link-status.ts
8592
+ import {
8593
+ createContext as createContext7,
8594
+ useContext as useContext14,
8595
+ useState as useState13,
8596
+ useEffect as useEffect13,
8597
+ useRef as useRef11,
8761
8598
  useOptimistic as useOptimistic5,
8762
8599
  startTransition as startTransition5
8763
8600
  } from "react";
8601
+ var LinkContext = createContext7(
8602
+ null
8603
+ );
8764
8604
 
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;
8605
+ // src/browser/event-controller.ts
8606
+ init_notify_listeners();
8607
+ if (typeof Symbol.dispose === "undefined") {
8608
+ Symbol.dispose = /* @__PURE__ */ Symbol("Symbol.dispose");
8609
+ }
8610
+ if (typeof Symbol.asyncDispose === "undefined") {
8611
+ Symbol.asyncDispose = /* @__PURE__ */ Symbol("Symbol.asyncDispose");
8612
+ }
8613
+ var locationChangeSubscriptions = /* @__PURE__ */ new WeakMap();
8614
+ function subscribeToLocationChange(eventController, listener) {
8615
+ let subscription = locationChangeSubscriptions.get(eventController);
8616
+ if (!subscription) {
8617
+ subscription = {
8618
+ registrations: /* @__PURE__ */ new Map(),
8619
+ notificationVersion: 0,
8620
+ unsubscribe: () => {
8784
8621
  }
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]}`;
8622
+ };
8623
+ locationChangeSubscriptions.set(eventController, subscription);
8624
+ const currentSubscription = subscription;
8625
+ subscription.unsubscribe = eventController.subscribe(() => {
8626
+ const notificationVersion = ++currentSubscription.notificationVersion;
8627
+ const nextHref = eventController.getState().location.href;
8628
+ notifyListeners(
8629
+ [...currentSubscription.registrations],
8630
+ ([, registration]) => {
8631
+ if (registration.href === nextHref) return;
8632
+ registration.href = nextHref;
8633
+ registration.listener(nextHref);
8634
+ },
8635
+ ([token2, registration]) => currentSubscription.registrations.get(token2) === registration,
8636
+ () => notificationVersion === currentSubscription.notificationVersion
8637
+ );
8638
+ });
8639
+ }
8640
+ const token = /* @__PURE__ */ Symbol();
8641
+ subscription.registrations.set(token, {
8642
+ href: eventController.getState().location.href,
8643
+ listener
8644
+ });
8645
+ let active2 = true;
8646
+ return () => {
8647
+ if (!active2) return;
8648
+ active2 = false;
8649
+ subscription.registrations.delete(token);
8650
+ if (subscription.registrations.size > 0) return;
8651
+ if (locationChangeSubscriptions.get(eventController) === subscription) {
8652
+ locationChangeSubscriptions.delete(eventController);
8788
8653
  }
8789
- } catch {
8654
+ subscription.unsubscribe();
8655
+ };
8656
+ }
8657
+
8658
+ // src/browser/prefetch/default-strategy.ts
8659
+ var hoverNoneQuery = null;
8660
+ var defaultStrategy = process.env.NODE_ENV === "production" ? "viewport" : "none";
8661
+ function getHoverNoneQuery() {
8662
+ if (typeof window === "undefined") return null;
8663
+ if (!hoverNoneQuery) {
8664
+ hoverNoneQuery = window.matchMedia("(hover: none)");
8665
+ }
8666
+ return hoverNoneQuery;
8667
+ }
8668
+ function getDefaultPrefetchStrategy() {
8669
+ return defaultStrategy;
8670
+ }
8671
+ function resolveAdaptiveStrategy(strategy) {
8672
+ if (strategy !== "adaptive") return strategy;
8673
+ return getHoverNoneQuery()?.matches ? "viewport" : "hover";
8674
+ }
8675
+ function subscribeToAdaptiveStrategyChange(listener) {
8676
+ const query = getHoverNoneQuery();
8677
+ if (!query) return () => {
8678
+ };
8679
+ if (typeof query.addEventListener === "function") {
8680
+ query.addEventListener("change", listener);
8681
+ return () => query.removeEventListener("change", listener);
8682
+ }
8683
+ query.addListener(listener);
8684
+ return () => query.removeListener(listener);
8685
+ }
8686
+
8687
+ // src/browser/prefetch/observer.ts
8688
+ init_notify_listeners();
8689
+ var callbacks = /* @__PURE__ */ new Map();
8690
+ var observer = null;
8691
+ function getObserver() {
8692
+ if (!observer) {
8693
+ observer = new IntersectionObserver(
8694
+ (entries, currentObserver) => {
8695
+ function* intersectingCallbacks() {
8696
+ for (const entry of entries) {
8697
+ if (entry.isIntersecting) {
8698
+ const subscriptions = callbacks.get(entry.target);
8699
+ if (subscriptions) {
8700
+ currentObserver.unobserve(entry.target);
8701
+ callbacks.delete(entry.target);
8702
+ for (const [subscription, callback] of subscriptions) {
8703
+ yield [subscriptions, subscription, callback];
8704
+ }
8705
+ }
8706
+ }
8707
+ }
8708
+ }
8709
+ notifyListeners(
8710
+ intersectingCallbacks(),
8711
+ ([, , callback]) => callback(),
8712
+ ([subscriptions, subscription]) => subscriptions.has(subscription)
8713
+ );
8714
+ },
8715
+ { rootMargin: "200px" }
8716
+ );
8717
+ for (const element of callbacks.keys()) observer.observe(element);
8790
8718
  }
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;
8719
+ return observer;
8808
8720
  }
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
- }
8721
+ function observeForPrefetch(element, onVisible) {
8722
+ if (typeof IntersectionObserver === "undefined") return () => {
8723
+ };
8724
+ const currentObserver = getObserver();
8725
+ let subscriptions = callbacks.get(element);
8726
+ if (!subscriptions) {
8727
+ subscriptions = /* @__PURE__ */ new Map();
8728
+ callbacks.set(element, subscriptions);
8823
8729
  }
8824
- const collectFn = collect ?? defaultCollect;
8825
- collectRegistry.set(
8826
- handleId,
8827
- collectFn
8828
- );
8829
- return {
8830
- __brand: "handle",
8831
- $$id: handleId
8730
+ const subscription = /* @__PURE__ */ Symbol();
8731
+ subscriptions.set(subscription, onVisible);
8732
+ if (subscriptions.size === 1) currentObserver.observe(element);
8733
+ return () => {
8734
+ const current = callbacks.get(element);
8735
+ subscriptions.delete(subscription);
8736
+ if (current !== subscriptions) return;
8737
+ if (current.size > 0) return;
8738
+ callbacks.delete(element);
8739
+ observer?.unobserve(element);
8832
8740
  };
8833
8741
  }
8834
8742
 
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;
8743
+ // src/browser/link-interceptor.ts
8744
+ init_invalidation();
8745
+ init_notify_listeners();
8746
+ var ELIGIBILITY_ATTRIBUTES = {
8747
+ href: "href",
8748
+ target: "target",
8749
+ download: "download",
8750
+ noIntercept: "data-no-intercept",
8751
+ linkComponent: "data-link-component",
8752
+ external: "data-external",
8753
+ prefetch: "data-prefetch",
8754
+ prefetchScope: "data-prefetch-scope"
8755
+ };
8756
+ var ELIGIBILITY_ATTRIBUTE_FILTER = Object.values(ELIGIBILITY_ATTRIBUTES);
8757
+ var ELIGIBLE_ANCHOR_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.href}]`;
8758
+ var LINK_COMPONENT_SELECTOR = `a[${ELIGIBILITY_ATTRIBUTES.linkComponent}]`;
8759
+ var PREFETCH_SCOPE_OPT_OUT_SELECTOR = `[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="false"],[${ELIGIBILITY_ATTRIBUTES.prefetchScope}="none"]`;
8760
+ function isElementNode(value) {
8761
+ return typeof value === "object" && value !== null && value.nodeType === 1 && typeof value.matches === "function";
8847
8762
  }
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) {
8763
+ var prefetchScopeObservers = /* @__PURE__ */ new WeakMap();
8764
+ function createPrefetchScopeObserverState(ownerDocument) {
8765
+ if (typeof MutationObserver === "undefined" || !ownerDocument.documentElement) {
8868
8766
  return void 0;
8869
8767
  }
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
- }
8768
+ const subscriptions = /* @__PURE__ */ new Map();
8769
+ const observer2 = new MutationObserver((mutations) => {
8770
+ const affected = /* @__PURE__ */ new Set();
8771
+ const collect = (element) => {
8772
+ subscriptions.get(element)?.forEach((listener) => affected.add(listener));
8773
+ };
8774
+ for (const mutation of mutations) {
8775
+ if (!isElementNode(mutation.target)) continue;
8776
+ collect(mutation.target);
8777
+ mutation.target.querySelectorAll(LINK_COMPONENT_SELECTOR).forEach(collect);
8876
8778
  }
8877
- return void 0;
8878
- }
8879
- return void 0;
8779
+ notifyListeners(affected, (listener) => listener());
8780
+ });
8781
+ observer2.observe(ownerDocument.documentElement, {
8782
+ subtree: true,
8783
+ attributes: true,
8784
+ attributeFilter: [ELIGIBILITY_ATTRIBUTES.prefetchScope]
8785
+ });
8786
+ return { observer: observer2, subscriptions };
8880
8787
  }
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);
8788
+ function subscribeToPrefetchScopeChange(element, listener) {
8789
+ const ownerDocument = element.ownerDocument;
8790
+ let state = prefetchScopeObservers.get(ownerDocument);
8791
+ if (!state) {
8792
+ state = createPrefetchScopeObserverState(ownerDocument);
8793
+ if (!state) return () => {
8794
+ };
8795
+ prefetchScopeObservers.set(ownerDocument, state);
8893
8796
  }
8894
- }
8895
- function updateIndicesAfterRemoval(keyToIndex, removedIndex) {
8896
- for (const [key, index] of keyToIndex) {
8897
- if (index > removedIndex) {
8898
- keyToIndex.set(key, index - 1);
8899
- }
8797
+ let listeners = state.subscriptions.get(element);
8798
+ if (!listeners) {
8799
+ listeners = /* @__PURE__ */ new Set();
8800
+ state.subscriptions.set(element, listeners);
8900
8801
  }
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);
8802
+ listeners.add(listener);
8803
+ let active2 = true;
8804
+ return () => {
8805
+ if (!active2) return;
8806
+ active2 = false;
8807
+ listeners.delete(listener);
8808
+ if (listeners.size === 0 && state.subscriptions.get(element) === listeners) {
8809
+ state.subscriptions.delete(element);
8910
8810
  }
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);
8811
+ if (state.subscriptions.size === 0 && prefetchScopeObservers.get(ownerDocument) === state) {
8812
+ state.observer.disconnect();
8813
+ prefetchScopeObservers.delete(ownerDocument);
8957
8814
  }
8815
+ };
8816
+ }
8817
+ function isHashOnlyNavigationToPath(anchor, pathname) {
8818
+ return pathname === window.location.pathname && anchor.search === window.location.search && !!anchor.hash;
8819
+ }
8820
+ function isHashOnlyNavigation(anchor) {
8821
+ return isHashOnlyNavigationToPath(anchor, anchor.pathname);
8822
+ }
8823
+ function isPrefetchScopeDisabled(element) {
8824
+ return typeof element.closest === "function" && element.closest(PREFETCH_SCOPE_OPT_OUT_SELECTOR) !== null;
8825
+ }
8826
+
8827
+ // src/browser/react/location-state.ts
8828
+ import { useState as useState14, useEffect as useEffect14, useRef as useRef12 } from "react";
8829
+
8830
+ // src/browser/react/location-state-shared.ts
8831
+ function isLocationStateEntry(value) {
8832
+ return value !== null && typeof value === "object" && "__rsc_ls_key" in value && "__rsc_ls_value" in value && typeof value.__rsc_ls_key === "string";
8833
+ }
8834
+ function resolveLocationStateEntries(entries) {
8835
+ const result = {};
8836
+ for (const entry of entries) {
8837
+ result[entry.__rsc_ls_key] = entry.__rsc_ls_lazy ? entry.__rsc_ls_value() : entry.__rsc_ls_value;
8958
8838
  }
8959
8839
  return result;
8960
8840
  }
8961
- var Meta = createHandle(collectMeta, "__rsc_router_meta__");
8962
8841
 
8963
- // src/handles/MetaTags.tsx
8964
- import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
8965
-
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
- );
8842
+ // src/browser/react/Link.tsx
8843
+ init_loader();
8844
+ import { jsx as jsx8 } from "react/jsx-runtime";
8845
+ var IGNORE_STRATEGY_CHANGES = (_listener) => () => {
8846
+ };
8847
+ function isExternalUrl(href2, origin) {
8848
+ if (href2.startsWith("//")) return true;
8849
+ if (href2.startsWith("http://") || href2.startsWith("https://")) {
8850
+ if (!origin) return false;
8851
+ try {
8852
+ return new URL(href2).origin !== origin;
8853
+ } catch {
8854
+ return false;
8855
+ }
8982
8856
  }
8983
- if (c.async && c.defer) {
8984
- console.warn(
8985
- `[Script] A config has both "async" and "defer"; they are mutually exclusive.`
8986
- );
8857
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href2)) {
8858
+ return true;
8987
8859
  }
8860
+ return false;
8988
8861
  }
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;
8862
+ var Link = forwardRef(function Link2({
8863
+ to,
8864
+ replace = false,
8865
+ scroll = true,
8866
+ reloadDocument = false,
8867
+ revalidate: revalidate2,
8868
+ prefetch,
8869
+ prefetchKey,
8870
+ state,
8871
+ children,
8872
+ onClick,
8873
+ ...props
8874
+ }, ref) {
8875
+ const ctx = useContext15(NavigationStoreContext);
8876
+ const storeLocation = ctx?.eventController.getState().location;
8877
+ const origin = storeLocation?.origin ?? (typeof window !== "undefined" ? window.location.origin : void 0);
8878
+ const isExternal = isExternalUrl(to, origin);
8879
+ const resolvedTo = useMemo7(() => {
8880
+ if (isExternal) return to;
8881
+ const bn = ctx?.basename;
8882
+ if (!bn || !to.startsWith("/") || to.startsWith(bn + "/") || to === bn)
8883
+ return to;
8884
+ return to === "/" ? bn : bn + to;
8885
+ }, [to, isExternal, ctx?.basename]);
8886
+ const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? getDefaultPrefetchStrategy();
8887
+ const resolvedStrategy = useSyncExternalStore(
8888
+ configuredStrategy === "adaptive" ? subscribeToAdaptiveStrategyChange : IGNORE_STRATEGY_CHANGES,
8889
+ () => resolveAdaptiveStrategy(configuredStrategy),
8890
+ () => configuredStrategy === "adaptive" ? "hover" : configuredStrategy
8891
+ );
8892
+ const internalRef = useRef13(null);
8893
+ const setRef = useCallback5(
8894
+ (node) => {
8895
+ internalRef.current = node;
8896
+ if (typeof ref === "function") {
8897
+ ref(node);
8898
+ } else if (ref) {
8899
+ ref.current = node;
9000
8900
  }
9001
- const existing = keyToIndex.get(key);
9002
- if (existing !== void 0) {
9003
- result[existing] = config;
8901
+ },
8902
+ [ref]
8903
+ );
8904
+ const stateRef = useRef13(state);
8905
+ stateRef.current = state;
8906
+ const handleClick = useCallback5(
8907
+ (e) => {
8908
+ onClick?.(e);
8909
+ if (e.defaultPrevented) return;
8910
+ if (isExternal) return;
8911
+ if (reloadDocument) return;
8912
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
8913
+ if (e.currentTarget.hasAttribute("download"))
8914
+ return;
8915
+ const target = e.currentTarget.target;
8916
+ if (target && target !== "_self") return;
8917
+ if (isHashOnlyNavigation(e.currentTarget)) {
8918
+ return;
8919
+ }
8920
+ if (!ctx?.navigate) {
8921
+ return;
8922
+ }
8923
+ e.preventDefault();
8924
+ e.stopPropagation();
8925
+ const currentState = stateRef.current;
8926
+ let resolvedState;
8927
+ if (Array.isArray(currentState) && currentState.length > 0 && isLocationStateEntry(currentState[0])) {
8928
+ resolvedState = resolveLocationStateEntries(
8929
+ currentState
8930
+ );
8931
+ } else if (typeof currentState === "function") {
8932
+ resolvedState = currentState();
8933
+ } else if (currentState != null) {
8934
+ resolvedState = currentState;
8935
+ }
8936
+ ctx.navigate(resolvedTo, {
8937
+ replace,
8938
+ scroll,
8939
+ state: resolvedState,
8940
+ revalidate: revalidate2
8941
+ });
8942
+ },
8943
+ [
8944
+ resolvedTo,
8945
+ isExternal,
8946
+ reloadDocument,
8947
+ replace,
8948
+ scroll,
8949
+ revalidate2,
8950
+ ctx,
8951
+ onClick
8952
+ ]
8953
+ );
8954
+ const handleMouseEnter = useCallback5(() => {
8955
+ const element = internalRef.current;
8956
+ if ((resolvedStrategy === "hover" || resolvedStrategy === "viewport") && !isExternal && ctx?.store && (!element || !isHashOnlyNavigation(element) && !isPrefetchScopeDisabled(element))) {
8957
+ const segmentState = ctx.store.getSegmentState();
8958
+ prefetchDirect2(
8959
+ resolvedTo,
8960
+ segmentState.currentSegmentIds,
8961
+ getAppVersion(),
8962
+ ctx.store.getRouterId?.(),
8963
+ prefetchKey
8964
+ );
8965
+ }
8966
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
8967
+ useEffect15(() => {
8968
+ if (isExternal || !ctx?.store) return;
8969
+ const isViewport = resolvedStrategy === "viewport";
8970
+ const isRender = resolvedStrategy === "render";
8971
+ if (!isViewport && !isRender) return;
8972
+ const armPrefetch = () => {
8973
+ const element2 = internalRef.current;
8974
+ if (element2 && (isHashOnlyNavigation(element2) || isPrefetchScopeDisabled(element2))) {
8975
+ return () => {
8976
+ };
8977
+ }
8978
+ let cancelled = false;
8979
+ let unsubIdle;
8980
+ let stopObserving;
8981
+ const triggerPrefetch = () => {
8982
+ if (cancelled) return;
8983
+ const currentElement = internalRef.current;
8984
+ if (currentElement && isPrefetchScopeDisabled(currentElement)) return;
8985
+ const segmentState = ctx.store.getSegmentState();
8986
+ prefetchQueued2(
8987
+ resolvedTo,
8988
+ segmentState.currentSegmentIds,
8989
+ getAppVersion(),
8990
+ ctx.store.getRouterId?.(),
8991
+ prefetchKey
8992
+ );
8993
+ };
8994
+ if (isRender) {
8995
+ unsubIdle = schedulePrefetchWhenRouterIdle(
8996
+ ctx.eventController,
8997
+ triggerPrefetch
8998
+ );
9004
8999
  } else {
9005
- keyToIndex.set(key, result.length);
9006
- result.push(config);
9000
+ const viewportElement = internalRef.current;
9001
+ if (viewportElement) {
9002
+ stopObserving = observeForPrefetch(viewportElement, () => {
9003
+ unsubIdle = schedulePrefetchWhenRouterIdle(
9004
+ ctx.eventController,
9005
+ triggerPrefetch
9006
+ );
9007
+ });
9008
+ }
9009
+ }
9010
+ return () => {
9011
+ cancelled = true;
9012
+ unsubIdle?.();
9013
+ stopObserving?.();
9014
+ };
9015
+ };
9016
+ let disarmPrefetch = armPrefetch();
9017
+ const element = internalRef.current;
9018
+ const unsubscribeScope = element ? subscribeToPrefetchScopeChange(element, () => {
9019
+ disarmPrefetch();
9020
+ disarmPrefetch = armPrefetch();
9021
+ }) : void 0;
9022
+ const unsubscribeLocation = subscribeToLocationChange(
9023
+ ctx.eventController,
9024
+ () => {
9025
+ disarmPrefetch();
9026
+ disarmPrefetch = armPrefetch();
9007
9027
  }
9028
+ );
9029
+ return () => {
9030
+ unsubscribeScope?.();
9031
+ unsubscribeLocation();
9032
+ disarmPrefetch();
9033
+ };
9034
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
9035
+ return /* @__PURE__ */ jsx8(
9036
+ "a",
9037
+ {
9038
+ ref: setRef,
9039
+ href: resolvedTo,
9040
+ onClick: handleClick,
9041
+ onMouseEnter: handleMouseEnter,
9042
+ "data-link-component": true,
9043
+ "data-external": isExternal ? "" : void 0,
9044
+ "data-scroll": scroll === false ? "false" : void 0,
9045
+ "data-replace": replace ? "true" : void 0,
9046
+ "data-revalidate": revalidate2 === false ? "false" : void 0,
9047
+ ...props,
9048
+ children: /* @__PURE__ */ jsx8(LinkContext.Provider, { value: resolvedTo, children })
9008
9049
  }
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";
9050
+ );
9051
+ });
9017
9052
 
9018
9053
  // src/handles/breadcrumbs.ts
9019
9054
  function collectBreadcrumbs(segments) {
@@ -9105,15 +9140,36 @@ var ALL_USE_ITEM_TYPES = new Set(
9105
9140
  );
9106
9141
 
9107
9142
  // src/server/request-context.ts
9108
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
9143
+ import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
9109
9144
  init_cookie_name();
9110
9145
 
9146
+ // src/server/handle-store.ts
9147
+ var LOADER_SLOT = Object.freeze({ loader: true });
9148
+
9149
+ // src/cache/cache-tag.ts
9150
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
9151
+ var cacheTagStorage = new AsyncLocalStorage3();
9152
+ var segmentOwnerStorage = new AsyncLocalStorage3();
9153
+ var recordedTagCapture = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:recorded-tag-capture")] ??= new AsyncLocalStorage3();
9154
+ var identityReads = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:identity-reads")] ??= /* @__PURE__ */ new WeakMap();
9155
+ function activeTagSet() {
9156
+ const capture = recordedTagCapture.getStore();
9157
+ const body = getLoaderBodyTags();
9158
+ if (capture && capture.outer === body) return capture.into;
9159
+ return body ?? capture?.into;
9160
+ }
9161
+ globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:identity-read-recorder")] = (surface, verb) => {
9162
+ const set = activeTagSet();
9163
+ if (set === void 0 || identityReads.has(set)) return;
9164
+ identityReads.set(set, { surface, verb, bodyId: getCurrentLoaderBodyId() });
9165
+ };
9166
+
9111
9167
  // src/router/router-context.ts
9112
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
9113
- var routerContext = new AsyncLocalStorage2();
9168
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
9169
+ var routerContext = new AsyncLocalStorage4();
9114
9170
 
9115
9171
  // src/server/request-context.ts
9116
- var requestContextStorage = new AsyncLocalStorage3();
9172
+ var requestContextStorage = new AsyncLocalStorage5();
9117
9173
 
9118
9174
  // src/route-definition/redirect.ts
9119
9175
  init_redirect_origin();
@@ -9307,7 +9363,7 @@ function serializeClientUrlPatterns(patterns) {
9307
9363
  // src/vite/discovery/client-urls-projection.ts
9308
9364
  init_client_ref_hashing();
9309
9365
  init_debug();
9310
- var debug11 = createRangoDebugger(NS.discovery);
9366
+ var debug10 = createRangoDebugger(NS.discovery);
9311
9367
  function hasDefaultExport(code) {
9312
9368
  let program;
9313
9369
  try {
@@ -9396,7 +9452,7 @@ async function refreshRecordedClientUrlProjections(state, ssrEnv, serverMod) {
9396
9452
  const projection = serializeClientUrlPatterns(
9397
9453
  definition
9398
9454
  );
9399
- debug11?.(
9455
+ debug10?.(
9400
9456
  "pre-entry refresh %s -> [%s]",
9401
9457
  source,
9402
9458
  projection.routes.map((route2) => route2.pattern).join(", ")
@@ -9478,21 +9534,21 @@ async function discoverClientUrlProjections(state, ssrEnv, serverMod) {
9478
9534
  }
9479
9535
 
9480
9536
  // src/vite/discovery/discover-routers.ts
9481
- var debug12 = createRangoDebugger(NS.discovery);
9537
+ var debug11 = createRangoDebugger(NS.discovery);
9482
9538
  async function discoverRouters(state, rscEnv, ssrEnv) {
9483
9539
  if (!state.resolvedEntryPath) return;
9484
9540
  const serverMod = await timed(
9485
- debug12,
9541
+ debug11,
9486
9542
  "inner: import @rangojs/router/server",
9487
9543
  () => rscEnv.runner.import("@rangojs/router/server")
9488
9544
  );
9489
9545
  await timed(
9490
- debug12,
9546
+ debug11,
9491
9547
  "inner: refresh recorded client URL projections",
9492
9548
  () => refreshRecordedClientUrlProjections(state, ssrEnv, serverMod)
9493
9549
  );
9494
9550
  await timed(
9495
- debug12,
9551
+ debug11,
9496
9552
  "inner: import entry",
9497
9553
  () => rscEnv.runner.import(state.resolvedEntryPath)
9498
9554
  );
@@ -9508,7 +9564,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9508
9564
  const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
9509
9565
  discoveryErrors.push(...handlerErrors);
9510
9566
  for (const { context, error } of handlerErrors) {
9511
- debug12?.("caught error while resolving %s: %O", context, error);
9567
+ debug11?.("caught error while resolving %s: %O", context, error);
9512
9568
  }
9513
9569
  const freshServerMod = await rscEnv.runner.import(
9514
9570
  "@rangojs/router/server"
@@ -9527,17 +9583,17 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9527
9583
  }
9528
9584
  }
9529
9585
  await timed(
9530
- debug12,
9586
+ debug11,
9531
9587
  "inner: discover client URL projections",
9532
9588
  () => discoverClientUrlProjections(state, ssrEnv, serverMod)
9533
9589
  );
9534
9590
  const buildMod = await timed(
9535
- debug12,
9591
+ debug11,
9536
9592
  "inner: import @rangojs/router/build",
9537
9593
  () => rscEnv.runner.import("@rangojs/router/build")
9538
9594
  );
9539
9595
  const generateManifestFull = buildMod.generateManifestFull;
9540
- debug12?.("inner: found %d router(s) in registry", registry.size);
9596
+ debug11?.("inner: found %d router(s) in registry", registry.size);
9541
9597
  const nestedRouterConflict = findNestedRouterConflict(
9542
9598
  [...registry.values()].map((router) => router.__sourceFile).filter(
9543
9599
  (sourceFile) => typeof sourceFile === "string"
@@ -9562,7 +9618,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9562
9618
  collectFallbackClientRefs(node, collectClientFallbackRef);
9563
9619
  }
9564
9620
  };
9565
- const manifestGenStart = debug12 ? performance.now() : 0;
9621
+ const manifestGenStart = debug11 ? performance.now() : 0;
9566
9622
  for (const [id, router] of registry) {
9567
9623
  if (!generateManifestFull) {
9568
9624
  continue;
@@ -9640,12 +9696,12 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9640
9696
  );
9641
9697
  }
9642
9698
  }
9643
- debug12?.(
9699
+ debug11?.(
9644
9700
  "inner: generated manifests for %d router(s) (%sms)",
9645
9701
  allManifests.length,
9646
9702
  (performance.now() - manifestGenStart).toFixed(1)
9647
9703
  );
9648
- const trieStart = debug12 ? performance.now() : 0;
9704
+ const trieStart = debug11 ? performance.now() : 0;
9649
9705
  if (Object.keys(newMergedRouteManifest).length > 0) {
9650
9706
  for (const { id, manifest } of allManifests) {
9651
9707
  const perRouterTrie = buildPerRouterTrie(manifest);
@@ -9654,7 +9710,7 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9654
9710
  }
9655
9711
  }
9656
9712
  }
9657
- debug12?.(
9713
+ debug11?.(
9658
9714
  "inner: trie build done (%sms)",
9659
9715
  (performance.now() - trieStart).toFixed(1)
9660
9716
  );
@@ -9689,13 +9745,15 @@ function buildShellManifestKey(pathname) {
9689
9745
 
9690
9746
  // src/vite/discovery/shell-prerender-phase.ts
9691
9747
  init_debug();
9692
- var debug13 = createRangoDebugger(NS.prerender);
9748
+ var debug12 = createRangoDebugger(NS.prerender);
9693
9749
  async function runShellPrerenderPhase(s, builder) {
9694
9750
  const tempServer = s.shellPhaseTempServer;
9695
9751
  if (!s.isBuildMode || !s.shellCandidates?.length || !tempServer) return;
9696
9752
  const candidates = s.shellCandidates;
9697
9753
  const startTotal = performance.now();
9698
9754
  console.log(`[rango] Shell-prerendering ${candidates.length} URL(s)...`);
9755
+ const keepAlive = setInterval(() => {
9756
+ }, 6e4);
9699
9757
  let restoreClientRequire;
9700
9758
  try {
9701
9759
  const rscEnv = tempServer.environments?.rsc;
@@ -9745,7 +9803,7 @@ async function runShellPrerenderPhase(s, builder) {
9745
9803
  const base = builder?.config?.base ?? "/";
9746
9804
  const normalizedBase = base.endsWith("/") ? base : `${base}/`;
9747
9805
  const bootstrapContent = `import("${normalizedBase}assets/${entryFile}")`;
9748
- debug13?.("shell prerender bootstrap: %s", bootstrapContent);
9806
+ debug12?.("shell prerender bootstrap: %s", bootstrapContent);
9749
9807
  const minimalPlugin = (tempServer.config?.plugins ?? []).find(
9750
9808
  (p) => p?.name === "rsc:minimal"
9751
9809
  );
@@ -9832,7 +9890,7 @@ async function runShellPrerenderPhase(s, builder) {
9832
9890
  buildEnv: s.resolvedBuildEnv,
9833
9891
  buildVersion,
9834
9892
  captureShellHTML,
9835
- debug: !!debug13
9893
+ debug: !!debug12
9836
9894
  });
9837
9895
  if (res.outcome === "route-mismatch") {
9838
9896
  mismatches.push(res.matchedRouteName ?? "(no match)");
@@ -9903,6 +9961,7 @@ async function runShellPrerenderPhase(s, builder) {
9903
9961
  } finally {
9904
9962
  delete globalThis.__loadPrerenderManifestModule;
9905
9963
  restoreClientRequire?.();
9964
+ clearInterval(keepAlive);
9906
9965
  }
9907
9966
  }
9908
9967
  function writeShellManifest(s, rscOutDir, staged) {
@@ -10414,7 +10473,7 @@ function postprocessBundle(state) {
10414
10473
  }
10415
10474
 
10416
10475
  // src/vite/discovery/gate-state.ts
10417
- function createDiscoveryGate(s, debug14) {
10476
+ function createDiscoveryGate(s, debug13) {
10418
10477
  let gatePending = false;
10419
10478
  let gateResolver = () => {
10420
10479
  };
@@ -10431,7 +10490,7 @@ function createDiscoveryGate(s, debug14) {
10431
10490
  const resolveGate = () => {
10432
10491
  if (!gatePending) return;
10433
10492
  if (inProgress || queued2 || pendingEvents) {
10434
- debug14?.(
10493
+ debug13?.(
10435
10494
  "hmr: resolveGate deferred \u2014 work in flight (inProgress=%s queued=%s pendingEvents=%s)",
10436
10495
  inProgress,
10437
10496
  queued2,
@@ -10440,7 +10499,7 @@ function createDiscoveryGate(s, debug14) {
10440
10499
  return;
10441
10500
  }
10442
10501
  gatePending = false;
10443
- debug14?.("hmr: discoveryDone resolved");
10502
+ debug13?.("hmr: discoveryDone resolved");
10444
10503
  gateResolver();
10445
10504
  };
10446
10505
  const noteRouteEvent = () => {
@@ -10450,7 +10509,7 @@ function createDiscoveryGate(s, debug14) {
10450
10509
  const runRefreshCycle = async (work) => {
10451
10510
  if (inProgress) {
10452
10511
  queued2 = true;
10453
- debug14?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
10512
+ debug13?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
10454
10513
  return;
10455
10514
  }
10456
10515
  pendingEvents = false;
@@ -10461,16 +10520,16 @@ function createDiscoveryGate(s, debug14) {
10461
10520
  inProgress = false;
10462
10521
  if (queued2) {
10463
10522
  queued2 = false;
10464
- debug14?.("hmr: consuming queued rediscovery");
10523
+ debug13?.("hmr: consuming queued rediscovery");
10465
10524
  runRefreshCycle(work).catch((err) => {
10466
- debug14?.(
10525
+ debug13?.(
10467
10526
  "hmr: queued cycle rejected \u2014 releasing gate (%s)",
10468
10527
  err instanceof Error ? err.message : String(err)
10469
10528
  );
10470
10529
  resolveGate();
10471
10530
  });
10472
10531
  } else if (pendingEvents) {
10473
- debug14?.(
10532
+ debug13?.(
10474
10533
  "hmr: holding gate for pending events (debounce not yet fired)"
10475
10534
  );
10476
10535
  } else {
@@ -10572,7 +10631,16 @@ async function createTempRscServer(state, options = {}) {
10572
10631
  return createViteServer({
10573
10632
  root: state.projectRoot,
10574
10633
  configFile: false,
10575
- server: { middlewareMode: true },
10634
+ server: {
10635
+ middlewareMode: true,
10636
+ // Build mode: no file watching. buildStart installs the per-router tries
10637
+ // into this realm and runShellPrerenderPhase reuses it after the bundles
10638
+ // are written. A watched write in between (writeRouteTypesFiles' own
10639
+ // <router>.named-routes.gen.ts, which every router file imports) can
10640
+ // make Vite full-reload the RSC runner; the shell phase then matches in
10641
+ // a fresh realm with no tries and a root path("/*") wins (#947).
10642
+ ...options.forceBuild ? { watch: null } : {}
10643
+ },
10576
10644
  appType: "custom",
10577
10645
  logLevel: "silent",
10578
10646
  resolve: resolveConfig,
@@ -12437,7 +12505,11 @@ If this is a multi-app host router, export a createHostRouter() instance and set
12437
12505
  plugins.push(createLoaderDirectiveGuardPlugin());
12438
12506
  plugins.push(createLoaderScanStubPlugin());
12439
12507
  plugins.push(exposeActionId());
12440
- plugins.push(useCacheTransform());
12508
+ plugins.push(
12509
+ useCacheTransform(
12510
+ createCounter(createRangoDebugger(NS.transform), "use-cache")
12511
+ )
12512
+ );
12441
12513
  plugins.push(exposeInternalIds());
12442
12514
  plugins.push(exposeRouterId());
12443
12515
  plugins.push(createVersionPlugin());
@@ -12481,7 +12553,7 @@ function poke() {
12481
12553
  apply: "serve",
12482
12554
  configureServer(server) {
12483
12555
  const stdin = process.stdin;
12484
- const debug14 = process.env.RANGO_POKE_DEBUG === "1";
12556
+ const debug13 = process.env.RANGO_POKE_DEBUG === "1";
12485
12557
  const triggerReload = (source) => {
12486
12558
  server.hot.send({ type: "full-reload", path: "*" });
12487
12559
  server.config.logger.info(` browser reload (${source})`, {
@@ -12514,7 +12586,7 @@ function poke() {
12514
12586
  lines.pop();
12515
12587
  return lines;
12516
12588
  };
12517
- if (debug14) {
12589
+ if (debug13) {
12518
12590
  server.config.logger.info(
12519
12591
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
12520
12592
  { timestamp: true }
@@ -12527,7 +12599,7 @@ function poke() {
12527
12599
  );
12528
12600
  }
12529
12601
  const onData = (data) => {
12530
- if (debug14) {
12602
+ if (debug13) {
12531
12603
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
12532
12604
  timestamp: true
12533
12605
  });