@rangojs/router 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -12,7 +12,7 @@
12
12
  * interface PaginationData { current: number; total: number }
13
13
  * export const Pagination = createVar<PaginationData>();
14
14
  *
15
- * // Non-cacheable var — ctx.get(User) throws inside a cache() boundary
15
+ * // Non-cacheable var — ctx.get(User) throws inside cache() or "use cache"
16
16
  * export const User = createVar<UserData>({ cache: false });
17
17
  *
18
18
  * // handler
@@ -26,7 +26,7 @@
26
26
  export interface ContextVar<T> {
27
27
  readonly __brand: "context-var";
28
28
  readonly key: symbol;
29
- /** When false, ctx.get(var) throws inside a cache() boundary. */
29
+ /** When false, ctx.get(var) throws inside cache() or "use cache". */
30
30
  readonly cache: boolean;
31
31
  /** Phantom field to carry the type parameter. Never set at runtime. */
32
32
  readonly __type?: T;
@@ -35,9 +35,10 @@ export interface ContextVar<T> {
35
35
  export interface ContextVarOptions {
36
36
  /**
37
37
  * When false, marks this variable as non-cacheable.
38
- * Reading this var with ctx.get() inside a cache() boundary throws. Use for
39
- * inherently request-specific data (user sessions, auth tokens, etc.) that
40
- * must never be baked into cached segments.
38
+ * Reading this var with ctx.get() inside a cache() boundary or a "use cache"
39
+ * function throws. Use for inherently request-specific data (user sessions,
40
+ * auth tokens, etc.) that must never be baked into cached segments or
41
+ * entries; pass the value into a "use cache" function as an argument.
41
42
  *
42
43
  * @default true
43
44
  */
@@ -45,7 +45,7 @@ export function escapeJsonForScript(json: string): string {
45
45
  * source; for untrusted dynamic data, JSON-encode it and read it back, rather than
46
46
  * inlining it as code.
47
47
  *
48
- * Used by the Script handle's <Scripts> renderer for inline `children`.
48
+ * Used by the Script handle's <Html.Scripts> renderer for inline `children`.
49
49
  */
50
50
  export function escapeScriptBody(js: string): string {
51
51
  return js.replace(/<!--/g, "<\\u0021--").replace(/<\/(script)/gi, "<\\/$1");
@@ -2,17 +2,18 @@
2
2
 
3
3
  /**
4
4
  * Component to render collected meta descriptors in the document head.
5
+ * Public as `Html.Meta` (`@rangojs/router/client`).
5
6
  *
6
- * Deferred (Promise) meta descriptors are resolved before MetaTags renders
7
+ * Deferred (Promise) meta descriptors are resolved before `<Html.Meta />` renders
7
8
  * (server-side on the full render, client-side before apply on navigation), so
8
9
  * it only ever receives resolved descriptors and never suspends.
9
10
  *
10
- * When theme is enabled in the router config, MetaTags also renders
11
+ * When theme is enabled in the router config, `<Html.Meta />` also renders
11
12
  * the theme initialization script to prevent FOUC (flash of unstyled content).
12
- * This makes MetaTags the sole FOUC-script injector for apps that render it;
13
- * the standalone `<ThemeScript />` is only needed when MetaTags is not used.
14
- * Rendering both is safe (the inline script guards listener registration) but
15
- * redundant.
13
+ * This makes `<Html.Meta />` the sole FOUC-script injector for apps that render
14
+ * it; the standalone `<ThemeScript />` is only needed when `<Html.Meta />` is not
15
+ * used. Rendering both is safe (the inline script guards listener registration)
16
+ * but redundant.
16
17
  *
17
18
  * @example
18
19
  * ```tsx
@@ -20,7 +21,7 @@
20
21
  * return (
21
22
  * <html lang="en" suppressHydrationWarning>
22
23
  * <head>
23
- * <MetaTags />
24
+ * <Html.Meta />
24
25
  * </head>
25
26
  * <body>...</body>
26
27
  * </html>
@@ -179,17 +180,18 @@ export function renderMetaDescriptor(
179
180
  }
180
181
 
181
182
  /**
182
- * Renders all collected meta descriptors from route handlers.
183
+ * Renders all collected meta descriptors from route handlers. Public as
184
+ * `Html.Meta` (`@rangojs/router/client`).
183
185
  *
184
- * Place this component inside the `<head>` element of your document.
186
+ * Place `<Html.Meta />` inside the `<head>` element of your document.
185
187
  * It will automatically update when meta descriptors change during navigation.
186
188
  *
187
189
  * When theme is enabled in router config, also renders the theme initialization
188
190
  * script to prevent FOUC (flash of unstyled content).
189
191
  *
190
- * Deferred (Promise) meta descriptors are resolved BEFORE MetaTags renders —
192
+ * Deferred (Promise) meta descriptors are resolved BEFORE this renders —
191
193
  * server-side on the full/SSR render, client-side before apply on navigation
192
- * (resolve-by-default) — so MetaTags only ever receives resolved descriptors and
194
+ * (resolve-by-default) — so it only ever receives resolved descriptors and
193
195
  * never suspends.
194
196
  */
195
197
  export function MetaTags(): React.ReactNode {
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * Renders the scripts collected by the Script handle into the document.
4
+ * Renders the scripts collected by the Script handle into the document. Public
5
+ * as `Html.Scripts` (`@rangojs/router/client`).
5
6
  *
6
- * Place `<Scripts />` inside `<head>` (default) and, if you push body scripts,
7
- * `<Scripts position="body" />` at the top of `<body>`. Each site renders the
8
- * configs whose `position` matches; the request CSP nonce is applied
7
+ * Place `<Html.Scripts />` inside `<head>` (default) and, if you push body
8
+ * scripts, `<Html.Scripts position="body" />` at the top of `<body>`. Each site
9
+ * renders the configs whose `position` matches; the request CSP nonce is applied
9
10
  * automatically to every DOCUMENT-RENDERED <script> (consumers never pass it). An
10
11
  * async script first encountered on a soft navigation is injected client-side
11
12
  * where the nonce is unavailable, so it carries no nonce and relies on
@@ -24,11 +25,11 @@
24
25
  * ```tsx
25
26
  * <html>
26
27
  * <head>
27
- * <MetaTags />
28
- * <Scripts />
28
+ * <Html.Meta />
29
+ * <Html.Scripts />
29
30
  * </head>
30
31
  * <body>
31
- * <Scripts position="body" />
32
+ * <Html.Scripts position="body" />
32
33
  * {children}
33
34
  * </body>
34
35
  * </html>
@@ -80,10 +81,10 @@ function passthroughAttributes(
80
81
  if (dev) {
81
82
  console.warn(
82
83
  isHandler
83
- ? `[Scripts] event handler "${key}" in a script's attributes is ` +
84
+ ? `[Html.Scripts] event handler "${key}" in a script's attributes is ` +
84
85
  `dropped; callbacks cannot cross the server -> client handle ` +
85
86
  `boundary. Use a "use client" component for load/error handling.`
86
- : `[Scripts] managed field "${key}" in a script's attributes is ` +
87
+ : `[Html.Scripts] managed field "${key}" in a script's attributes is ` +
87
88
  `dropped; set it via the ScriptConfig fields (the request nonce ` +
88
89
  `is applied automatically).`,
89
90
  );
@@ -129,7 +130,7 @@ function renderScript(
129
130
  async
130
131
  ) {
131
132
  console.warn(
132
- `[Scripts] An async external script (src="${src}") is hoisted into ` +
133
+ `[Html.Scripts] An async external script (src="${src}") is hoisted into ` +
133
134
  `<head> by React; position: "body" is ignored for it.`,
134
135
  );
135
136
  }
@@ -19,7 +19,7 @@
19
19
  * return (
20
20
  * <html>
21
21
  * <head>
22
- * <MetaTags />
22
+ * <Html.Meta />
23
23
  * </head>
24
24
  * ...
25
25
  * </html>
@@ -225,7 +225,7 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
225
225
  * Built-in handle for managing document metadata.
226
226
  *
227
227
  * Use `ctx.use(Meta)` in route handlers to push meta descriptors.
228
- * Use `<MetaTags />` component to render them in the document head.
228
+ * Use the `<Html.Meta />` component to render them in the document head.
229
229
  *
230
230
  * Deferred (Promise) descriptors are resolved before collectMeta runs
231
231
  * (resolve-by-default), so they participate in deduplication and `%s`
@@ -3,22 +3,23 @@
3
3
  * route/layout handlers.
4
4
  *
5
5
  * Push from a SERVER handler with `ctx.use(Script)(config)`; render with the
6
- * `<Scripts />` component (from `@rangojs/router/client`) placed in the Document
7
- * `<head>` (and optionally a second `<Scripts position="body" />` at the top of
8
- * `<body>`). This mirrors the Meta / <MetaTags> pair.
6
+ * `<Html.Scripts />` component (from `@rangojs/router/client`) placed in the
7
+ * Document `<head>` (and optionally a second `<Html.Scripts position="body" />`
8
+ * at the top of `<body>`). This mirrors the Meta / <Html.Meta> pair.
9
9
  *
10
- * The request CSP nonce is applied AUTOMATICALLY by <Scripts> to document-rendered
11
- * scripts; consumers never pass a nonce. (An async script first loaded on a soft
12
- * navigation is injected client-side without a nonce — it relies on
13
- * 'strict-dynamic' or a host allowance; see the EXECUTION CONTRACT below and the
14
- * /scripts skill.) A ScriptConfig is fully serializable (it crosses the
15
- * server -> client handle-collection boundary), so callbacks like onLoad are NOT
16
- * supported — a consumer needing them renders their own "use client" script.
10
+ * The request CSP nonce is applied AUTOMATICALLY by <Html.Scripts> to
11
+ * document-rendered scripts; consumers never pass a nonce. (An async script
12
+ * first loaded on a soft navigation is injected client-side without a nonce —
13
+ * it relies on 'strict-dynamic' or a host allowance; see the EXECUTION CONTRACT
14
+ * below and the /scripts skill.) A ScriptConfig is fully serializable (it
15
+ * crosses the server -> client handle-collection boundary), so callbacks like
16
+ * onLoad are NOT supported — a consumer needing them renders their own
17
+ * "use client" script.
17
18
  *
18
19
  * EXECUTION CONTRACT (see the /scripts skill for the full story):
19
20
  * - Inline (`children`) and ordered external (`src`, optional `defer`) scripts
20
21
  * are DOCUMENT-LOAD scripts: they execute only when present in the initial HTML
21
- * response. <Scripts> freezes them after hydration, so a later client (soft)
22
+ * response. <Html.Scripts> freezes them after hydration, so a later client (soft)
22
23
  * navigation never inserts an inert copy — React creates client-mounted
23
24
  * <script> elements via innerHTML, which the HTML spec makes non-executing.
24
25
  * - Async external scripts (`src` + `async: true`) are React RESOURCES: they load
@@ -78,9 +79,9 @@ export type ScriptAttributes = Omit<
78
79
  /** Fields shared by every script shape. */
79
80
  interface ScriptConfigBase {
80
81
  /**
81
- * Where <Scripts> renders this script.
82
- * - "head" (default): the `<head>` <Scripts> site.
83
- * - "body": the `<Scripts position="body" />` site at the top of <body>.
82
+ * Where <Html.Scripts> renders this script.
83
+ * - "head" (default): the `<head>` <Html.Scripts> site.
84
+ * - "body": the `<Html.Scripts position="body" />` site at the top of <body>.
84
85
  * Note: an external `async` script is hoisted into <head> by React regardless.
85
86
  */
86
87
  position?: "head" | "body";
@@ -96,7 +97,7 @@ interface ScriptConfigBase {
96
97
  /**
97
98
  * Inline script: a raw JS body rendered in place, escaped against `</script>`
98
99
  * breakout. DOCUMENT-LOAD only (executes when present in the initial HTML;
99
- * <Scripts> freezes it after hydration so navigation never inserts an inert
100
+ * <Html.Scripts> freezes it after hydration so navigation never inserts an inert
100
101
  * copy). `id` is REQUIRED — inline scripts are never deduped by React, so a
101
102
  * layout and a child pushing the same bootstrap would inject it twice. It is also
102
103
  * rendered as the script's DOM `id`. Forbids `src`/`async`/`defer`. For analytics
@@ -146,7 +147,7 @@ export interface OrderedScriptConfig extends ScriptConfigBase {
146
147
  * inline (`id` + `children`), external async (`src` + `async: true`), or external
147
148
  * ordered (`src`, optional `defer`). Invalid combinations (both `src`+`children`,
148
149
  * `async`+`defer`, inline without `id`) are compile errors. The CSP nonce is
149
- * applied by <Scripts>, never here.
150
+ * applied by <Html.Scripts>, never here.
150
151
  */
151
152
  export type ScriptConfig =
152
153
  | InlineScriptConfig
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * ```typescript
9
- * import { href } from "rsc-router/client";
9
+ * import { href } from "@rangojs/router/client";
10
10
  *
11
11
  * href("/blog/my-post"); // ✓ matches /blog/:slug
12
12
  * href("/shop/product/widget"); // ✓ matches /shop/product/:slug
package/src/html.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Members of the `Html` namespace; client.tsx and client.rsc.tsx expose this
3
+ * module as `import * as Html`. A module namespace, not an object literal, so
4
+ * bundlers drop members an app never renders (a literal kept opt-in
5
+ * ScrollRestoration in every app: +313 B gzip, over the router-chunk ratchet).
6
+ * No "use client": each member stays its own module's client reference, so a
7
+ * server component can dot into `Html`. Pinned by __tests__/html.test.ts and
8
+ * testing/__tests__/html-namespace.rsc-test.tsx.
9
+ */
10
+ export { MetaTags as Meta } from "./handles/MetaTags.js";
11
+ export { Scripts } from "./handles/Scripts.js";
12
+ export { ScrollRestoration } from "./browser/react/ScrollRestoration.js";
package/src/index.rsc.ts CHANGED
@@ -51,6 +51,7 @@ export type {
51
51
  LoaderDefinition,
52
52
  LoaderFn,
53
53
  LoaderContext,
54
+ LoaderOptions,
54
55
  FetchableLoaderOptions,
55
56
  LoadOptions,
56
57
  // Error boundary types
@@ -188,6 +189,9 @@ export {
188
189
  type RouterRequestInput,
189
190
  } from "./router.js";
190
191
 
192
+ // Route manifest snapshot returned by router.debugManifest()
193
+ export type { SerializedManifest } from "./debug.js";
194
+
191
195
  // RSC handler types (server-side)
192
196
  export type { HandlerCacheConfig } from "./rsc/types.js";
193
197
  export {
@@ -254,6 +258,7 @@ export type {
254
258
  export type {
255
259
  ScopedReverseFunction,
256
260
  ReverseFunction,
261
+ GlobalReverseFunction,
257
262
  ExtractLocalRoutes,
258
263
  ParamsFor,
259
264
  } from "./reverse.js";
package/src/index.ts CHANGED
@@ -334,6 +334,7 @@ export type { BreadcrumbItem } from "./handles/breadcrumbs.js";
334
334
  export type {
335
335
  ScopedReverseFunction,
336
336
  ReverseFunction,
337
+ GlobalReverseFunction,
337
338
  ExtractLocalRoutes,
338
339
  ParamsFor,
339
340
  } from "./reverse.js";
@@ -162,10 +162,13 @@ export class LoaderStore {
162
162
  * browser/partial-update.ts calls INSIDE every transition commit, so a
163
163
  * reader whose content is held on screen by that transition pins
164
164
  * `isLoading: true` for the data it is still showing until the transition
165
- * lands (use-loader.tsx). The new tree's own read of the loader suspends at
166
- * the read site and never observes the flag. A Set, not a flag: overlapping
167
- * navigations (second click before the first stream lands) keep the family
168
- * pending until the LAST stream settles.
165
+ * lands (use-loader.tsx). Family-keyed on purpose: a persisting layout
166
+ * reader of the same createLoader the child is re-running is pinned too
167
+ * (the loader is in flight; not "this segment's copy is being replaced" —
168
+ * issue #862, working as designed). The new tree's own read of the loader
169
+ * suspends at the read site and never observes the flag. A Set, not a
170
+ * flag: overlapping navigations (second click before the first stream
171
+ * lands) keep the family pending until the LAST stream settles.
169
172
  */
170
173
  private readonly pendingStreams = new Map<string, Set<Promise<unknown>>>();
171
174
 
@@ -44,7 +44,7 @@ import { isRouteNotFoundError } from "../errors.js";
44
44
  import { createReverseFunction } from "../router/handler-context.js";
45
45
  import { executeMiddleware, matchMiddleware } from "../router/middleware.js";
46
46
  import type { MiddlewareEntry } from "../router/middleware.js";
47
- import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
47
+ import { getGlobalRouteMap } from "../route-map-builder.js";
48
48
  import {
49
49
  resolvePprConfig,
50
50
  type ResolvedPprConfig,
@@ -234,14 +234,11 @@ async function attemptBuildCapture(
234
234
  setRequestContextParams(preview.params ?? {}, preview.routeKey);
235
235
  }
236
236
 
237
- const routeReverse = createReverseFunction(
238
- getGlobalRouteMap(),
239
- preview?.routeKey,
240
- preview?.params ?? {},
241
- preview?.routeKey
242
- ? isRouteRootScoped(preview.routeKey, router.id)
243
- : undefined,
244
- );
237
+ // Global-only, like the live request's middleware reverse
238
+ // (rsc/handler.ts: createReverseFunction(getRequiredRouteMap())): no
239
+ // include() scope, no param auto-fill. A scoped reverse here resolved
240
+ // `.name` at build while the live request threw `Unknown route`.
241
+ const middlewareReverse = createReverseFunction(getGlobalRouteMap());
245
242
 
246
243
  const runCapture = () =>
247
244
  runBuildCaptureFinal({
@@ -268,7 +265,7 @@ async function attemptBuildCapture(
268
265
  env,
269
266
  variables,
270
267
  runCapture,
271
- routeReverse,
268
+ middlewareReverse,
272
269
  baseCtx,
273
270
  );
274
271
 
@@ -281,7 +278,7 @@ async function attemptBuildCapture(
281
278
  env,
282
279
  variables,
283
280
  runRouteMiddleware,
284
- routeReverse,
281
+ middlewareReverse,
285
282
  baseCtx,
286
283
  );
287
284
  });
package/src/reverse.ts CHANGED
@@ -207,6 +207,43 @@ export type ScopedReverseFunction<
207
207
  ): string;
208
208
  };
209
209
 
210
+ /**
211
+ * Reverse function that resolves global route names only: no include() scope,
212
+ * no param auto-fill.
213
+ *
214
+ * This is the `ctx.reverse` of middleware (`MiddlewareContext`) and
215
+ * response-route handlers (`ResponseHandlerContext`). On a live request both
216
+ * get `createReverseFunction(routeMap)` with no route name or params (the
217
+ * middleware callers in rsc/handler.ts and rsc/loader-fetch.ts,
218
+ * handleResponseRoute, router/intercept-resolution.ts; mirrored by
219
+ * testing/run-middleware.ts and testing/dispatch.ts), so a dot-local `.name`
220
+ * has no scope to resolve against and throws `Unknown route`. Build-time PPR
221
+ * shell capture (prerender/build-shell-capture.ts) gives global and route
222
+ * middleware the same global-only reverse.
223
+ *
224
+ * - Route map with a string index signature (no generated map, the permissive
225
+ * `Record<string, string>` fallback): any name except a dot-prefixed literal.
226
+ * `Exclude` is required because `keyof` collapses to `string` there, so the
227
+ * global overloads of `ScopedReverseFunction` would accept `.name` too.
228
+ * - Concrete route map: the global overloads of `ScopedReverseFunction`, with
229
+ * name and param validation. The empty local map (`{}`) leaves no dot-prefixed
230
+ * overload that can match.
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * reverse("blog.post", { slug: "hello" }) // ok: global route + params
235
+ * reverse(".post", { slug: "hello" }) // compile error: no include() scope
236
+ * ```
237
+ */
238
+ export type GlobalReverseFunction<TGlobalRoutes> =
239
+ string extends keyof TGlobalRoutes
240
+ ? <TName extends string>(
241
+ name: Exclude<TName, `.${string}`>,
242
+ params?: Record<string, string>,
243
+ search?: Record<string, unknown>,
244
+ ) => string
245
+ : ScopedReverseFunction<{}, TGlobalRoutes>;
246
+
210
247
  /**
211
248
  * Extract local routes type from UrlPatterns
212
249
  * Used with scopedReverse() to get the routes type from patterns
@@ -290,11 +327,14 @@ export type { RouteResponse } from "./urls.js";
290
327
  * Get a locally-typed reverse function from ctx.reverse for composable modules.
291
328
  *
292
329
  * This is a type-only cast - ctx.reverse already resolves names at runtime.
293
- * Provides type safety: `.name` validates against local routes,
294
- * `name` validates against global named-routes.
330
+ * Only the module's LOCAL routes are typed: `.name` and bare `name` both
331
+ * validate against the local map (the global map parameter defaults to it).
332
+ * Always dot-prefix local names — a bare name type-checks against the local
333
+ * map but resolves as a global name at runtime. For global names, call
334
+ * ctx.reverse directly.
295
335
  *
296
336
  * @param reverse - The ctx.reverse function from HandlerContext
297
- * @returns The same reverse function, typed with local + global routes
337
+ * @returns The same reverse function, typed with the module's local routes
298
338
  *
299
339
  * @example
300
340
  * ```typescript
@@ -305,7 +345,7 @@ export type { RouteResponse } from "./urls.js";
305
345
  *
306
346
  * reverse(".index"); // ✓ Local route
307
347
  * reverse(".post", { slug: "x" }); // ✓ Local with params
308
- * reverse("shop.cart"); // ✓ Global route
348
+ * ctx.reverse("shop.cart"); // Global route: use ctx.reverse
309
349
  *
310
350
  * return <BlogIndex />;
311
351
  * }, { name: "index" }),