@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -28,9 +28,29 @@ const DEFAULT_ACTION_STATE: TrackedActionState = {
28
28
  // Maximum number of history entries to cache (URLs visited)
29
29
  const HISTORY_CACHE_SIZE = 20;
30
30
 
31
- // Cache entry: [url-key, segments, stale, handleData?]
32
- // stale=true means the data may be outdated and should be revalidated on access
33
- type HistoryCacheEntry = [string, ResolvedSegment[], boolean, HandleData?];
31
+ // Cache entry:
32
+ // [url-key, segments, stale, handleData?, routerId?, navInstance?, handlesPending?]
33
+ // stale=true means the data may be outdated and should be revalidated on access.
34
+ // navInstance is the monotonic nav-instance token (see navInstance below): it
35
+ // identifies the per-commit visit that owns this entry. generateHistoryKey is
36
+ // URL-only, so A->B->A reuses the same key; the token lets a late async
37
+ // resolution tell its own visit's entry apart from a newer same-URL visit's, so
38
+ // a stale nav can never clobber a fresher one.
39
+ // handlesPending=true means the entry's handle data is INCOMPLETE (a deferred
40
+ // Meta was still pending when the user navigated away, so it never streamed). A
41
+ // popstate return must REVALIDATE WITH A FULL RE-RENDER (no client segment IDs)
42
+ // to re-stream the handles — a diff-only revalidation omits unchanged segments'
43
+ // handles, so the deferred Meta would never land. Cleared once the deferred Meta
44
+ // resolves while the entry is still owned.
45
+ type HistoryCacheEntry = [
46
+ string,
47
+ ResolvedSegment[],
48
+ boolean,
49
+ HandleData?,
50
+ string?,
51
+ number?,
52
+ boolean?,
53
+ ];
34
54
 
35
55
  /**
36
56
  * Shallow clone handleData to avoid reference sharing between cache entries.
@@ -38,7 +58,7 @@ type HistoryCacheEntry = [string, ResolvedSegment[], boolean, HandleData?];
38
58
  * since mutations happen at the array level, not on individual data objects.
39
59
  * This preserves any non-serializable types (React elements, functions, etc.)
40
60
  */
41
- function cloneHandleData(handleData: HandleData): HandleData {
61
+ export function cloneHandleData(handleData: HandleData): HandleData {
42
62
  const cloned: HandleData = {};
43
63
  for (const [handleKey, segmentMap] of Object.entries(handleData)) {
44
64
  cloned[handleKey] = {};
@@ -124,14 +144,14 @@ export interface NavigationStoreConfig {
124
144
 
125
145
  /**
126
146
  * Enable cross-tab cache invalidation via BroadcastChannel (default: true)
127
- * When cache is cleared (via server actions or useClientCache().clear()),
147
+ * When cache is cleared (via server actions or invalidateClientCache()),
128
148
  * other tabs will also clear their cache
129
149
  */
130
150
  crossTabSync?: boolean;
131
151
 
132
152
  /**
133
153
  * Auto-refresh when another tab mutates data on the same path (default: true)
134
- * Triggered when cache is cleared via server actions or useClientCache().clear()
154
+ * Triggered when cache is cleared via server actions or invalidateClientCache()
135
155
  * Requires crossTabSync to be enabled
136
156
  */
137
157
  crossTabAutoRefresh?: boolean;
@@ -233,6 +253,14 @@ export function createNavigationStore(
233
253
  // Oldest entries (at front) are removed when over cacheSize limit
234
254
  const historyCache: HistoryCacheEntry[] = [];
235
255
 
256
+ // Monotonic nav-instance token. Bumped each time a cache entry is created or
257
+ // replaced in cacheSegmentsForHistory (i.e. once per commit). Because
258
+ // generateHistoryKey is URL-only, two visits to the same URL share a key; this
259
+ // token gives each visit a distinct identity so a late async handle resolution
260
+ // can tell whether it still owns the live page / the target cache entry, and
261
+ // never overwrite a newer same-URL visit's state.
262
+ let navInstance = 0;
263
+
236
264
  // Current history key (set on navigation, stored in history.state)
237
265
  let currentHistoryKey = config?.initialHistoryKey || generateHistoryKey();
238
266
 
@@ -242,6 +270,10 @@ export function createNavigationStore(
242
270
  config.initialHistoryKey,
243
271
  config.initialSegments,
244
272
  false,
273
+ undefined,
274
+ undefined,
275
+ ++navInstance,
276
+ false,
245
277
  ]);
246
278
  }
247
279
 
@@ -258,6 +290,11 @@ export function createNavigationStore(
258
290
  // Used to maintain intercept context during action revalidation
259
291
  let interceptSourceUrl: string | null = null;
260
292
 
293
+ // Router identity - tracks which router is currently active.
294
+ // When this changes on a partial response, the client forces a full
295
+ // tree replacement instead of reconciling with stale segments.
296
+ let currentRouterId: string | undefined;
297
+
261
298
  // Action state tracking (for useAction hook)
262
299
  // Maps action function ID to its tracked state
263
300
  const actionStates = new Map<string, TrackedActionState>();
@@ -269,18 +306,17 @@ export function createNavigationStore(
269
306
  /**
270
307
  * Create a debounced function that batches rapid calls
271
308
  */
309
+ // A non-keyed notifier is the keyed one restricted to a single constant key;
310
+ // its own keyed instance means the "" key never collides with action keys.
272
311
  function createDebouncedNotifier<T extends (...args: any[]) => void>(
273
312
  fn: T,
274
313
  ms: number = 20,
275
314
  ): T {
276
- let timeout: ReturnType<typeof setTimeout> | null = null;
277
- return ((...args: Parameters<T>) => {
278
- if (timeout !== null) clearTimeout(timeout);
279
- timeout = setTimeout(() => {
280
- timeout = null;
281
- fn(...args);
282
- }, ms);
283
- }) as T;
315
+ const keyed = createKeyedDebouncedNotifier(
316
+ (_key: string, ...args: any[]) => fn(...args),
317
+ ms,
318
+ );
319
+ return ((...args: Parameters<T>) => keyed("", ...args)) as T;
284
320
  }
285
321
 
286
322
  /**
@@ -325,12 +361,24 @@ export function createNavigationStore(
325
361
  }
326
362
 
327
363
  /**
328
- * Mark all cache entries as stale (internal - does not broadcast)
364
+ * Mark every history entry stale WITHOUT touching the prefetch caches or the
365
+ * rango state. Used by the jar-divergence observer: an external rotation has
366
+ * already changed the state value (so prefetch/HTTP entries strand under the
367
+ * retired key), and this tab must NOT re-rotate — only the history cache,
368
+ * which is not state-keyed, needs marking.
329
369
  */
330
- function markCacheAsStaleInternal(): void {
370
+ function markHistoryStale(): void {
331
371
  for (let i = 0; i < historyCache.length; i++) {
332
372
  historyCache[i][2] = true;
333
373
  }
374
+ }
375
+
376
+ /**
377
+ * Mark all cache entries as stale (internal - does not broadcast). Also
378
+ * clears the prefetch caches, which rotates the rango state.
379
+ */
380
+ function markCacheAsStaleInternal(): void {
381
+ markHistoryStale();
334
382
  clearPrefetchCache();
335
383
  }
336
384
 
@@ -543,6 +591,29 @@ export function createNavigationStore(
543
591
  currentHistoryKey = key;
544
592
  },
545
593
 
594
+ /**
595
+ * Current nav-instance token: the instance of the most recently committed
596
+ * navigation (the value last written by cacheSegmentsForHistory). A late
597
+ * async handle resolution captures this at the start of its own nav and
598
+ * compares it back here to detect whether a NEWER navigation has since
599
+ * committed (token advanced), guarding against a stale nav writing a fresher
600
+ * nav's live state.
601
+ */
602
+ getNavInstance(): number {
603
+ return navInstance;
604
+ },
605
+
606
+ /**
607
+ * The nav-instance token recorded on a specific cache entry, or undefined if
608
+ * no entry exists for that key. Because the history key is URL-only, this is
609
+ * how a late resolution tells "the entry I seeded is still mine" from "a
610
+ * newer same-URL visit replaced my entry".
611
+ */
612
+ getCacheEntryInstance(historyKey: string): number | undefined {
613
+ const entry = historyCache.find(([key]) => key === historyKey);
614
+ return entry ? entry[5] : undefined;
615
+ },
616
+
546
617
  /**
547
618
  * Store segments for a history entry
548
619
  * Updates existing entry if key exists, otherwise adds new entry
@@ -561,6 +632,11 @@ export function createNavigationStore(
561
632
  ? cloneHandleData(handleData)
562
633
  : undefined;
563
634
 
635
+ // Each commit (create or replace) is a new nav instance. The bump happens
636
+ // here, exactly once per cacheSegmentsForHistory call, so getNavInstance()
637
+ // reflects the visit whose entry this is.
638
+ const instance = ++navInstance;
639
+
564
640
  // Check if entry already exists and update it
565
641
  const existingIndex = historyCache.findIndex(
566
642
  ([key]) => key === historyKey,
@@ -571,10 +647,21 @@ export function createNavigationStore(
571
647
  segments,
572
648
  false,
573
649
  clonedHandleData,
650
+ currentRouterId,
651
+ instance,
652
+ false, // fresh commit: handles complete unless a deferred apply marks it
574
653
  ];
575
654
  } else {
576
655
  // Add new entry at the end (not stale)
577
- historyCache.push([historyKey, segments, false, clonedHandleData]);
656
+ historyCache.push([
657
+ historyKey,
658
+ segments,
659
+ false,
660
+ clonedHandleData,
661
+ currentRouterId,
662
+ instance,
663
+ false,
664
+ ]);
578
665
  // Remove oldest entries if over limit
579
666
  while (historyCache.length > cacheSize) {
580
667
  historyCache.shift();
@@ -586,14 +673,24 @@ export function createNavigationStore(
586
673
  * Get cached segments for a history entry
587
674
  * Returns { segments, stale, handleData } or undefined if not cached
588
675
  */
589
- getCachedSegments(
590
- historyKey: string,
591
- ):
592
- | { segments: ResolvedSegment[]; stale: boolean; handleData?: HandleData }
676
+ getCachedSegments(historyKey: string):
677
+ | {
678
+ segments: ResolvedSegment[];
679
+ stale: boolean;
680
+ handleData?: HandleData;
681
+ routerId?: string;
682
+ handlesPending?: boolean;
683
+ }
593
684
  | undefined {
594
685
  const entry = historyCache.find(([key]) => key === historyKey);
595
686
  if (!entry) return undefined;
596
- return { segments: entry[1], stale: entry[2], handleData: entry[3] };
687
+ return {
688
+ segments: entry[1],
689
+ stale: entry[2],
690
+ handleData: entry[3],
691
+ routerId: entry[4],
692
+ handlesPending: entry[6],
693
+ };
597
694
  },
598
695
 
599
696
  /**
@@ -604,11 +701,23 @@ export function createNavigationStore(
604
701
  },
605
702
 
606
703
  /**
607
- * Update only the handleData for an existing cache entry
608
- * Does nothing if the cache entry doesn't exist
609
- * This is used to fix stale handleData after async handles processing
704
+ * Update only the handleData (and optionally the stale flag) for an existing
705
+ * cache entry. Does nothing if the cache entry doesn't exist.
706
+ *
707
+ * Used to fix stale handleData after async handles processing AND to flip an
708
+ * entry's stale / handlesPending bits for the deferred-Meta
709
+ * invalidate+revalidate path: while a nav's Meta is deferred-pending its
710
+ * entry is marked stale + handlesPending (a popstate return then revalidates
711
+ * with a full re-render instead of serving the carry/seed as fresh), and once
712
+ * the deferred Meta resolves both are cleared. When a flag is omitted the
713
+ * entry's current value is preserved.
610
714
  */
611
- updateCacheHandleData(historyKey: string, handleData: HandleData): void {
715
+ updateCacheHandleData(
716
+ historyKey: string,
717
+ handleData: HandleData,
718
+ stale?: boolean,
719
+ handlesPending?: boolean,
720
+ ): void {
612
721
  const existingIndex = historyCache.findIndex(
613
722
  ([key]) => key === historyKey,
614
723
  );
@@ -619,8 +728,11 @@ export function createNavigationStore(
619
728
  historyCache[existingIndex] = [
620
729
  entry[0],
621
730
  entry[1],
622
- entry[2],
731
+ stale ?? entry[2], // set stale when provided, else preserve current
623
732
  clonedHandleData,
733
+ entry[4], // preserve routerId
734
+ entry[5], // preserve navInstance (entry ownership identity)
735
+ handlesPending ?? entry[6], // set when provided, else preserve current
624
736
  ];
625
737
  }
626
738
  },
@@ -633,6 +745,16 @@ export function createNavigationStore(
633
745
  markCacheAsStaleInternal();
634
746
  },
635
747
 
748
+ /**
749
+ * Mark every history entry stale WITHOUT clearing the prefetch caches or
750
+ * rotating the rango state. The jar-divergence observer calls this after an
751
+ * external rotation has already changed the state value, so re-rotating
752
+ * here would ping-pong with the tab that rotated.
753
+ */
754
+ markHistoryCacheStale(): void {
755
+ markHistoryStale();
756
+ },
757
+
636
758
  /**
637
759
  * Clear the history cache and broadcast to other tabs
638
760
  * Use this for hard invalidation when data is definitely stale
@@ -649,14 +771,6 @@ export function createNavigationStore(
649
771
  markStaleAndBroadcast();
650
772
  },
651
773
 
652
- /**
653
- * Broadcast cache invalidation to other tabs without clearing local cache
654
- * Used after consolidation fetch where local cache has fresh data
655
- */
656
- broadcastCacheInvalidation(): void {
657
- broadcastInvalidation();
658
- },
659
-
660
774
  /**
661
775
  * Set the callback to invoke when cross-tab refresh is triggered
662
776
  * Called by navigation bridge during initialization
@@ -687,6 +801,14 @@ export function createNavigationStore(
687
801
  interceptSourceUrl = url;
688
802
  },
689
803
 
804
+ getRouterId(): string | undefined {
805
+ return currentRouterId;
806
+ },
807
+
808
+ setRouterId(id: string): void {
809
+ currentRouterId = id;
810
+ },
811
+
690
812
  // ========================================================================
691
813
  // UI Update Notifications
692
814
  // ========================================================================
@@ -765,42 +887,3 @@ export function createNavigationStore(
765
887
  },
766
888
  };
767
889
  }
768
-
769
- // Singleton store instance
770
- let storeInstance: NavigationStore | null = null;
771
-
772
- /**
773
- * Initialize the global navigation store
774
- *
775
- * Should be called once during app initialization.
776
- * Subsequent calls return the existing instance.
777
- */
778
- export function initNavigationStore(
779
- config?: NavigationStoreConfig,
780
- ): NavigationStore {
781
- if (!storeInstance) {
782
- storeInstance = createNavigationStore(config);
783
- }
784
- return storeInstance;
785
- }
786
-
787
- /**
788
- * Get the global navigation store
789
- *
790
- * Throws if store hasn't been initialized.
791
- */
792
- export function getNavigationStore(): NavigationStore {
793
- if (!storeInstance) {
794
- throw new Error(
795
- "Navigation store not initialized. Call initNavigationStore first.",
796
- );
797
- }
798
- return storeInstance;
799
- }
800
-
801
- /**
802
- * Reset the store instance (for testing)
803
- */
804
- export function resetNavigationStore(): void {
805
- storeInstance = null;
806
- }
@@ -11,9 +11,8 @@ import {
11
11
  } from "./scroll-restoration.js";
12
12
  import type { EventController, NavigationHandle } from "./event-controller.js";
13
13
  import { debugLog } from "./logging.js";
14
- import { buildHistoryState } from "./history-state.js";
14
+ import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
15
15
 
16
- // Re-export for consumers that import from navigation-transaction
17
16
  export { resolveNavigationState } from "./history-state.js";
18
17
 
19
18
  /** Check if a history state object contains location state keys. */
@@ -25,7 +24,6 @@ function hasLocationState(state: unknown): boolean {
25
24
  );
26
25
  }
27
26
 
28
- // Polyfill Symbol.dispose for Safari and older browsers
29
27
  if (typeof Symbol.dispose === "undefined") {
30
28
  (Symbol as any).dispose = Symbol("Symbol.dispose");
31
29
  }
@@ -114,7 +112,6 @@ export function createNavigationTransaction(
114
112
  let committed = false;
115
113
  const currentUrl = window.location.href;
116
114
 
117
- // Start navigation in event controller (this sets loading state)
118
115
  const handle = eventController.startNavigation(url, options);
119
116
 
120
117
  /**
@@ -138,76 +135,50 @@ export function createNavigationTransaction(
138
135
 
139
136
  const parsedUrl = new URL(url, window.location.origin);
140
137
 
141
- // Generate history key from URL (with intercept suffix for separate caching)
142
138
  const historyKey = generateHistoryKey(url, { intercept });
143
139
 
144
- // For cache-only commits (stale revalidation), only update cache and return
145
- // Don't touch store state or history - user may have navigated elsewhere
146
140
  if (cacheOnly) {
147
141
  const currentHandleData = eventController.getHandleState().data;
148
142
  store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
149
- // Complete the navigation handle so currentNavigation is cleared.
150
- // Without this, the entry lingers and weakens state-machine invariants.
151
143
  handle.complete(parsedUrl);
152
144
  debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
153
145
  return { scroll: false };
154
146
  }
155
147
 
156
- // Save current scroll position before navigating
157
148
  handleNavigationStart();
158
149
 
159
- // Update segment state atomically
160
150
  store.setSegmentIds(segmentIds);
161
151
  store.setCurrentUrl(url);
162
152
  store.setPath(parsedUrl.pathname);
163
153
 
164
154
  store.setHistoryKey(historyKey);
165
155
 
166
- // Cache segments with current handleData for this history entry
167
156
  const currentHandleData = eventController.getHandleState().data;
168
157
  store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
169
158
 
170
- // For server actions, skip URL/history updates but still complete navigation
171
159
  if (storeOnly) {
172
160
  debugLog("[Browser] Store updated (action)");
173
- // Complete navigation to clear loading state
174
161
  handle.complete(parsedUrl);
175
162
  return { scroll: false };
176
163
  }
177
164
 
178
- // Build history state - include user state, intercept info, and server-set state
179
165
  const historyState = buildHistoryState(
180
166
  opts.state,
181
167
  { intercept, sourceUrl: interceptSourceUrl },
182
168
  serverState,
183
169
  );
184
170
 
185
- // Snapshot old state before pushState/replaceState overwrites it.
186
- // Used to detect when location state is being cleared.
187
171
  const oldState = window.history.state;
188
172
 
189
- // Update browser URL
190
- if (replace) {
191
- window.history.replaceState(historyState, "", url);
192
- } else {
193
- window.history.pushState(historyState, "", url);
194
- }
195
- // Ensure new history entry has a scroll restoration key
173
+ pushHistoryWithIdx(historyState, url, replace ?? false);
196
174
  ensureHistoryKey();
197
175
 
198
- // Notify location state hooks when either old or new state carries
199
- // location state. This covers both "set new state" and "clear old state"
200
- // for same-page navigations where components don't remount.
201
176
  if (hasLocationState(oldState) || hasLocationState(historyState)) {
202
177
  window.dispatchEvent(new Event("__rsc_locationstate"));
203
178
  }
204
179
 
205
- // Complete the navigation in event controller (sets idle state, updates location)
206
180
  handle.complete(parsedUrl);
207
181
 
208
- // NOTE: Scroll is NOT handled here. The caller (partial-update.ts) handles
209
- // scroll AFTER onUpdate() so React has the new content before we scroll.
210
-
211
182
  debugLog(
212
183
  "[Browser] Navigation committed, historyKey:",
213
184
  historyKey,
@@ -221,10 +192,6 @@ export function createNavigationTransaction(
221
192
  handle,
222
193
  commit,
223
194
 
224
- /**
225
- * Create a bound transaction with pre-configured URL options
226
- * segmentIds and segments provided at commit time (after they're resolved)
227
- */
228
195
  with(
229
196
  opts: Omit<CommitOptions, "segmentIds" | "segments">,
230
197
  ): BoundTransaction {
@@ -240,30 +207,16 @@ export function createNavigationTransaction(
240
207
  segments: ResolvedSegment[],
241
208
  overrides?: BoundCommitOverrides,
242
209
  ) => {
243
- // Allow overrides to disable scroll (e.g., for intercepts)
244
- const finalScroll =
245
- overrides?.scroll !== undefined ? overrides.scroll : opts.scroll;
246
- // Allow overrides to force replace (e.g., for intercepts)
247
- const finalReplace =
248
- overrides?.replace !== undefined ? overrides.replace : opts.replace;
249
- // Intercept info: overrides take precedence, fallback to opts
250
- const intercept =
251
- overrides?.intercept !== undefined
252
- ? overrides.intercept
253
- : opts.intercept;
210
+ const finalScroll = overrides?.scroll ?? opts.scroll;
211
+ const finalReplace = overrides?.replace ?? opts.replace;
212
+ const intercept = overrides?.intercept ?? opts.intercept;
254
213
  const interceptSourceUrl =
255
- overrides?.interceptSourceUrl !== undefined
256
- ? overrides.interceptSourceUrl
257
- : opts.interceptSourceUrl;
258
- // Cache-only mode: overrides take precedence, fallback to opts
259
- const cacheOnly =
260
- overrides?.cacheOnly !== undefined
261
- ? overrides.cacheOnly
262
- : opts.cacheOnly;
263
- // User state: overrides take precedence, fallback to opts
214
+ overrides?.interceptSourceUrl ?? opts.interceptSourceUrl;
215
+ const cacheOnly = overrides?.cacheOnly ?? opts.cacheOnly;
216
+ // state is `unknown` (null is meaningful) so `??` would wrongly drop a
217
+ // null override; serverState always comes from overrides, never opts.
264
218
  const state =
265
219
  overrides?.state !== undefined ? overrides.state : opts.state;
266
- // Server-set location state: only from overrides (set by partial-update)
267
220
  const serverState = overrides?.serverState;
268
221
  return commit({
269
222
  ...opts,
@@ -282,13 +235,10 @@ export function createNavigationTransaction(
282
235
  },
283
236
 
284
237
  [Symbol.dispose]() {
285
- // Superseded: another navigation took over.
286
238
  if (handle.signal.aborted) {
287
239
  return;
288
240
  }
289
241
 
290
- // Failed (not committed): keep the target URL -- the error UI owns it.
291
- // Just reset the event controller to idle.
292
242
  if (!committed) {
293
243
  handle[Symbol.dispose]();
294
244
  }
@@ -1,5 +1,5 @@
1
1
  import { NetworkError, isNetworkError } from "../errors.js";
2
- import { NetworkErrorThrower } from "../network-error-thrower.js";
2
+ import { RenderErrorThrower } from "../render-error-thrower.js";
3
3
  import type { UpdateSubscriber } from "./types.js";
4
4
  import { createElement, startTransition } from "react";
5
5
 
@@ -24,18 +24,18 @@ export function toNetworkError(
24
24
  }
25
25
 
26
26
  /**
27
- * Emit a NetworkError to the UI via the onUpdate subscriber.
28
- * Wraps in startTransition and renders a NetworkErrorThrower component
29
- * that throws during render to trigger the nearest error boundary.
27
+ * Render an error into the segment tree via the onUpdate subscriber so the
28
+ * nearest error boundary catches it. Wrapped in startTransition; RenderErrorThrower
29
+ * throws during render (async rejections do not reach boundaries on their own).
30
30
  */
31
- export function emitNetworkError(
31
+ function emitErrorToBoundary(
32
32
  onUpdate: UpdateSubscriber,
33
- error: NetworkError,
33
+ error: unknown,
34
34
  pathname: string,
35
35
  ): void {
36
36
  startTransition(() => {
37
37
  onUpdate({
38
- root: createElement(NetworkErrorThrower, { error }),
38
+ root: createElement(RenderErrorThrower, { error }),
39
39
  metadata: {
40
40
  pathname,
41
41
  segments: [],
@@ -45,6 +45,33 @@ export function emitNetworkError(
45
45
  });
46
46
  }
47
47
 
48
+ /**
49
+ * Emit a NetworkError to the nearest error boundary (offline, failed fetch).
50
+ */
51
+ export function emitNetworkError(
52
+ onUpdate: UpdateSubscriber,
53
+ error: NetworkError,
54
+ pathname: string,
55
+ ): void {
56
+ emitErrorToBoundary(onUpdate, error, pathname);
57
+ }
58
+
59
+ /**
60
+ * Emit a navigation processing error to the nearest error boundary. Used when a
61
+ * navigation response cannot be processed (an undecodable Flight body, or any
62
+ * unanticipated failure while building the response) -- for both fresh and
63
+ * prefetched responses, since both funnel through the navigation catch. Without
64
+ * this, such a failure becomes an uncaught rejection that silently aborts the
65
+ * navigation instead of surfacing the route's error boundary.
66
+ */
67
+ export function emitNavigationError(
68
+ onUpdate: UpdateSubscriber,
69
+ error: unknown,
70
+ pathname: string,
71
+ ): void {
72
+ emitErrorToBoundary(onUpdate, error, pathname);
73
+ }
74
+
48
75
  /**
49
76
  * Check if an error is safe to suppress in background operations.
50
77
  *