@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
@@ -8,9 +8,6 @@ argument-hint: [component]
8
8
 
9
9
  Layouts wrap child routes and persist during navigation within their scope.
10
10
 
11
- Canonical semantics reference:
12
- [docs/execution-model.md](../../docs/internal/execution-model.md)
13
-
14
11
  ## Basic Layout
15
12
 
16
13
  ```typescript
@@ -118,6 +115,8 @@ function ShopLayout() {
118
115
  }
119
116
  ```
120
117
 
118
+ A layout's `transition()` config wraps the content that flows through `<Outlet />` — not the layout chrome itself, and not sibling `<ParallelOutlet />` slots. Stacking transitions across nested layouts collapses around the deepest default outlet content. See [skills/view-transitions](../view-transitions/SKILL.md) for the full wrap rules and intercept-modal interaction.
119
+
121
120
  ## Named Outlets
122
121
 
123
122
  For parallel routes, use named outlets:
@@ -203,8 +202,10 @@ layout(<ShopLayout />, () => [
203
202
  ])
204
203
 
205
204
  // Or revalidate based on conditions
205
+ import * as CartActions from "./actions/cart";
206
+
206
207
  layout(<CartLayout />, () => [
207
- revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
208
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
208
209
 
209
210
  path("/cart", CartPage, { name: "cart" }),
210
211
  ])
@@ -222,8 +223,9 @@ them on both producer and consumer segments:
222
223
 
223
224
  ```typescript
224
225
  // revalidation-contracts.ts
225
- export const revalidateCartData = ({ actionId }) =>
226
- actionId?.includes("src/actions/cart.ts#addToCart") ?? false;
226
+ import { addToCart } from "./actions/cart";
227
+
228
+ export const revalidateCartData = (ctx) => ctx.isAction(addToCart) || undefined;
227
229
  ```
228
230
 
229
231
  ```typescript
@@ -243,9 +245,10 @@ You can also package them as importable handoff helpers:
243
245
  ```typescript
244
246
  // revalidation-contracts.ts
245
247
  import { revalidate } from "@rangojs/router";
248
+ import * as AuthActions from "./actions/auth";
246
249
 
247
- export const revalidateAuthData = ({ actionId }) =>
248
- actionId?.includes("src/actions/auth.ts#") ?? false;
250
+ export const revalidateAuthData = (ctx) =>
251
+ ctx.isAction(AuthActions) || undefined;
249
252
  export const revalidateAuth = () => [revalidate(revalidateAuthData)];
250
253
  ```
251
254
 
@@ -263,6 +266,7 @@ layout(<ShellLayout />, () => [
263
266
  ```typescript
264
267
  import { urls } from "@rangojs/router";
265
268
  import { Outlet, ParallelOutlet } from "@rangojs/router/client";
269
+ import * as CartActions from "./actions/cart";
266
270
 
267
271
  function ShopLayout() {
268
272
  return (
@@ -292,7 +296,7 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
292
296
  }, () => [
293
297
  // Layout loaders
294
298
  loader(CartLoader, () => [
295
- revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
299
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
296
300
  ]),
297
301
 
298
302
  // Parallel routes
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: links
3
- description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, and scopedReverse
4
- argument-hint: [ctx.reverse|href|useHref|useMount|scopedReverse]
3
+ description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse
4
+ argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
5
5
  ---
6
6
 
7
7
  # Links & URL Generation
@@ -10,7 +10,12 @@ argument-hint: [ctx.reverse|href|useHref|useMount|scopedReverse]
10
10
 
11
11
  **Default server API: `ctx.reverse()`.** Generate URLs from the handler context — it's typed, auto-fills mount params, and resolves local (`.name`) and absolute (`name.sub`) names.
12
12
 
13
- **`reverse()` is server-only.** It depends on the route manifest and handler context, neither of which are available in the browser. Client components receive URLs as props, loader data, or server-action return values — they never call `reverse` directly.
13
+ **On the client, two patterns:**
14
+
15
+ 1. **Receive URLs as props / loader data / action return.** The default. The server has the full route manifest and handler context — generate URLs there and hand strings to client components.
16
+ 2. **`useReverse(routes)`.** Import a generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` (the leading dot is optional). Mount-aware via `useMount()`, auto-fills params from `useParams()`, fully typed from the imported map. Use this when a client component needs to generate URLs into a known module without round-tripping through the server.
17
+
18
+ `ctx.reverse()` itself is **server-only** — it depends on the full route manifest and handler context. Client components never import or call it.
14
19
 
15
20
  ## Server: ctx.reverse()
16
21
 
@@ -127,26 +132,30 @@ path("/product/:slug", (ctx) => {
127
132
 
128
133
  ## Client components: receive URLs as props
129
134
 
130
- `reverse()` is not available inside `"use client"` modules — there is no handler context and no route manifest in the browser bundle. Generate the URL on the server and hand it to the client component.
135
+ `ctx.reverse()` is not available inside `"use client"` modules — there is no handler context in the browser bundle. For in-module names, prefer `useReverse(routes)` (see below) and import the relevant `urls/*.gen.js`. For cross-module URLs or one-off names, generate the URL on the server and hand it to the client component using one of these three patterns:
131
136
 
132
- Three patterns, in order of preference:
137
+ 1. Pass as a prop from a server component:
133
138
 
134
139
  ```tsx
135
- // 1. Pass as a prop from a server component
136
140
  // server
137
141
  function BlogPostPage(ctx: HandlerContext) {
138
142
  return <ShareButton url={ctx.reverse(".post", { slug: ctx.params.slug })} />;
139
143
  }
144
+ ```
140
145
 
141
- // client
146
+ ```tsx
142
147
  "use client";
148
+
143
149
  export function ShareButton({ url }: { url: string }) {
144
- return <button onClick={() => navigator.clipboard.writeText(url)}>Share</button>;
150
+ return (
151
+ <button onClick={() => navigator.clipboard.writeText(url)}>Share</button>
152
+ );
145
153
  }
146
154
  ```
147
155
 
156
+ 2. Return from a loader (attached to the route via the DSL):
157
+
148
158
  ```tsx
149
- // 2. Return from a loader (attached to the route via the DSL)
150
159
  // server — loaders/nav.ts
151
160
  export const NavLoader = createLoader((ctx) => ({
152
161
  home: ctx.reverse("home"),
@@ -157,9 +166,11 @@ export const NavLoader = createLoader((ctx) => ({
157
166
  const urlpatterns = urls(({ path, loader }) => [
158
167
  path("/", HomePage, { name: "home" }, () => [loader(NavLoader)]),
159
168
  ]);
169
+ ```
160
170
 
161
- // client
171
+ ```tsx
162
172
  "use client";
173
+
163
174
  function Nav() {
164
175
  const { data } = useLoader(NavLoader);
165
176
  return <Link to={data.home}>Home</Link>;
@@ -168,16 +179,19 @@ function Nav() {
168
179
 
169
180
  `useLoader()` requires the loader to be attached to an active route. If you need on-demand fetching instead, use `useFetchLoader()`.
170
181
 
182
+ 3. Return from a server action:
183
+
171
184
  ```tsx
172
- // 3. Return from a server action
173
- // server
174
185
  "use server";
186
+
175
187
  export async function getProductUrl(slug: string) {
176
188
  const ctx = getRequestContext();
177
189
  return ctx.reverse("product", { slug });
178
190
  }
179
191
  ```
180
192
 
193
+ See `/server-actions` for the full action surface (`getRequestContext()` is the same context middleware and handlers use).
194
+
181
195
  For static path strings (not named routes), client components can use `href()` — see below.
182
196
 
183
197
  ## Client: href()
@@ -199,7 +213,17 @@ function GlobalNav() {
199
213
  }
200
214
  ```
201
215
 
202
- `href()` provides compile-time validation via `ValidPaths` type. Paths are validated against registered route patterns using `PatternToPath`.
216
+ `href()` provides compile-time validation via the `Rango.Path` type. Paths are validated against registered route patterns using `PatternToPath`.
217
+
218
+ When wrapping `href()`, type the wrapper's path parameter as `Rango.Path` so it
219
+ keeps the same generated-route validation. `Rango.Path` is ambient — no import,
220
+ just like `Rango.Env` / `Rango.Vars`:
221
+
222
+ ```typescript
223
+ import { href } from "@rangojs/router/client";
224
+
225
+ export const appHref = (path: Rango.Path): string => href(path);
226
+ ```
203
227
 
204
228
  `href()` is a raw path helper — it is **not** basename-aware. It returns the path as-is (or with the include mount prefix via `useHref()`). For basename-aware navigation, use `Link`, `useRouter().push()`, or `reverse()`, which auto-prefix root-relative paths with the router's basename.
205
229
 
@@ -245,18 +269,161 @@ function MountInfo() {
245
269
 
246
270
  `useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree.
247
271
 
248
- ## When to use what
272
+ ## Client: useReverse(routes)
273
+
274
+ Hook that returns a typed local reverse function for a `routes` map imported from a generated `.gen.ts` next to a `urls()` module. The route map is the **exposure boundary** — `useReverse` only knows about names in that map, never the full app manifest.
275
+
276
+ > **Which map?** `useReverse` accepts any routes map. Prefer the per-module `routes` (e.g. `urls/blog.gen.ts`): it gives **mount-aware** local `.name` reverse (auto-prefixes the `include()` mount) and only that module's names enter the client bundle. You _can_ instead pass `router.named-routes.gen.ts` (`NamedRoutes`) for **global** names (`blog.post`; the leading dot is optional) — it is a plain importable map and works on the client (it is **not** server-only) — but its paths are **absolute** while `useReverse` mount-prefixes, so it is correct only at the root mount (under a non-root mount it double-prefixes), and importing it pulls every route name and pattern in the app into the client bundle (a small names-to-paths map — not components or loaders), versus the per-module map which exposes only one module's names. So the per-module map is preferred for in-module links; the named-routes map is the escape hatch for global names.
277
+
278
+ ```tsx
279
+ "use client";
280
+ import { Link, useReverse } from "@rangojs/router/client";
281
+ import { routes as blogRoutes } from "../urls/blog.gen.js";
282
+
283
+ export function BlogNav() {
284
+ const reverse = useReverse(blogRoutes);
285
+
286
+ return (
287
+ <nav>
288
+ <Link to={reverse("index")}>Blog</Link>
289
+ <Link to={reverse("post", { postId: "hello" })}>Post</Link>
290
+ </nav>
291
+ );
292
+ }
293
+ ```
294
+
295
+ ### How it resolves
296
+
297
+ 1. Strips an optional leading `.` and looks up the name in the imported `routes` map.
298
+ 2. Joins the local pattern with the surrounding `useMount()` value — the include's URL pattern.
299
+ 3. Substitutes params: explicit params from the call, then auto-filled from `useParams()` for anything still unresolved (mount params like `:tenantId` flow in this way).
300
+ 4. Appends a query string if a search object is passed and the route has a `search` schema.
301
+
302
+ ### Mount-relativity
303
+
304
+ Patterns in the generated `routes` map are **mount-relative** — they're the patterns as defined inside the `urls()` module, _not_ the full app paths. Mount-joining happens at runtime via `useMount()`, so the same component works under any include:
305
+
306
+ ```typescript
307
+ // urls/blog.tsx
308
+ export const blogPatterns = urls(({ path }) => [
309
+ path("/", BlogIndex, { name: "index" }),
310
+ path("/:postId", BlogPost, { name: "post" }),
311
+ ]);
312
+
313
+ // Generated urls/blog.gen.ts
314
+ // export const routes = { index: "/", post: "/:postId" } as const;
315
+
316
+ // urls.tsx — same module mounted twice
317
+ include("/news", blogPatterns, { name: "news" }), // <BlogNav> renders /news, /news/hello
318
+ include("/journal", blogPatterns, { name: "diary" }), // <BlogNav> renders /journal, /journal/hello
319
+ ```
249
320
 
250
- | Context | API | Resolves | Use for |
251
- | ---------------- | ------------------------------- | ------------------------------- | ----------------------------------- |
252
- | Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | **Default** server-side URL generation |
253
- | Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
254
- | Client component | (URL passed as prop / loader data / action return) | Named routes | Any URL derived from a named route — generate on server, pass in |
255
- | Client component | `href("/path")` | Absolute paths (static strings) | Static navigation where no named-route lookup is needed |
256
- | Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
257
- | Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
321
+ The `/` pattern under a non-root mount collapses cleanly: under `/news`, `reverse(".index")` returns `/news` (no trailing slash), matching `ctx.reverse(".index")` on the server.
322
+
323
+ ### Auto-filled params (mount params)
324
+
325
+ When the include itself carries `:params`, those are auto-filled from `useParams()` so the caller doesn't have to thread them through:
326
+
327
+ ```typescript
328
+ // urls.tsx
329
+ include("/tenant/:tenantId", clientReversePatterns, { name: "tenant" });
330
+ ```
331
+
332
+ ```tsx
333
+ // At /tenant/acme/posts/p1, useParams() = { tenantId: "acme", postId: "p1" }
334
+ const reverse = useReverse(clientReverseRoutes);
335
+
336
+ reverse(".index"); // "/tenant/acme"
337
+ reverse(".post", { postId: "p2" }); // "/tenant/acme/posts/p2" (tenantId auto-filled)
338
+ reverse(".post", { tenantId: "other", postId: "p2" }); // "/tenant/other/posts/p2" (explicit override)
339
+ ```
340
+
341
+ Auto-fill follows soft navigation — when the matched route changes, `useReverse` re-renders with the new params.
342
+
343
+ ### Search schemas
344
+
345
+ Routes declared with a `search` schema accept a typed search object as the third argument:
346
+
347
+ ```typescript
348
+ // urls/blog.tsx
349
+ path("/search", SearchPage, {
350
+ name: "search",
351
+ search: { q: "string", page: "number?" },
352
+ }),
353
+
354
+ // Generated as: search: { path: "/search", search: { q: "string", page: "number?" } }
355
+ ```
356
+
357
+ ```tsx
358
+ const reverse = useReverse(blogRoutes);
359
+ reverse(".search", {}, { q: "hello world", page: 2 });
360
+ // "/news/search?q=hello%20world&page=2"
361
+ ```
362
+
363
+ ### Errors
364
+
365
+ - Unknown name: throws `Unknown route: ".not-a-route"`.
366
+ - Missing required param: throws `Missing param "postId" for route ".detail"`.
367
+
368
+ Both happen synchronously during `reverse()` — wrap calls in try/catch (or an ErrorBoundary if the throw happens during render) when you need to surface them as UI.
369
+
370
+ ### The leading dot is optional
371
+
372
+ `reverse("post")` and `reverse(".post")` resolve **identically** — the leading dot is cosmetic. The map you import IS the scope, so there is no separate global namespace to disambiguate and the dot carries no meaning; it exists only as a readability convention and for parity with `ctx.reverse(".name")` on the server. To link into a different module, import that module's `routes`:
373
+
374
+ ```tsx
375
+ import { routes as blogRoutes } from "../urls/blog.gen.js";
376
+ import { routes as shopRoutes } from "../urls/shop.gen.js";
377
+
378
+ function CrossNav() {
379
+ const blog = useReverse(blogRoutes);
380
+ const shop = useReverse(shopRoutes);
381
+ return (
382
+ <nav>
383
+ <Link to={blog("index")}>Blog</Link>
384
+ <Link to={shop("cart")}>Cart</Link>
385
+ </nav>
386
+ );
387
+ }
388
+ ```
389
+
390
+ ### Codegen
391
+
392
+ Each `urls()` module gets a sibling `.gen.ts` with the local route names and patterns, produced by `rango generate`:
393
+
394
+ ```bash
395
+ pnpm exec rango generate src/urls/blog.tsx
396
+ # or generate everything under a directory:
397
+ pnpm exec rango generate src/urls --static
398
+ ```
399
+
400
+ Don't edit the file by hand — re-run codegen when patterns change.
401
+
402
+ **Today the Vite plugin only regenerates the router-level `*.named-routes.gen.ts`.** Per-module `urls/*.gen.ts` files are emitted only by the CLI (or `writePerModuleRouteTypesForFile` programmatically). Commit the generated files and re-run `rango generate` whenever a `urls()` module's `path()`/`include()` shape changes. A common workflow is to wire it into a `predev` script:
403
+
404
+ ```jsonc
405
+ // package.json
406
+ {
407
+ "scripts": {
408
+ "predev": "rango generate src",
409
+ "dev": "vite",
410
+ },
411
+ }
412
+ ```
413
+
414
+ ## When to use what
258
415
 
259
- > `reverse()` is server-only. Client components never import or call it — they receive the already-resolved string.
416
+ | Context | API | Resolves | Use for |
417
+ | ---------------- | -------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
418
+ | Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | **Default** server-side URL generation |
419
+ | Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
420
+ | Client component | `useReverse(routes)` | Local names from an imported `routes` map | Typed in-module URL generation without round-tripping the server |
421
+ | Client component | (URL passed as prop / loader data / action return) | Named routes | Cross-module URLs or one-off names you don't want to import |
422
+ | Client component | `href("/path")` | Absolute paths (static strings) | Static navigation where no named-route lookup is needed |
423
+ | Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
424
+ | Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
425
+
426
+ > `ctx.reverse()` is server-only. On the client, either generate URLs on the server and pass them in, or import the `routes` map and use `useReverse(routes)` for in-module names.
260
427
 
261
428
  ## Complete example: mounted module
262
429