@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c

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 (392) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +126 -16
  3. package/dist/bin/rango.js +319 -95
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +2724 -1053
  6. package/package.json +68 -14
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +64 -2
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +224 -32
  11. package/skills/caching/SKILL.md +279 -17
  12. package/skills/composability/SKILL.md +27 -3
  13. package/skills/css/SKILL.md +76 -0
  14. package/skills/debug-manifest/SKILL.md +4 -2
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +11 -9
  17. package/skills/hooks/SKILL.md +243 -29
  18. package/skills/host-router/SKILL.md +83 -23
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +68 -19
  21. package/skills/layout/SKILL.md +13 -9
  22. package/skills/links/SKILL.md +190 -23
  23. package/skills/loader/SKILL.md +235 -9
  24. package/skills/middleware/SKILL.md +18 -10
  25. package/skills/migrate-nextjs/SKILL.md +43 -19
  26. package/skills/migrate-react-router/SKILL.md +8 -2
  27. package/skills/mime-routes/SKILL.md +28 -1
  28. package/skills/observability/SKILL.md +172 -0
  29. package/skills/parallel/SKILL.md +18 -7
  30. package/skills/prerender/SKILL.md +65 -60
  31. package/skills/rango/SKILL.md +251 -24
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +115 -48
  34. package/skills/route/SKILL.md +46 -5
  35. package/skills/router-setup/SKILL.md +30 -8
  36. package/skills/scripts/SKILL.md +179 -0
  37. package/skills/server-actions/SKILL.md +775 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +130 -0
  40. package/skills/testing/bindings.md +103 -0
  41. package/skills/testing/cache-prerender.md +127 -0
  42. package/skills/testing/client-components.md +124 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +91 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +122 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +123 -0
  54. package/skills/typesafety/SKILL.md +322 -29
  55. package/skills/use-cache/SKILL.md +57 -14
  56. package/skills/view-transitions/SKILL.md +337 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +0 -65
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/connection-warmup.ts +134 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +192 -150
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/navigation-bridge.ts +94 -25
  70. package/src/browser/navigation-client.ts +121 -84
  71. package/src/browser/navigation-store-handle.ts +38 -0
  72. package/src/browser/navigation-store.ts +115 -67
  73. package/src/browser/navigation-transaction.ts +9 -59
  74. package/src/browser/network-error-handler.ts +34 -7
  75. package/src/browser/partial-update.ts +147 -128
  76. package/src/browser/prefetch/cache.ts +107 -56
  77. package/src/browser/prefetch/fetch.ts +204 -34
  78. package/src/browser/prefetch/queue.ts +6 -3
  79. package/src/browser/rango-state.ts +158 -76
  80. package/src/browser/react/Link.tsx +30 -7
  81. package/src/browser/react/NavigationProvider.tsx +283 -118
  82. package/src/browser/react/ScrollRestoration.tsx +10 -6
  83. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  84. package/src/browser/react/filter-segment-order.ts +66 -7
  85. package/src/browser/react/index.ts +0 -48
  86. package/src/browser/react/location-state-shared.ts +178 -8
  87. package/src/browser/react/location-state.ts +39 -14
  88. package/src/browser/react/use-action.ts +6 -15
  89. package/src/browser/react/use-handle.ts +17 -14
  90. package/src/browser/react/use-href.tsx +8 -1
  91. package/src/browser/react/use-link-status.ts +33 -8
  92. package/src/browser/react/use-navigation.ts +10 -5
  93. package/src/browser/react/use-params.ts +11 -11
  94. package/src/browser/react/use-reverse.ts +106 -0
  95. package/src/browser/react/use-router.ts +25 -3
  96. package/src/browser/react/use-search-params.ts +0 -5
  97. package/src/browser/react/use-segments.ts +11 -21
  98. package/src/browser/response-adapter.ts +99 -8
  99. package/src/browser/rsc-router.tsx +91 -24
  100. package/src/browser/scroll-restoration.ts +30 -17
  101. package/src/browser/segment-structure-assert.ts +2 -2
  102. package/src/browser/server-action-bridge.ts +214 -55
  103. package/src/browser/types.ts +80 -9
  104. package/src/browser/validate-redirect-origin.ts +43 -16
  105. package/src/build/collect-fallback-refs.ts +107 -0
  106. package/src/build/generate-manifest.ts +60 -35
  107. package/src/build/generate-route-types.ts +2 -1
  108. package/src/build/index.ts +8 -2
  109. package/src/build/prefix-tree-utils.ts +123 -0
  110. package/src/build/route-trie.ts +117 -14
  111. package/src/build/route-types/ast-route-extraction.ts +15 -8
  112. package/src/build/route-types/codegen.ts +16 -5
  113. package/src/build/route-types/include-resolution.ts +117 -23
  114. package/src/build/route-types/param-extraction.ts +6 -3
  115. package/src/build/route-types/per-module-writer.ts +22 -6
  116. package/src/build/route-types/router-processing.ts +55 -28
  117. package/src/build/route-types/scan-filter.ts +1 -1
  118. package/src/build/route-types/source-scan.ts +216 -0
  119. package/src/build/runtime-discovery.ts +9 -20
  120. package/src/cache/cache-error.ts +104 -0
  121. package/src/cache/cache-key-utils.ts +29 -13
  122. package/src/cache/cache-policy.ts +108 -34
  123. package/src/cache/cache-runtime.ts +224 -41
  124. package/src/cache/cache-scope.ts +188 -82
  125. package/src/cache/cache-tag.ts +103 -0
  126. package/src/cache/cf/cf-base64.ts +33 -0
  127. package/src/cache/cf/cf-cache-constants.ts +127 -0
  128. package/src/cache/cf/cf-cache-store.ts +1989 -378
  129. package/src/cache/cf/cf-cache-types.ts +349 -0
  130. package/src/cache/cf/cf-kv-utils.ts +46 -0
  131. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  132. package/src/cache/cf/index.ts +6 -16
  133. package/src/cache/document-cache.ts +89 -21
  134. package/src/cache/handle-snapshot.ts +70 -0
  135. package/src/cache/index.ts +10 -20
  136. package/src/cache/memory-segment-store.ts +136 -37
  137. package/src/cache/profile-registry.ts +46 -31
  138. package/src/cache/read-through-swr.ts +56 -12
  139. package/src/cache/segment-codec.ts +9 -17
  140. package/src/cache/tag-invalidation.ts +230 -0
  141. package/src/cache/types.ts +37 -100
  142. package/src/client.rsc.tsx +44 -21
  143. package/src/client.tsx +36 -61
  144. package/src/cloudflare/index.ts +11 -0
  145. package/src/cloudflare/tracing.ts +109 -0
  146. package/src/component-utils.ts +19 -0
  147. package/src/components/DefaultDocument.tsx +8 -2
  148. package/src/context-var.ts +18 -6
  149. package/src/decode-loader-results.ts +52 -0
  150. package/src/defer.ts +196 -0
  151. package/src/deps/ssr.ts +0 -1
  152. package/src/encode-kv.ts +49 -0
  153. package/src/errors.ts +30 -4
  154. package/src/escape-script.ts +52 -0
  155. package/src/handle.ts +31 -23
  156. package/src/handles/MetaTags.tsx +62 -19
  157. package/src/handles/Scripts.tsx +183 -0
  158. package/src/handles/breadcrumbs.ts +37 -8
  159. package/src/handles/is-thenable.ts +19 -0
  160. package/src/handles/meta.ts +51 -40
  161. package/src/handles/script.ts +244 -0
  162. package/src/host/cookie-handler.ts +9 -60
  163. package/src/host/errors.ts +0 -24
  164. package/src/host/index.ts +8 -2
  165. package/src/host/pattern-matcher.ts +23 -52
  166. package/src/host/router.ts +107 -99
  167. package/src/host/testing.ts +40 -27
  168. package/src/host/types.ts +37 -4
  169. package/src/host/utils.ts +1 -1
  170. package/src/href-client.ts +137 -22
  171. package/src/index.rsc.ts +96 -12
  172. package/src/index.ts +94 -14
  173. package/src/internal-debug.ts +11 -10
  174. package/src/loader-store.ts +500 -0
  175. package/src/loader.rsc.ts +20 -13
  176. package/src/loader.ts +12 -11
  177. package/src/missing-id-error.ts +68 -0
  178. package/src/outlet-context.ts +1 -1
  179. package/src/outlet-provider.tsx +1 -5
  180. package/src/prerender/param-hash.ts +16 -16
  181. package/src/prerender/store.ts +32 -37
  182. package/src/prerender.ts +61 -6
  183. package/src/redirect-origin.ts +100 -0
  184. package/src/regex-escape.ts +8 -0
  185. package/src/render-error-thrower.tsx +20 -0
  186. package/src/response-utils.ts +34 -0
  187. package/src/reverse.ts +65 -40
  188. package/src/root-error-boundary.tsx +1 -19
  189. package/src/route-content-wrapper.tsx +19 -77
  190. package/src/route-definition/dsl-helpers.ts +304 -309
  191. package/src/route-definition/helper-factories.ts +28 -140
  192. package/src/route-definition/helpers-types.ts +82 -55
  193. package/src/route-definition/index.ts +1 -2
  194. package/src/route-definition/redirect.ts +44 -11
  195. package/src/route-definition/resolve-handler-use.ts +12 -1
  196. package/src/route-definition/use-item-types.ts +29 -0
  197. package/src/route-map-builder.ts +0 -16
  198. package/src/route-types.ts +19 -46
  199. package/src/router/basename.ts +14 -0
  200. package/src/router/content-negotiation.ts +73 -25
  201. package/src/router/error-handling.ts +45 -18
  202. package/src/router/find-match.ts +44 -23
  203. package/src/router/handler-context.ts +27 -43
  204. package/src/router/instrument.ts +350 -0
  205. package/src/router/intercept-resolution.ts +39 -20
  206. package/src/router/lazy-includes.ts +10 -47
  207. package/src/router/loader-resolution.ts +155 -72
  208. package/src/router/logging.ts +0 -6
  209. package/src/router/manifest.ts +18 -29
  210. package/src/router/match-api.ts +9 -24
  211. package/src/router/match-context.ts +0 -22
  212. package/src/router/match-handlers.ts +58 -58
  213. package/src/router/match-middleware/background-revalidation.ts +40 -24
  214. package/src/router/match-middleware/cache-lookup.ts +159 -285
  215. package/src/router/match-middleware/cache-store.ts +64 -52
  216. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  217. package/src/router/match-middleware/segment-resolution.ts +0 -22
  218. package/src/router/match-pipelines.ts +1 -42
  219. package/src/router/match-result.ts +44 -74
  220. package/src/router/metrics.ts +0 -34
  221. package/src/router/middleware-types.ts +7 -134
  222. package/src/router/middleware.ts +247 -166
  223. package/src/router/navigation-snapshot.ts +0 -51
  224. package/src/router/params-util.ts +23 -0
  225. package/src/router/pattern-matching.ts +85 -94
  226. package/src/router/prefetch-cache-ttl.ts +51 -0
  227. package/src/router/prerender-match.ts +104 -65
  228. package/src/router/preview-match.ts +3 -1
  229. package/src/router/request-classification.ts +28 -62
  230. package/src/router/revalidation.ts +123 -73
  231. package/src/router/route-snapshot.ts +0 -1
  232. package/src/router/router-context.ts +3 -28
  233. package/src/router/router-interfaces.ts +83 -35
  234. package/src/router/router-options.ts +136 -5
  235. package/src/router/router-registry.ts +2 -5
  236. package/src/router/segment-resolution/fresh.ts +97 -84
  237. package/src/router/segment-resolution/helpers.ts +86 -6
  238. package/src/router/segment-resolution/loader-cache.ts +76 -39
  239. package/src/router/segment-resolution/revalidation.ts +272 -320
  240. package/src/router/segment-resolution/static-store.ts +19 -5
  241. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  242. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  243. package/src/router/segment-resolution.ts +5 -1
  244. package/src/router/segment-wrappers.ts +6 -5
  245. package/src/router/state-cookie-name.ts +33 -0
  246. package/src/router/substitute-pattern-params.ts +56 -0
  247. package/src/router/telemetry-otel.ts +161 -199
  248. package/src/router/telemetry.ts +96 -19
  249. package/src/router/timeout.ts +0 -20
  250. package/src/router/tracing.ts +206 -0
  251. package/src/router/trie-matching.ts +162 -64
  252. package/src/router/types.ts +9 -63
  253. package/src/router/url-params.ts +0 -5
  254. package/src/router.ts +110 -55
  255. package/src/rsc/handler-context.ts +3 -2
  256. package/src/rsc/handler.ts +264 -220
  257. package/src/rsc/helpers.ts +100 -6
  258. package/src/rsc/index.ts +2 -5
  259. package/src/rsc/json-route-result.ts +38 -0
  260. package/src/rsc/loader-fetch.ts +114 -38
  261. package/src/rsc/manifest-init.ts +28 -41
  262. package/src/rsc/origin-guard.ts +39 -25
  263. package/src/rsc/progressive-enhancement.ts +117 -11
  264. package/src/rsc/redirect-guard.ts +99 -0
  265. package/src/rsc/response-cache-serve.ts +238 -0
  266. package/src/rsc/response-error.ts +79 -12
  267. package/src/rsc/response-route-handler.ts +88 -188
  268. package/src/rsc/rsc-rendering.ts +98 -76
  269. package/src/rsc/runtime-warnings.ts +23 -10
  270. package/src/rsc/server-action.ts +281 -117
  271. package/src/rsc/ssr-setup.ts +16 -0
  272. package/src/rsc/transition-gate.ts +89 -0
  273. package/src/rsc/types.ts +23 -5
  274. package/src/runtime-env.ts +18 -0
  275. package/src/search-params.ts +35 -30
  276. package/src/segment-loader-promise.ts +31 -4
  277. package/src/segment-system.tsx +254 -143
  278. package/src/serialize.ts +243 -0
  279. package/src/server/context.ts +163 -51
  280. package/src/server/cookie-parse.ts +32 -0
  281. package/src/server/cookie-store.ts +80 -5
  282. package/src/server/handle-store.ts +21 -38
  283. package/src/server/loader-registry.ts +33 -42
  284. package/src/server/request-context.ts +287 -178
  285. package/src/ssr/index.tsx +21 -16
  286. package/src/static-handler.ts +10 -13
  287. package/src/testing/cache-status.ts +162 -0
  288. package/src/testing/collect-handle.ts +40 -0
  289. package/src/testing/dispatch.ts +701 -0
  290. package/src/testing/dom.entry.ts +22 -0
  291. package/src/testing/e2e/fixture.ts +188 -0
  292. package/src/testing/e2e/index.ts +128 -0
  293. package/src/testing/e2e/matchers.ts +35 -0
  294. package/src/testing/e2e/page-helpers.ts +272 -0
  295. package/src/testing/e2e/parity.ts +387 -0
  296. package/src/testing/e2e/server.ts +195 -0
  297. package/src/testing/flight-matchers.ts +97 -0
  298. package/src/testing/flight-normalize.ts +11 -0
  299. package/src/testing/flight-runtime.d.ts +57 -0
  300. package/src/testing/flight-tree.ts +682 -0
  301. package/src/testing/flight.entry.ts +52 -0
  302. package/src/testing/flight.ts +257 -0
  303. package/src/testing/generated-routes.ts +183 -0
  304. package/src/testing/index.ts +105 -0
  305. package/src/testing/internal/context.ts +371 -0
  306. package/src/testing/internal/flight-client-globals.ts +30 -0
  307. package/src/testing/internal/seed-vars.ts +54 -0
  308. package/src/testing/render-handler.ts +357 -0
  309. package/src/testing/render-route.tsx +581 -0
  310. package/src/testing/run-loader.ts +385 -0
  311. package/src/testing/run-middleware.ts +205 -0
  312. package/src/testing/run-transition-when.ts +164 -0
  313. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  314. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  315. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  316. package/src/testing/vitest-stubs/version.ts +5 -0
  317. package/src/testing/vitest.ts +305 -0
  318. package/src/theme/ThemeProvider.tsx +20 -58
  319. package/src/theme/ThemeScript.tsx +7 -9
  320. package/src/theme/constants.ts +52 -13
  321. package/src/theme/index.ts +0 -7
  322. package/src/theme/theme-context.ts +1 -5
  323. package/src/theme/theme-script.ts +22 -21
  324. package/src/theme/use-theme.ts +0 -3
  325. package/src/types/boundaries.ts +0 -35
  326. package/src/types/cache-types.ts +13 -4
  327. package/src/types/error-types.ts +30 -90
  328. package/src/types/global-namespace.ts +54 -41
  329. package/src/types/handler-context.ts +110 -62
  330. package/src/types/index.ts +3 -10
  331. package/src/types/loader-types.ts +11 -9
  332. package/src/types/request-scope.ts +112 -0
  333. package/src/types/route-config.ts +6 -50
  334. package/src/types/route-entry.ts +0 -6
  335. package/src/types/segments.ts +135 -14
  336. package/src/urls/include-helper.ts +9 -56
  337. package/src/urls/index.ts +1 -11
  338. package/src/urls/path-helper-types.ts +29 -12
  339. package/src/urls/path-helper.ts +17 -106
  340. package/src/urls/pattern-types.ts +36 -19
  341. package/src/urls/response-types.ts +22 -29
  342. package/src/urls/type-extraction.ts +58 -139
  343. package/src/urls/urls-function.ts +1 -19
  344. package/src/use-loader.tsx +292 -107
  345. package/src/vite/debug.ts +185 -0
  346. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  347. package/src/vite/discovery/discover-routers.ts +126 -85
  348. package/src/vite/discovery/discovery-errors.ts +194 -0
  349. package/src/vite/discovery/gate-state.ts +171 -0
  350. package/src/vite/discovery/prerender-collection.ts +96 -68
  351. package/src/vite/discovery/route-types-writer.ts +40 -84
  352. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  353. package/src/vite/discovery/state.ts +44 -0
  354. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  355. package/src/vite/index.ts +2 -0
  356. package/src/vite/inject-client-debug.ts +36 -0
  357. package/src/vite/plugin-types.ts +126 -8
  358. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  359. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  360. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  361. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  362. package/src/vite/plugins/expose-action-id.ts +48 -95
  363. package/src/vite/plugins/expose-id-utils.ts +88 -55
  364. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  365. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  366. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  367. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  368. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  369. package/src/vite/plugins/performance-tracks.ts +26 -25
  370. package/src/vite/plugins/refresh-cmd.ts +1 -1
  371. package/src/vite/plugins/use-cache-transform.ts +73 -83
  372. package/src/vite/plugins/version-injector.ts +40 -29
  373. package/src/vite/plugins/version-plugin.ts +37 -40
  374. package/src/vite/plugins/virtual-entries.ts +39 -25
  375. package/src/vite/rango.ts +109 -118
  376. package/src/vite/router-discovery.ts +718 -119
  377. package/src/vite/utils/ast-handler-extract.ts +26 -35
  378. package/src/vite/utils/banner.ts +1 -1
  379. package/src/vite/utils/bundle-analysis.ts +10 -15
  380. package/src/vite/utils/client-chunks.ts +184 -0
  381. package/src/vite/utils/directive-prologue.ts +40 -0
  382. package/src/vite/utils/forward-user-plugins.ts +171 -0
  383. package/src/vite/utils/manifest-utils.ts +4 -59
  384. package/src/vite/utils/package-resolution.ts +20 -52
  385. package/src/vite/utils/prerender-utils.ts +54 -39
  386. package/src/vite/utils/shared-utils.ts +90 -41
  387. package/src/browser/action-response-classifier.ts +0 -99
  388. package/src/browser/react/use-client-cache.ts +0 -58
  389. package/src/browser/shallow.ts +0 -40
  390. package/src/handles/index.ts +0 -7
  391. package/src/network-error-thrower.tsx +0 -23
  392. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Span tracing hook (platform-agnostic).
3
+ *
4
+ * The core router emits its existing performance phases (request, middleware, action,
5
+ * loaders, render, ssr) as spans by calling traceSpan() at a small set of
6
+ * execution boundaries. When no tracing is configured the call is a direct
7
+ * pass-through: fn is invoked with a no-op span, with no wrapper and no
8
+ * allocation, so a non-traced request behaves exactly as before.
9
+ *
10
+ * A platform integration supplies a SpanRunner that wraps fn in a real span.
11
+ * Two runners ship: the Cloudflare one (createCloudflareTracing in
12
+ * src/cloudflare/tracing.ts), which bridges onto executionContext.tracing.
13
+ * enterSpan, and the OTel one (createOTelTracing in router/telemetry-otel.ts),
14
+ * which bridges onto tracer.startActiveSpan. Both wrap the actual work — not a
15
+ * post-hoc event — so spans nest by async context and the platform's automatic
16
+ * spans (KV/D1/fetch) nest under the right phase.
17
+ *
18
+ * traceSpan() below is the low-level wrap primitive. It is INTERNAL: the only
19
+ * caller is observePhase() (instrument.ts), the single phase-instrumentation
20
+ * API, which co-emits the span AND the debugPerformance perf metric from one
21
+ * wrap site (or just the span, for metric:false phases) so the two surfaces
22
+ * can't drift. Every router phase routes through observePhase via the PHASES
23
+ * registry; do not call traceSpan directly from new code.
24
+ *
25
+ * Phase coverage (all via observePhase): rango.request (span-only; handler:total
26
+ * metered directly), rango.middleware (span-only incl. intercept middleware;
27
+ * pre/post metered directly), rango.action (action:<id>; server-action
28
+ * execution, JS + no-JS/PE), rango.loader (loader:<id>; single metering site at
29
+ * useLoader, plus the fetchable path), rango.handler (span-only, one per segment
30
+ * route/layout handler execution; the handler:<id> perf metric is owned by the
31
+ * track() at the call site), rango.render (render:total:<route>; normal AND
32
+ * action-revalidation renders), rango.ssr (ssr:render-html).
33
+ *
34
+ * Span-duration caveat (best-effort, never buffers): a span ends when its
35
+ * callback's value (or promise) settles. For the streaming phases (request,
36
+ * render, ssr) that is when the Response / HTML / RSC stream is CONSTRUCTED, not
37
+ * when the body finishes draining — instrumentation never wraps or buffers the
38
+ * response body, so it cannot regress response latency or streaming. A loader /
39
+ * Suspense child that resolves while the body streams therefore keeps a
40
+ * rango.loader span that can extend past its render parent; overlapping spans are
41
+ * valid (the loader really did take that long). Phase spans bound the work up to
42
+ * stream-handoff, which is also what the co-emitted perf metric measures.
43
+ *
44
+ * Both shipped runners (Cloudflare, OTel) keep the core agnostic: the
45
+ * platform-specific bridge lives at the edge behind the SpanRunner contract.
46
+ */
47
+
48
+ /**
49
+ * Minimal span handle passed to traced work. Structurally compatible with both
50
+ * Cloudflare's `Span` and OTel's `Span` (only setAttribute is used here).
51
+ */
52
+ export interface TraceSpan {
53
+ setAttribute(key: string, value: string | number | boolean): void;
54
+ }
55
+
56
+ /**
57
+ * Wraps a unit of work in a span. A runner MUST invoke fn exactly once, pass it
58
+ * a span, return fn's result unchanged, and propagate thrown errors / rejected
59
+ * promises unchanged. When fn returns a promise the span ends once it settles.
60
+ */
61
+ export type SpanRunner = <T>(name: string, fn: (span: TraceSpan) => T) => T;
62
+
63
+ /** The router phases that can be wrapped in a span. */
64
+ export type TracePhase =
65
+ | "request"
66
+ | "middleware"
67
+ | "action"
68
+ | "loader"
69
+ | "handler"
70
+ | "render"
71
+ | "ssr";
72
+
73
+ /** Per-phase span toggles. Omitted phases default to enabled. */
74
+ export interface TracePhaseToggles {
75
+ request?: boolean;
76
+ middleware?: boolean;
77
+ action?: boolean;
78
+ loader?: boolean;
79
+ handler?: boolean;
80
+ render?: boolean;
81
+ ssr?: boolean;
82
+ }
83
+
84
+ /**
85
+ * Value passed to `createRouter({ tracing })`. Produced by a platform factory
86
+ * such as `createCloudflareTracing()`.
87
+ */
88
+ export interface RouterTracingConfig {
89
+ /** Platform span runner. */
90
+ runner: SpanRunner;
91
+ /** Master switch. Defaults to true when a config object is provided. */
92
+ enabled?: boolean;
93
+ /** Per-phase span toggles. */
94
+ spans?: TracePhaseToggles;
95
+ }
96
+
97
+ /**
98
+ * Resolved tracing state stored on the router/request context. `undefined`
99
+ * means tracing is fully disabled and every traceSpan() call is a pass-through.
100
+ */
101
+ export interface ResolvedTracing {
102
+ runner: SpanRunner;
103
+ phases: Record<TracePhase, boolean>;
104
+ }
105
+
106
+ /** Shared no-op span. setAttribute is a no-op so disabled call sites stay free. */
107
+ export const NOOP_TRACE_SPAN: TraceSpan = {
108
+ setAttribute() {},
109
+ };
110
+
111
+ const ALL_PHASES_ON: Record<TracePhase, boolean> = {
112
+ request: true,
113
+ middleware: true,
114
+ action: true,
115
+ loader: true,
116
+ handler: true,
117
+ render: true,
118
+ ssr: true,
119
+ };
120
+
121
+ /**
122
+ * Resolve a user-supplied tracing config into the fast internal form, or
123
+ * `undefined` when tracing is off (no config, `enabled: false`, or no runner).
124
+ */
125
+ export function resolveTracing(
126
+ config: RouterTracingConfig | undefined,
127
+ ): ResolvedTracing | undefined {
128
+ if (
129
+ !config ||
130
+ config.enabled === false ||
131
+ typeof config.runner !== "function"
132
+ ) {
133
+ return undefined;
134
+ }
135
+ const spans = config.spans;
136
+ return {
137
+ runner: config.runner,
138
+ phases: spans
139
+ ? {
140
+ request: spans.request ?? true,
141
+ middleware: spans.middleware ?? true,
142
+ action: spans.action ?? true,
143
+ loader: spans.loader ?? true,
144
+ handler: spans.handler ?? true,
145
+ render: spans.render ?? true,
146
+ ssr: spans.ssr ?? true,
147
+ }
148
+ : ALL_PHASES_ON,
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Wrap `fn` in a span for `phase`. When tracing is off (or the phase is
154
+ * disabled) fn runs directly with a no-op span — identical to the untraced
155
+ * path. Otherwise the platform runner wraps fn so the span covers the real
156
+ * work and nests by async context.
157
+ */
158
+ export function traceSpan<T>(
159
+ tracing: ResolvedTracing | undefined,
160
+ phase: TracePhase,
161
+ name: string,
162
+ fn: (span: TraceSpan) => T,
163
+ ): T {
164
+ if (tracing === undefined || tracing.phases[phase] === false) {
165
+ return fn(NOOP_TRACE_SPAN);
166
+ }
167
+ return tracing.runner(name, fn);
168
+ }
169
+
170
+ /**
171
+ * Run `fn` once and invoke `onSettle` exactly once when it terminates — on a
172
+ * synchronous return, a synchronous throw, an async resolution, or an async
173
+ * rejection. `onSettle` receives the error (or `undefined` on success). fn's
174
+ * value is returned and errors propagate unchanged.
175
+ *
176
+ * Centralizes the run-once-then-settle control flow shared by the two span
177
+ * surfaces: observePhase records the perf metric on settle, and the OTel runner
178
+ * ends (or error-marks) the span on settle. The Cloudflare runner delegates
179
+ * settling to enterSpan, so it does not use this.
180
+ */
181
+ export function runThenSettle<T>(
182
+ fn: () => T,
183
+ onSettle: (error: unknown) => void,
184
+ ): T {
185
+ let out: T;
186
+ try {
187
+ out = fn();
188
+ } catch (error) {
189
+ onSettle(error);
190
+ throw error;
191
+ }
192
+ if (out instanceof Promise) {
193
+ return out.then(
194
+ (value) => {
195
+ onSettle(undefined);
196
+ return value;
197
+ },
198
+ (error) => {
199
+ onSettle(error);
200
+ throw error;
201
+ },
202
+ ) as unknown as T;
203
+ }
204
+ onSettle(undefined);
205
+ return out;
206
+ }
@@ -5,7 +5,11 @@
5
5
  * Falls back to null when no match is found (caller uses regex fallback).
6
6
  */
7
7
 
8
- import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
8
+ import type {
9
+ TrieNode,
10
+ TrieLeaf,
11
+ NegotiateVariant,
12
+ } from "../build/route-trie.js";
9
13
  import { safeDecodeURIComponent } from "./url-params.js";
10
14
 
11
15
  export interface TrieMatchResult {
@@ -15,10 +19,6 @@ export interface TrieMatchResult {
15
19
  sp: string;
16
20
  /** Matched route params */
17
21
  params: Record<string, string>;
18
- /** Optional param names (absent params have empty string value) */
19
- optionalParams?: string[];
20
- /** Ancestry shortCodes for layout pruning */
21
- ancestry: string[];
22
22
  /** Redirect target if trailing slash requires it */
23
23
  redirectTo?: string;
24
24
  /** Route has pre-rendered data available */
@@ -28,7 +28,7 @@ export interface TrieMatchResult {
28
28
  /** Response type for non-RSC routes (json, text, image, any) */
29
29
  responseType?: string;
30
30
  /** Negotiate variants: response-type routes sharing this path */
31
- negotiateVariants?: Array<{ routeKey: string; responseType: string }>;
31
+ negotiateVariants?: NegotiateVariant[];
32
32
  /** RSC-first: RSC route was defined before response-type variants */
33
33
  rscFirst?: true;
34
34
  }
@@ -43,14 +43,12 @@ export function tryTrieMatch(
43
43
  ): TrieMatchResult | null {
44
44
  if (!trie) return null;
45
45
 
46
- // Split pathname into segments, filtering empty strings from leading/trailing slashes
47
46
  const pathnameHasTrailingSlash =
48
47
  pathname.length > 1 && pathname.endsWith("/");
49
48
  const normalizedPath = pathnameHasTrailingSlash
50
49
  ? pathname.slice(0, -1)
51
50
  : pathname;
52
51
 
53
- // Handle root path
54
52
  if (normalizedPath === "" || normalizedPath === "/") {
55
53
  if (trie.r) {
56
54
  return validateAndBuild(
@@ -61,13 +59,24 @@ export function tryTrieMatch(
61
59
  pathnameHasTrailingSlash,
62
60
  );
63
61
  }
62
+ // A root-level wildcard ("/*") matches "/" with an empty remainder, the
63
+ // same value the regex matcher produces for the bare prefix. Without this
64
+ // the trie misses, the regex fallback runs, and its no-config branch emits
65
+ // a corrupt slice-off redirect. The static terminal still wins above.
66
+ if (trie.w) {
67
+ return validateAndBuild(
68
+ trie.w,
69
+ [],
70
+ "",
71
+ pathname,
72
+ pathnameHasTrailingSlash,
73
+ );
74
+ }
64
75
  return null;
65
76
  }
66
77
 
67
- // Remove leading slash and split
68
78
  const segments = normalizedPath.slice(1).split("/");
69
79
 
70
- // Try exact match with normalized path (no trailing slash)
71
80
  const result = walkTrie(trie, segments, 0, []);
72
81
  if (result) {
73
82
  return validateAndBuild(
@@ -76,6 +85,7 @@ export function tryTrieMatch(
76
85
  result.wildcardValue,
77
86
  pathname,
78
87
  pathnameHasTrailingSlash,
88
+ result.validatedParams,
79
89
  );
80
90
  }
81
91
 
@@ -86,11 +96,75 @@ interface WalkResult {
86
96
  leaf: TrieLeaf;
87
97
  paramValues: string[];
88
98
  wildcardValue?: string;
99
+ /**
100
+ * For a constraint-bearing leaf (leaf.cv set), the params map that
101
+ * leafConstraintsPass already decoded AND validated during the walk. Carried
102
+ * forward so validateAndBuild reuses it instead of re-decoding paramValues and
103
+ * re-running constraintsSatisfied a second time on the winning leaf. Undefined
104
+ * for unconstrained leaves (no decode/validate happened in the walk).
105
+ */
106
+ validatedParams?: Record<string, string>;
107
+ }
108
+
109
+ /**
110
+ * Check a leaf's constraints (leaf.cv) against already-resolved named params.
111
+ * Empty/undefined values are exempt (optional params that were not bound).
112
+ */
113
+ function constraintsSatisfied(
114
+ leaf: TrieLeaf,
115
+ params: Record<string, string>,
116
+ ): boolean {
117
+ if (!leaf.cv) return true;
118
+ for (const paramName in leaf.cv) {
119
+ const allowed = leaf.cv[paramName]!;
120
+ const value = params[paramName];
121
+ if (value !== undefined && value !== "" && !allowed.includes(value)) {
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+
128
+ /**
129
+ * Constraint check for a candidate terminal DURING the walk. Builds the named
130
+ * params from positional walk values (decoded the same way validateAndBuild
131
+ * does) and validates leaf.cv. Returning null lets walkTrie unwind to a
132
+ * lower-priority sibling instead of committing to a leaf that would only be
133
+ * rejected post-walk — that post-walk rejection is what forced the regex
134
+ * fallback (and its false "trie gap" R3 warning) for perfectly valid configs.
135
+ *
136
+ * On success returns the built+validated params for a constraint-bearing leaf so
137
+ * walkTrie can carry them to validateAndBuild (avoiding a second decode + a
138
+ * second constraintsSatisfied pass on the winner); returns the shared EMPTY_PASS
139
+ * sentinel for an unconstrained leaf (no work was done, nothing to carry).
140
+ */
141
+ const EMPTY_PASS: Record<string, string> = {};
142
+ function leafConstraintsPass(
143
+ leaf: TrieLeaf,
144
+ paramValues: string[],
145
+ wildcardValue: string | undefined,
146
+ ): Record<string, string> | null {
147
+ if (!leaf.cv) return EMPTY_PASS;
148
+ const params: Record<string, string> = {};
149
+ if (leaf.pa) {
150
+ for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
151
+ params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
152
+ }
153
+ }
154
+ if (wildcardValue !== undefined && "pn" in leaf) {
155
+ params[(leaf as TrieLeaf & { pn: string }).pn] =
156
+ safeDecodeURIComponent(wildcardValue);
157
+ }
158
+ return constraintsSatisfied(leaf, params) ? params : null;
89
159
  }
90
160
 
91
161
  /**
92
- * Walk the trie by segments with priority: static > param > wildcard.
93
- * Uses backtracking to try all possible matches.
162
+ * Walk the trie by segments with priority: static > suffix-param > param >
163
+ * wildcard (Priority 1-4 below; matches the canonical M4 ordering in
164
+ * docs/internal/matching-and-lazy-discovery.md).
165
+ * Uses backtracking to try all possible matches. Per-leaf constraints are
166
+ * enforced at each candidate terminal so a constraint miss backtracks to a
167
+ * lower-priority sibling rather than aborting the whole match.
94
168
  */
95
169
  function walkTrie(
96
170
  node: TrieNode,
@@ -98,10 +172,37 @@ function walkTrie(
98
172
  index: number,
99
173
  paramValues: string[],
100
174
  ): WalkResult | null {
101
- // All segments consumed: check for terminal
102
175
  if (index === segments.length) {
103
176
  if (node.r) {
104
- return { leaf: node.r, paramValues: [...paramValues] };
177
+ const validatedParams = leafConstraintsPass(
178
+ node.r,
179
+ paramValues,
180
+ undefined,
181
+ );
182
+ if (validatedParams) {
183
+ return {
184
+ leaf: node.r,
185
+ paramValues: [...paramValues],
186
+ validatedParams,
187
+ };
188
+ }
189
+ }
190
+ // A wildcard at this node matches the bare prefix with an empty remainder
191
+ // (e.g. "/files" against "/files/*"), mirroring the regex matcher's `*=""`.
192
+ // walkTrie otherwise only reaches node.w in the index<length branch below,
193
+ // so without this a request to the wildcard's own prefix misses the trie
194
+ // and the regex fallback emits a corrupt redirect. A static terminal
195
+ // (node.r) still wins.
196
+ if (node.w) {
197
+ const validatedParams = leafConstraintsPass(node.w, paramValues, "");
198
+ if (validatedParams) {
199
+ return {
200
+ leaf: node.w,
201
+ paramValues: [...paramValues],
202
+ wildcardValue: "",
203
+ validatedParams,
204
+ };
205
+ }
105
206
  }
106
207
  return null;
107
208
  }
@@ -109,14 +210,15 @@ function walkTrie(
109
210
  const segment = segments[index];
110
211
  const staticChild = node.s?.[segment];
111
212
 
112
- // Priority 1: Static match
113
213
  if (staticChild) {
114
214
  const result = walkTrie(staticChild, segments, index + 1, paramValues);
115
215
  if (result) return result;
116
216
  }
117
217
 
118
- // Priority 2: Suffix-param match (e.g., :productId.html)
119
218
  if (node.xp) {
219
+ // node.xp keys are pre-sorted longest-suffix-first at build time
220
+ // (route-trie.ts sortSuffixParams), so the first match is the most specific
221
+ // suffix: `/app.min.js` matches `:file.min.js` before `:file.js`.
120
222
  for (const suffix in node.xp) {
121
223
  if (segment.endsWith(suffix) && segment.length > suffix.length) {
122
224
  const paramValue = segment.slice(0, -suffix.length);
@@ -133,22 +235,30 @@ function walkTrie(
133
235
  }
134
236
  }
135
237
 
136
- // Priority 3: Param match
137
- if (node.p) {
238
+ // A required single-segment param captures 1+ chars (the regex matcher emits
239
+ // `([^/]+)`), so an empty path segment from a double slash (`/a//b`) must NOT
240
+ // bind `:s` to "". Reject it here so the trie matches the regex contract and
241
+ // a malformed URL 404s instead of running the handler with an empty param.
242
+ // The suffix-param branch above already requires `segment.length > suffix`,
243
+ // and node.w may legitimately be empty, so only this branch needs the guard.
244
+ if (node.p && segment !== "") {
138
245
  paramValues.push(segment);
139
246
  const result = walkTrie(node.p.c, segments, index + 1, paramValues);
140
247
  paramValues.pop();
141
248
  if (result) return result;
142
249
  }
143
250
 
144
- // Priority 4: Wildcard match (consumes rest)
145
251
  if (node.w) {
146
252
  const rest = joinRemainingSegments(segments, index);
147
- return {
148
- leaf: node.w,
149
- paramValues: [...paramValues],
150
- wildcardValue: rest,
151
- };
253
+ const validatedParams = leafConstraintsPass(node.w, paramValues, rest);
254
+ if (validatedParams) {
255
+ return {
256
+ leaf: node.w,
257
+ paramValues: [...paramValues],
258
+ wildcardValue: rest,
259
+ validatedParams,
260
+ };
261
+ }
152
262
  }
153
263
 
154
264
  return null;
@@ -156,16 +266,20 @@ function walkTrie(
156
266
 
157
267
  function joinRemainingSegments(segments: string[], start: number): string {
158
268
  if (start >= segments.length) return "";
159
-
160
- let rest = segments[start]!;
161
- for (let i = start + 1; i < segments.length; i++) {
162
- rest += "/" + segments[i]!;
163
- }
164
- return rest;
269
+ return segments.slice(start).join("/");
165
270
  }
166
271
 
167
272
  /**
168
273
  * Post-match: validate constraints and handle trailing slash logic.
274
+ *
275
+ * `validatedParams` is the params map walkTrie already decoded AND validated via
276
+ * leafConstraintsPass for a constraint-bearing winning leaf. When present (and
277
+ * non-empty) we reuse it verbatim and SKIP the second decode + the second
278
+ * constraintsSatisfied pass — both are byte-identical to the walk-time work.
279
+ * When absent (unconstrained leaf, or the root-path call sites that never walk)
280
+ * we still BUILD the params here (that is not redundant — they must be returned)
281
+ * and run constraintsSatisfied for safety; an unconstrained leaf's check is a
282
+ * cheap early `!leaf.cv` return.
169
283
  */
170
284
  function validateAndBuild(
171
285
  leaf: TrieLeaf,
@@ -173,46 +287,32 @@ function validateAndBuild(
173
287
  wildcardValue: string | undefined,
174
288
  originalPathname: string,
175
289
  pathnameHasTrailingSlash: boolean,
290
+ validatedParams?: Record<string, string>,
176
291
  ): TrieMatchResult | null {
177
- // Build named params by zipping leaf.pa with positional paramValues.
178
- // Params are URL-decoded at this boundary so ctx.params holds the values
179
- // apps expect (matching Express/React Router) and round-trip cleanly
180
- // through ctx.reverse.
181
- const params: Record<string, string> = {};
182
- if (leaf.pa) {
183
- for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
184
- params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
292
+ let params: Record<string, string>;
293
+ // EMPTY_PASS (the unconstrained sentinel) and undefined both mean "nothing was
294
+ // pre-validated"; only a populated map carried from a constraint-bearing leaf
295
+ // lets us skip the rebuild + re-check.
296
+ if (validatedParams && validatedParams !== EMPTY_PASS) {
297
+ params = validatedParams;
298
+ } else {
299
+ params = {};
300
+ if (leaf.pa) {
301
+ for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
302
+ params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
303
+ }
185
304
  }
186
- }
187
305
 
188
- // Add wildcard param (wildcard leaves have pn from TrieNode.w type)
189
- if (wildcardValue !== undefined && "pn" in leaf) {
190
- params[(leaf as TrieLeaf & { pn: string }).pn] =
191
- safeDecodeURIComponent(wildcardValue);
192
- }
193
-
194
- // Validate constraints against decoded values so constraint lists can be
195
- // written in decoded form (e.g. ["en-GB", "en US"]).
196
- if (leaf.cv) {
197
- for (const paramName in leaf.cv) {
198
- const allowed = leaf.cv[paramName]!;
199
- const value = params[paramName];
200
- if (value !== undefined && value !== "" && !allowed.includes(value)) {
201
- return null;
202
- }
306
+ if (wildcardValue !== undefined && "pn" in leaf) {
307
+ params[(leaf as TrieLeaf & { pn: string }).pn] =
308
+ safeDecodeURIComponent(wildcardValue);
203
309
  }
204
- }
205
310
 
206
- // Fill in empty strings for optional params that weren't matched
207
- if (leaf.op) {
208
- for (const name of leaf.op) {
209
- if (!(name in params)) {
210
- params[name] = "";
211
- }
311
+ if (!constraintsSatisfied(leaf, params)) {
312
+ return null;
212
313
  }
213
314
  }
214
315
 
215
- // Trailing slash handling
216
316
  const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
217
317
  let redirectTo: string | undefined;
218
318
 
@@ -230,10 +330,8 @@ function validateAndBuild(
230
330
  routeKey: leaf.n,
231
331
  sp: leaf.sp,
232
332
  params,
233
- ancestry: leaf.a,
234
333
  };
235
334
 
236
- if (leaf.op) result.optionalParams = leaf.op;
237
335
  if (redirectTo) result.redirectTo = redirectTo;
238
336
  if (leaf.pr) result.pr = true;
239
337
  if (leaf.pt) result.pt = true;
@@ -22,27 +22,11 @@ import type {
22
22
  ShouldRevalidateFn,
23
23
  } from "../types";
24
24
 
25
- /**
26
- * Result of resolving loaders with revalidation
27
- * Contains both segments to render and all matched segment IDs
28
- */
29
- export interface LoaderRevalidationResult {
30
- segments: ResolvedSegment[];
31
- matchedIds: string[];
32
- }
33
-
34
- /**
35
- * Result of resolving segments with revalidation
36
- * Contains both segments to render and all matched segment IDs
37
- */
38
25
  export interface SegmentRevalidationResult {
39
26
  segments: ResolvedSegment[];
40
27
  matchedIds: string[];
41
28
  }
42
29
 
43
- /**
44
- * Action context type for revalidation
45
- */
46
30
  export type ActionContext = {
47
31
  actionId?: string;
48
32
  actionUrl?: URL;
@@ -50,23 +34,6 @@ export type ActionContext = {
50
34
  formData?: FormData;
51
35
  };
52
36
 
53
- /**
54
- * Dependencies passed to segment resolution functions
55
- * These are created within createRouter and passed to extracted utilities
56
- */
57
- export interface RouterDependencies<TEnv> {
58
- findNearestErrorBoundary: (
59
- entry: EntryData | null,
60
- ) => ReactNode | ErrorBoundaryHandler | null;
61
- findNearestNotFoundBoundary: (
62
- entry: EntryData | null,
63
- ) => ReactNode | NotFoundBoundaryHandler | null;
64
- }
65
-
66
- /**
67
- * Dependencies injected from createRouter closure into extracted segment resolution functions.
68
- * These are the closure-bound helpers that cannot be imported directly.
69
- */
70
37
  export interface SegmentResolutionDeps<TEnv = any> {
71
38
  wrapLoaderPromise: <T>(
72
39
  promise: Promise<T>,
@@ -98,23 +65,16 @@ export interface SegmentResolutionDeps<TEnv = any> {
98
65
  ) => ReactNode | NotFoundBoundaryHandler | null;
99
66
  notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
100
67
  callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
68
+ /**
69
+ * Router-level default for the per-segment `transition({ viewTransition })`
70
+ * flag, from createRouter({ viewTransition }). Resolved into each segment's
71
+ * transition config during resolution (only `false` is stamped) so the render
72
+ * gate reads the boundary decision off the segment on both server and client.
73
+ * Undefined is treated as "auto" (wrap).
74
+ */
75
+ viewTransitionDefault?: "auto" | false;
101
76
  }
102
77
 
103
- /**
104
- * Dependencies injected from createRouter closure into extracted intercept resolution functions.
105
- */
106
- export interface InterceptResolutionDeps<TEnv = any> {
107
- wrapLoaderPromise: SegmentResolutionDeps<TEnv>["wrapLoaderPromise"];
108
- evaluateInterceptWhen: (
109
- intercept: InterceptEntry,
110
- selectorContext: InterceptSelectorContext | null,
111
- isAction: boolean,
112
- ) => boolean;
113
- }
114
-
115
- /**
116
- * Dependencies injected from createRouter closure into extracted match API functions.
117
- */
118
78
  export interface MatchApiDeps<TEnv = any> {
119
79
  findMatch: (pathname: string, ms?: any) => any;
120
80
  getMetricsStore: () => any;
@@ -129,23 +89,13 @@ export interface MatchApiDeps<TEnv = any> {
129
89
  getRouteMap: () => Record<string, string>;
130
90
  }
131
91
 
132
- /**
133
- * Title descriptor types for template support
134
- */
135
92
  export type TitleDescriptor =
136
93
  | string
137
94
  | { template: string; default: string } // For layouts - template applied to child titles
138
- | { absolute: string }; // Bypass parent template
95
+ | { absolute: string };
139
96
 
140
- /**
141
- * Unset descriptor to remove inherited meta
142
- * Key format matches getMetaKey output: "title", "name:description", "property:og:image"
143
- */
144
97
  export type UnsetDescriptor = { unset: string };
145
98
 
146
- /**
147
- * Base meta descriptor types (sync values)
148
- */
149
99
  export type MetaDescriptorBase =
150
100
  | { charSet: "utf-8" }
151
101
  | { title: TitleDescriptor }
@@ -157,10 +107,6 @@ export type MetaDescriptorBase =
157
107
  | UnsetDescriptor
158
108
  | { [name: string]: unknown };
159
109
 
160
- /**
161
- * Meta descriptor that can be sync or async.
162
- * Use Promise<MetaDescriptorBase> for streaming meta that resolves after initial render.
163
- */
164
110
  export type MetaDescriptor = MetaDescriptorBase | Promise<MetaDescriptorBase>;
165
111
 
166
112
  type LdJsonObject = { [Key in string]: LdJsonValue } & {
@@ -25,11 +25,6 @@ export function safeDecodeURIComponent(raw: string): string {
25
25
  }
26
26
  }
27
27
 
28
- // encodeURIComponent over-encodes for path segments. After running it,
29
- // un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
30
- // keeps human-readable characters that are legal in a path segment.
31
- // Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
32
- // encoded.
33
28
  const PATH_SAFE_ESCAPES: Record<string, string> = {
34
29
  "%3A": ":",
35
30
  "%40": "@",