@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
@@ -1,12 +1,24 @@
1
1
  ---
2
2
  name: parallel
3
- description: Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router. Use when a layout needs multiple independently-loading regions (e.g. a sidebar and main panel), or rendering more than one route segment at the same URL.
3
+ description: Define parallel routes (named slots) for multi-column layouts, sidebars, and UI-less metadata slots in @rangojs/router. Use when a layout needs multiple independently-loading regions (e.g. a sidebar and main panel), or rendering more than one route segment at the same URL.
4
4
  argument-hint: [@slot-name]
5
5
  ---
6
6
 
7
7
  # Parallel Routes
8
8
 
9
- Parallel routes render multiple components simultaneously in named slots.
9
+ `parallel({ "@slot": handler, ... }, use?)` renders extra components alongside
10
+ the page in named slots. The owning layout (or route) places each slot with
11
+ `<ParallelOutlet name="@slot" />`. Use it for sidebars, multi-column layouts,
12
+ UI-less metadata slots, and regions that load and stream independently.
13
+
14
+ Slot rules:
15
+
16
+ - Slot names start with `@` and must not contain `.` (a dot is rejected at
17
+ definition time).
18
+ - `parallel()` can't be nested inside another `parallel()`, and `intercept()`
19
+ can't be declared inside one.
20
+ - A slot value is a handler function, a ReactNode, a `Static()` definition, or
21
+ a slot descriptor `{ handler, use }` (see "Two scopes for explicit `use`").
10
22
 
11
23
  ## Not this skill if…
12
24
 
@@ -80,7 +92,7 @@ set by an outer handler or layout, revalidate that outer segment too, or
80
92
  have the parallel reload/guard the data itself.
81
93
 
82
94
  ```typescript
83
- path("/dashboard/:id", (ctx) => {
95
+ path("/dashboard/:id", async (ctx) => {
84
96
  const user = await getUser(ctx.params.id);
85
97
  ctx.set("user", user);
86
98
  return <DashboardPage user={user} />;
@@ -100,13 +112,18 @@ path("/dashboard/:id", (ctx) => {
100
112
 
101
113
  Parallel slot handlers can call `ctx.use(Meta)` or `ctx.use(Breadcrumbs)` to
102
114
  push handle data. (Loader bodies can too — see `/loader` → "Writing Handles
103
- from Loaders" — which often replaces the UI-less `@meta` slot below.) The data is associated with the **parent** layout or route
104
- segment, not the parallel segment itself. This is because parallels execute
105
- after their parent handler and inherit its segment scope.
115
+ from Loaders" — which often replaces the UI-less `@meta` slot below.)
116
+
117
+ A slot's pushes are stored under the slot's own segment (`<parent>.@slot`),
118
+ separate from the parent's pushes:
106
119
 
107
- This works well for document-level metadata — the handle data follows the
108
- parent's lifecycle (appears when the parent is mounted, removed when it
109
- unmounts).
120
+ - **Order:** they are collected immediately after the parent segment's pushes,
121
+ before any child route's. A route's `@meta` slot therefore comes after every
122
+ layout above it, and a layout-level slot comes before the routes it wraps.
123
+ - **Lifecycle:** they appear while the slot is mounted with its parent and are
124
+ removed when it unmounts.
125
+ - **Partial updates:** a slot-only revalidation replaces just the slot's
126
+ pushes; the parent's pushes stay intact.
110
127
 
111
128
  ```typescript
112
129
  parallel({
@@ -120,16 +137,15 @@ parallel({
120
137
  })
121
138
  ```
122
139
 
123
- Multiple parallels on the same parent can each push handle data — they all
124
- accumulate under the parent segment ID.
140
+ Multiple parallels on the same parent can each push handle data — each slot's
141
+ pushes accumulate in its own bucket.
125
142
 
126
143
  ### Pattern: `@meta` slot for per-route metadata overrides
127
144
 
128
145
  A dedicated `@meta` parallel slot lets routes define metadata separately from
129
146
  their handler logic. The layout sets defaults via a title template, and each
130
- route overrides via its own `@meta` slot. Since child segments push after
131
- parents and `collectMeta` uses last-wins deduplication, overrides work
132
- naturally.
147
+ route overrides via its own `@meta` slot. The route's slot pushes after the
148
+ layout, and `Meta` keeps the last value per key, so the override wins.
133
149
 
134
150
  > **Loader-derived metadata: push from the loader instead.** This slot's
135
151
  > `await ctx.use(ProductLoader)` routes the data through HANDLER consumption —
@@ -183,13 +199,20 @@ parallel(
183
199
  "@sidebar": () => <CategorySidebar />,
184
200
  },
185
201
  () => [
186
- loader(CategoriesLoader),
202
+ // The loader keeps its own revalidation: by default it re-runs after
203
+ // every action. Scope it here to stop that.
204
+ loader(CategoriesLoader, () => [revalidate(() => false)]),
187
205
  loading(<SidebarSkeleton />),
188
- revalidate(() => false), // Never revalidate sidebar
206
+ revalidate(() => false), // the slot component renders once, then is kept
189
207
  ]
190
208
  )
191
209
  ```
192
210
 
211
+ A `revalidate()` in the slot's `use()` decides whether the **slot component**
212
+ re-renders. Each `loader()` inside it is revalidated separately, with its own
213
+ `revalidate()` items and the loader defaults (see `/loader` → "`revalidate()`
214
+ return shapes").
215
+
193
216
  ### Streaming Behavior
194
217
 
195
218
  Parallels with `loading()` are **independent streaming units**. They don't
@@ -225,6 +248,9 @@ parallel(
225
248
  Slot handlers can carry their own loader, loading, error/notFound boundaries, revalidation, and transition defaults via `.use`. The mount site then declares **just the slot names** — no per-call data wiring.
226
249
 
227
250
  ```typescript
251
+ import { loader, loading, revalidate, type Handler } from "@rangojs/router";
252
+ import { revalidateCartData } from "./revalidation-contracts";
253
+
228
254
  const CartSummary: Handler = async (ctx) => {
229
255
  const cart = await ctx.use(CartLoader);
230
256
  return <CartSummaryView cart={cart} />;
@@ -291,8 +317,8 @@ When multiple `parallel()` calls define the same slot name, **the last
291
317
  definition wins**. Earlier definitions of that slot are removed. Other
292
318
  slots from the earlier call are preserved.
293
319
 
294
- This enables composition patterns where included routes override
295
- parent-defined slots:
320
+ This lets a mount site override one slot that a shared factory or a
321
+ handler's `.use` defined (see `/handler-use` → "Replacing a whole slot"):
296
322
 
297
323
  ```typescript
298
324
  layout(DashboardLayout, () => [
@@ -309,10 +335,11 @@ layout(DashboardLayout, () => [
309
335
  ])
310
336
  ```
311
337
 
312
- After resolution, the layout has two parallel entries:
313
-
314
- - `{ "@footer": () => <Footer /> }` (first call, `@sidebar` removed)
315
- - `{ "@sidebar": () => <CustomSidebar /> }` (second call, wins)
338
+ Each slot is stored as its own entry keyed by slot name, so a later
339
+ `parallel()` replaces only the keys it names. After resolution `@footer` comes
340
+ from the first call and `@sidebar` from the second — including that call's
341
+ `use()` items (loaders, loading, revalidate); the first call's items for
342
+ `@sidebar` are gone with it.
316
343
 
317
344
  ## Multiple Parallel Slots
318
345
 
@@ -359,18 +386,25 @@ parallel(
359
386
  },
360
387
  () => [
361
388
  loader(CartLoader),
362
- // Revalidate when cart actions occur
389
+ // Layout-level slot (kept after actions by default): also re-render after
390
+ // cart actions. Under a path() the slot re-renders after every action anyway.
363
391
  revalidate((ctx) => ctx.isAction(CartActions) || undefined),
364
392
  ]
365
393
  )
366
394
  ```
367
395
 
368
- Where the slot sits decides its action default. A parallel under a
369
- `path()` (or one of its orphan layouts) belongs to the route entry and
370
- revalidates together with it on every action — handler-set data stays
371
- consistent with no configuration. A parallel under a standalone
372
- `layout()` entry follows the parent-chain default instead: skipped on
373
- actions unless a `revalidate()` opts it in.
396
+ Where the slot sits decides its default (once it is on screen; the first
397
+ render always happens):
398
+
399
+ | Slot declared under | Navigation | Action |
400
+ | --------------------------------------- | ------------------------------------ | ---------- |
401
+ | a `path()` or one of its orphan layouts | re-renders when params/search change | re-renders |
402
+ | a standalone `layout()` | kept | kept |
403
+
404
+ A route-scoped slot belongs to the route entry, so handler-set data stays
405
+ consistent with no configuration. A layout-level slot follows the parent-chain
406
+ default: kept unless a `revalidate()` opts it in. The slot's loaders are
407
+ revalidated separately (see "Parallel Routes with Loaders").
374
408
 
375
409
  In either position, revalidating only the parallel does not re-run outer
376
410
  handlers/layouts. If the slot reads `ctx.get()` data established above
@@ -382,7 +416,7 @@ A `revalidate()` callback may return a hard `boolean`, a soft
382
416
  `undefined`) to defer to the next revalidator. See
383
417
  [loader/SKILL.md#revalidate-return-shapes](../loader/SKILL.md#revalidate-return-shapes)
384
418
  for the full contract — it's the same across `loader()`, `path()`,
385
- `layout()`, `parallel()`, and `intercept()`.
419
+ `layout()`, and `parallel()`.
386
420
 
387
421
  A route-scoped slot revalidates on ANY params or search change by default —
388
422
  including query-only navigations (`?tab=…`) its content doesn't depend on. If
@@ -396,14 +430,15 @@ either, and keeps the previous param's content on e.g. a product-to-product
396
430
  navigation.
397
431
 
398
432
  `revalidate()` decides whether to _re_-render a slot, never whether to render
399
- it the first time. A slot the browser has not rendered yet has nothing cached
400
- to keep showing, so returning `false` for it would just leave a hole. On that
401
- first render the decision is clamped to `true` and your callback's `false` is
402
- ignored; from the second visit onward it is honored and the browser keeps the
403
- copy it already has. This is what makes `revalidate(() => false)` mean "render
404
- once, then never refetch" rather than "sometimes never appear at all" — before
405
- the clamp, landing on a sibling route and navigating in left the slot blank
406
- until a full reload.
433
+ it the first time. A slot the browser has not rendered yet has nothing to keep
434
+ showing, so on that first render the decision is clamped to `true` and your
435
+ callback's `false` is ignored; from then on it is honored and the browser keeps
436
+ the copy it already has. So `revalidate(() => false)` means "render once, then
437
+ never re-render", never "don't appear".
438
+
439
+ Revalidate callbacks must be synchronous. A callback that returns a Promise is
440
+ ignored (the default decision is kept) and dev logs a warning — move async work
441
+ into a loader.
407
442
 
408
443
  ### Revalidation Contracts for Parallel Dependencies
409
444
 
@@ -412,9 +447,10 @@ the parallel consumer:
412
447
 
413
448
  ```typescript
414
449
  // revalidation-contracts.ts
450
+ import type { Revalidate } from "@rangojs/router";
415
451
  import * as CartActions from "./actions/cart";
416
452
 
417
- export const revalidateCartData = (ctx) =>
453
+ export const revalidateCartData: Revalidate = (ctx) =>
418
454
  ctx.isAction(CartActions) || undefined;
419
455
 
420
456
  layout(CartLayout, () => [