@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -20,24 +20,23 @@ import {
20
20
  } from "./intercept-utils.js";
21
21
  import type { BoundTransaction } from "./navigation-transaction.js";
22
22
  import { ServerRedirect } from "../errors.js";
23
- import { debugLog } from "./logging.js";
24
- import { validateRedirectOrigin } from "./validate-redirect-origin.js";
23
+ import {
24
+ debugLog,
25
+ isBrowserDebugEnabled,
26
+ IS_BROWSER_DEBUG,
27
+ } from "./logging.js";
28
+ import {
29
+ validateRedirectOrigin,
30
+ validateExternalRedirect,
31
+ } from "./validate-redirect-origin.js";
25
32
  import type { NavigationUpdate } from "./types.js";
26
33
 
27
- /** Build a scroll payload from the commit's scroll option */
28
34
  function toScrollPayload(
29
35
  scroll: boolean | undefined,
30
36
  ): NonNullable<NavigationUpdate["scroll"]> {
31
37
  return { enabled: scroll !== false ? scroll : false };
32
38
  }
33
39
 
34
- /**
35
- * Whether to wrap an update in startViewTransition.
36
- *
37
- * Intercept-driven updates only mutate the parallel slot — the main outlet
38
- * shows the same content — so transitions on the underlying main segments
39
- * shouldn't fire (otherwise their elements get hoisted above the modal).
40
- */
41
40
  function shouldStartViewTransition(segments: ResolvedSegment[]): boolean {
42
41
  let hasIntercept = false;
43
42
  let hasTransition = false;
@@ -61,13 +60,6 @@ export interface PartialUpdateConfig {
61
60
  ) => Promise<ReactNode> | ReactNode;
62
61
  /** RSC version getter — returns the current version (may change after HMR) */
63
62
  getVersion?: () => string | undefined;
64
- /**
65
- * Replace the active app-shell when a cross-app navigation is detected.
66
- * Called before the full-update tree replacement renders, so the new
67
- * payload's rootLayout, basename, and version are picked up. Theme,
68
- * warmup, and prefetch TTL are not part of the shell — see AppShell.
69
- */
70
- applyAppShell?: (next: import("./app-shell.js").AppShell) => void;
71
63
  }
72
64
 
73
65
  /**
@@ -119,15 +111,6 @@ export type PartialUpdater = (
119
111
  mode?: UpdateMode,
120
112
  ) => Promise<void>;
121
113
 
122
- /**
123
- * Create a partial updater for fetching and applying RSC partial updates
124
- *
125
- * This function is shared between navigation-bridge and server-action-bridge
126
- * to handle partial RSC updates with HMR resilience.
127
- *
128
- * @param config - Partial update configuration
129
- * @returns fetchPartialUpdate function
130
- */
131
114
  export function createPartialUpdater(
132
115
  config: PartialUpdateConfig,
133
116
  ): PartialUpdater {
@@ -137,24 +120,14 @@ export function createPartialUpdater(
137
120
  onUpdate,
138
121
  renderSegments,
139
122
  getVersion = () => undefined,
140
- applyAppShell,
141
123
  } = config;
142
124
 
143
- /**
144
- * Get current page's cached segments as an array
145
- */
146
125
  function getCurrentCachedSegments(): ResolvedSegment[] {
147
126
  const currentKey = store.getHistoryKey();
148
127
  const cached = store.getCachedSegments(currentKey);
149
128
  return cached?.segments || [];
150
129
  }
151
130
 
152
- /**
153
- * Fetch partial update and trigger UI update
154
- *
155
- * @param tx - Transaction for committing segment state (required)
156
- * @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
157
- */
158
131
  async function fetchPartialUpdate(
159
132
  targetUrl: string,
160
133
  segmentIds: string[] | undefined,
@@ -166,117 +139,114 @@ export function createPartialUpdater(
166
139
  const segmentState = store.getSegmentState();
167
140
  const url = targetUrl || window.location.href;
168
141
 
169
- // Capture history key at start for stale revalidation consistency check
170
142
  const historyKeyAtStart = store.getHistoryKey();
171
143
 
172
144
  const interceptSourceUrl = mode.interceptSourceUrl;
173
145
 
174
- // When leaving intercept, filter out intercept-specific segments
175
146
  let segments: string[];
176
147
  if (mode.type === "leave-intercept") {
177
148
  const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
178
149
  const currentCached = getCurrentCachedSegments();
179
150
  const interceptIds = new Set(
180
- currentCached
181
- .filter((s) => s.namespace?.startsWith("intercept:"))
182
- .map((s) => s.id),
151
+ currentCached.filter(isInterceptSegment).map((s) => s.id),
183
152
  );
184
153
  segments = currentSegments.filter((id) => !interceptIds.has(id));
185
- debugLog(
186
- `[Browser] Leaving intercept - filtered segments: ${segments.join(", ")}`,
187
- );
154
+ if (IS_BROWSER_DEBUG) {
155
+ debugLog(
156
+ `[Browser] Leaving intercept - filtered segments: ${segments.join(", ")}`,
157
+ );
158
+ }
188
159
  } else {
189
160
  segments = segmentIds ?? segmentState.currentSegmentIds;
190
161
  }
191
162
 
192
- // For intercept revalidation, use the intercept source URL as previousUrl.
193
- // For leave-intercept, tx.currentUrl captures window.location.href at tx
194
- // creation, which on popstate is already the destination URL and would
195
- // tell the server "from == to". segmentState.currentUrl still points at
196
- // the URL the cached segments render (the intercept URL), which is the
197
- // correct "from" for the server's diff computation.
198
163
  const previousUrl =
199
164
  mode.type === "leave-intercept"
200
165
  ? segmentState.currentUrl || tx.currentUrl
201
166
  : interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
202
167
 
203
- debugLog(`\n[Browser] >>> NAVIGATION`);
204
- debugLog(`[Browser] From: ${previousUrl}`);
205
- debugLog(`[Browser] To: ${url}`);
206
- debugLog(`[Browser] Segments to send: ${segments.join(", ")}`);
207
- if (interceptSourceUrl) {
208
- debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
168
+ if (IS_BROWSER_DEBUG) {
169
+ debugLog(`\n[Browser] >>> NAVIGATION`);
170
+ debugLog(`[Browser] From: ${previousUrl}`);
171
+ debugLog(`[Browser] To: ${url}`);
172
+ debugLog(`[Browser] Segments to send: ${segments.join(", ")}`);
173
+ if (interceptSourceUrl) {
174
+ debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
175
+ }
209
176
  }
210
177
 
211
- // Get cached segments for merging with server diff.
212
- // When navigating with targetCacheSegments, use those for consistency.
213
- // Otherwise fall back to current page's segments (for same-route revalidation).
214
178
  const targetCache =
215
179
  mode.type === "navigate" && mode.targetCacheSegments?.length
216
180
  ? mode.targetCacheSegments
217
181
  : undefined;
218
182
  const cachedSegs = targetCache ?? getCurrentCachedSegments();
219
183
  const cachedSegsSource = targetCache ? "history-cache" : "current-page";
220
- debugLog(
221
- `[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
222
- );
184
+ if (IS_BROWSER_DEBUG) {
185
+ debugLog(
186
+ `[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
187
+ );
188
+ }
223
189
 
224
- // Fetch partial payload (no abort signal - RSC doesn't support it well)
225
190
  let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
226
191
  fetchResult = await client.fetchPartial({
227
192
  targetUrl: url,
228
193
  segmentIds: segments,
229
194
  previousUrl,
230
- // Mark stale when explicitly requested OR when no segments are sent
231
- // (action redirect sends empty segments for a fresh render).
232
195
  staleRevalidation:
233
196
  mode.type === "stale-revalidation" || segments.length === 0,
234
197
  version: getVersion(),
235
198
  routerId: store.getRouterId?.(),
236
199
  });
237
- // Mark navigation as streaming (response received, now parsing RSC).
238
- // Called after fetchPartial so pendingUrl stays set during the network wait,
239
- // allowing useLinkStatus to show per-link pending indicators.
240
200
  const streamingToken = tx.startStreaming();
241
- const { payload, streamComplete: rawStreamComplete } = fetchResult;
201
+ const {
202
+ payload,
203
+ streamComplete: rawStreamComplete,
204
+ fullyPrefetched,
205
+ } = fetchResult;
242
206
  debugLog("payload.metadata", payload.metadata);
243
207
 
244
- const streamComplete = rawStreamComplete.then(() => {
245
- streamingToken.end();
246
- });
247
-
248
- // Detect app switch: if routerId changed, the navigation crossed into
249
- // a different router (e.g., via host router path mount). Downgrade
250
- // partial to full so the entire tree is replaced without reconciliation
251
- // against stale segments from the previous app, and replace the app
252
- // shell (rootLayout, basename, version) so the target app's document
253
- // and router config take effect instead of remaining captured from the
254
- // initial load. Theme, warmup, and prefetch TTL are intentionally
255
- // document-lifetime (see AppShell doc); a new document navigation
256
- // applies them.
257
- if (payload.metadata?.routerId) {
258
- const prevRouterId = store.getRouterId?.();
259
- if (prevRouterId && prevRouterId !== payload.metadata.routerId) {
260
- debugLog(
261
- `[Browser] App switch detected (${prevRouterId} → ${payload.metadata.routerId}), forcing full update`,
262
- );
263
- payload.metadata.isPartial = false;
264
- applyAppShell?.({
265
- routerId: payload.metadata.routerId,
266
- rootLayout: payload.metadata.rootLayout,
267
- basename: payload.metadata.basename,
268
- version: payload.metadata.version,
269
- });
270
- }
271
- store.setRouterId?.(payload.metadata.routerId);
208
+ // Side effect only: end the streaming token once the stream settles.
209
+ // The wrapped promise was never read as a value; only the .end() matters.
210
+ // The .catch keeps an unhandled rejection from leaking if the stream errors.
211
+ rawStreamComplete.then(() => streamingToken.end()).catch(() => {});
212
+
213
+ const currentRouterId = store.getRouterId?.();
214
+ if (
215
+ payload.metadata?.routerId &&
216
+ currentRouterId &&
217
+ payload.metadata.routerId !== currentRouterId
218
+ ) {
219
+ console.error(
220
+ `[rango] Partial response router id "${payload.metadata.routerId}" does not ` +
221
+ `match this client ("${currentRouterId}"); discarding it and reloading to re-sync.`,
222
+ );
223
+ window.location.href = url;
224
+ return;
272
225
  }
273
226
 
274
- // Handle server-side redirect with state
275
227
  if (payload.metadata?.redirect) {
276
228
  if (signal?.aborted) {
277
229
  debugLog("[Browser] Ignoring stale redirect (aborted)");
278
230
  return;
279
231
  }
232
+ // Explicit off-host redirect (redirect(url, { external: true })):
233
+ // hard-navigate, but still scheme-validate (http/https only). external
234
+ // waives the same-origin check the app opted out of, NOT scheme safety, so
235
+ // a forged payload carrying a javascript:/data: URL cannot script via
236
+ // location.assign.
237
+ if (payload.metadata.redirect.external) {
238
+ const externalUrl = validateExternalRedirect(
239
+ payload.metadata.redirect.url,
240
+ window.location.origin,
241
+ );
242
+ if (!externalUrl) {
243
+ debugLog("[Browser] Ignoring blocked external redirect payload");
244
+ return;
245
+ }
246
+ debugLog("[Browser] External redirect (hard navigation)");
247
+ window.location.assign(externalUrl);
248
+ return;
249
+ }
280
250
  const redirectUrl = validateRedirectOrigin(
281
251
  payload.metadata.redirect.url,
282
252
  window.location.origin,
@@ -298,10 +268,11 @@ export function createPartialUpdater(
298
268
  return;
299
269
  }
300
270
 
301
- debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
302
- debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
271
+ if (IS_BROWSER_DEBUG) {
272
+ debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
273
+ debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
274
+ }
303
275
 
304
- // If diff is empty, nothing changed on server side.
305
276
  if (!diff || diff.length === 0) {
306
277
  const matchedIds = matched || [];
307
278
  const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
@@ -309,7 +280,6 @@ export function createPartialUpdater(
309
280
  .map((id: string) => cacheMap.get(id))
310
281
  .filter(Boolean) as ResolvedSegment[];
311
282
 
312
- // When navigating with cached segments to a different route, render them.
313
283
  if (mode.type === "navigate" && targetCache) {
314
284
  debugLog(
315
285
  "[Browser] No diff but navigating with cached segments - rendering target route",
@@ -324,10 +294,6 @@ export function createPartialUpdater(
324
294
  existingSegments,
325
295
  );
326
296
 
327
- // tx.commit() cached the source page's handleData because
328
- // eventController hasn't been updated yet. Overwrite with the
329
- // correct cached handleData to prevent cache corruption on
330
- // subsequent navigations to this same URL.
331
297
  if (mode.targetCacheHandleData) {
332
298
  store.updateCacheHandleData(
333
299
  store.getHistoryKey(),
@@ -335,10 +301,6 @@ export function createPartialUpdater(
335
301
  );
336
302
  }
337
303
 
338
- // Include cachedHandleData in metadata so NavigationProvider can restore
339
- // breadcrumbs and other handle data from cache.
340
- // Remove `handles` from metadata to prevent NavigationProvider from
341
- // processing an empty handles stream, which would clear the cached breadcrumbs.
342
304
  const { handles: _unusedHandles, ...metadataWithoutHandles } =
343
305
  payload.metadata!;
344
306
  const cachedUpdate = {
@@ -365,7 +327,6 @@ export function createPartialUpdater(
365
327
  return;
366
328
  }
367
329
 
368
- // When leaving intercept, force re-render even with empty diff
369
330
  if (mode.type === "leave-intercept") {
370
331
  debugLog(
371
332
  "[Browser] Leaving intercept - forcing re-render to remove modal",
@@ -390,7 +351,6 @@ export function createPartialUpdater(
390
351
  return;
391
352
  }
392
353
 
393
- // Same route revalidation with no changes - skip UI update
394
354
  debugLog(
395
355
  "[Browser] No changes - all revalidations returned false, keeping existing UI",
396
356
  );
@@ -399,7 +359,6 @@ export function createPartialUpdater(
399
359
  return;
400
360
  }
401
361
 
402
- // Reconcile server segments with cached segments (single source of truth)
403
362
  const matchedIds = matched || [];
404
363
  const actor: ReconcileActor =
405
364
  mode.type === "stale-revalidation" || mode.type === "action"
@@ -415,7 +374,6 @@ export function createPartialUpdater(
415
374
  insertMissingDiff: true,
416
375
  });
417
376
 
418
- // HMR RESILIENCE: Check if we're missing any matched segments
419
377
  const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
420
378
  const missingIds = matchedIds.filter(
421
379
  (id: string) => !reconciledIdSet.has(id),
@@ -437,13 +395,19 @@ export function createPartialUpdater(
437
395
  return;
438
396
  }
439
397
  if (mode.type === "action") {
398
+ // An action refetch that lands on missing segments (navigated away /
399
+ // consolidation / HMR) drops rather than refetch-all: the action flow
400
+ // is storeOnly / skipLoadingState, so a full refetch here would fight
401
+ // it. Keep the stale-but-consistent tree; log so the drop is visible.
402
+ debugLog(
403
+ `[Browser] Action refetch: ${missingCount} segments missing; dropping (stale-but-consistent tree kept).`,
404
+ );
440
405
  return;
441
406
  }
442
407
  console.warn(
443
408
  `[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
444
409
  );
445
410
 
446
- // Refetch with empty segments = server sends everything
447
411
  return fetchPartialUpdate(url, [], true, signal, tx, mode);
448
412
  }
449
413
 
@@ -452,44 +416,59 @@ export function createPartialUpdater(
452
416
  return;
453
417
  }
454
418
 
455
- // Rebuild tree on client (await for loader data resolution)
456
419
  const renderOptions = {
457
420
  isAction: mode.type === "action",
458
- forceAwait: mode.type === "stale-revalidation",
421
+ // forceAwait unwraps the ROUTER loader promises during render so they
422
+ // land without a loading()/fallback frame. A fully-prefetched nav has
423
+ // its router data already resolved (the prefetch stream drained), so
424
+ // awaiting it here is free and lets us commit NORMALLY (not in a
425
+ // transition) below — a normal commit still shows fallbacks for any
426
+ // CLIENT component that suspends on mount, which a transition would
427
+ // wrongly suppress by holding the old UI until that suspense settles.
428
+ forceAwait: mode.type === "stale-revalidation" || fullyPrefetched,
459
429
  interceptSegments:
460
430
  reconciled.interceptSegments.length > 0
461
431
  ? reconciled.interceptSegments
462
432
  : undefined,
463
433
  };
464
- const newTree = await (signal
465
- ? Promise.race([
434
+ let newTree: Awaited<ReturnType<typeof renderSegments>>;
435
+ if (signal) {
436
+ // Race render against abort. Store the abort handler and register it
437
+ // { once:true } so a non-aborted render (which wins the race) can
438
+ // remove it in finally — otherwise the listener stays attached and the
439
+ // rejecting promise never settles. Mirrors teeWithCompletion in
440
+ // browser/response-adapter.ts.
441
+ let onAbort: (() => void) | undefined;
442
+ const abortPromise = new Promise<never>((_, reject) => {
443
+ if (signal.aborted) {
444
+ reject(new DOMException("Navigation aborted", "AbortError"));
445
+ return;
446
+ }
447
+ onAbort = () =>
448
+ reject(new DOMException("Navigation aborted", "AbortError"));
449
+ signal.addEventListener("abort", onAbort, { once: true });
450
+ });
451
+ try {
452
+ newTree = await Promise.race([
466
453
  renderSegments(reconciled.mainSegments, renderOptions),
467
- new Promise<never>((_, reject) => {
468
- if (signal.aborted) {
469
- reject(new DOMException("Navigation aborted", "AbortError"));
470
- }
471
- signal.addEventListener("abort", () => {
472
- reject(new DOMException("Navigation aborted", "AbortError"));
473
- });
474
- }),
475
- ])
476
- : renderSegments(reconciled.mainSegments, renderOptions));
477
-
478
- // Final abort check before committing - another navigation may have started
454
+ abortPromise,
455
+ ]);
456
+ } finally {
457
+ if (onAbort) signal.removeEventListener("abort", onAbort);
458
+ }
459
+ } else {
460
+ newTree = await renderSegments(reconciled.mainSegments, renderOptions);
461
+ }
462
+
479
463
  if (signal?.aborted) {
480
464
  debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
481
465
  return;
482
466
  }
483
467
 
484
- // Check if this is an intercept response (any slot is active)
485
468
  const isInterceptResponse = hasActiveInterceptSlots(
486
469
  payload.metadata?.slots,
487
470
  );
488
471
 
489
- // Track intercept context (only on navigation, not actions or stale revalidation)
490
- // Use the authoritative source from mode/history state when restoring an
491
- // intercept via popstate cache miss; fall back to the current URL for fresh
492
- // intercept navigations.
493
472
  const effectiveInterceptSource =
494
473
  interceptSourceUrl || segmentState.currentUrl;
495
474
  if (mode.type !== "action" && mode.type !== "stale-revalidation") {
@@ -500,9 +479,6 @@ export function createPartialUpdater(
500
479
  }
501
480
  }
502
481
 
503
- // Commit navigation - use server's matched as the authoritative segment ID list.
504
- // reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
505
- // but the server's matched always includes all expected segment IDs.
506
482
  const allSegmentIds = matchedIds;
507
483
  const serverLocationState = payload.metadata?.locationState;
508
484
  const overrides: CommitOverrides | undefined = isInterceptResponse
@@ -521,7 +497,6 @@ export function createPartialUpdater(
521
497
  overrides,
522
498
  );
523
499
 
524
- // For stale revalidation: verify history key hasn't changed before updating UI
525
500
  if (mode.type === "stale-revalidation") {
526
501
  const historyKeyNow = store.getHistoryKey();
527
502
  if (historyKeyNow !== historyKeyAtStart) {
@@ -534,9 +509,22 @@ export function createPartialUpdater(
534
509
 
535
510
  debugLog("[partial-update] updating document");
536
511
 
537
- // Emit update to trigger React render.
538
- // Scroll info is included so NavigationProvider applies it after React commits.
539
512
  const hasTransition = shouldStartViewTransition(reconciled.segments);
513
+ // [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Reports which reconciled
514
+ // segment still carries a transition after the server-side when-gate, and
515
+ // whether the commit will be held in a startTransition. If `withTransition`
516
+ // lists an ancestor (layout/root) id rather than the gated leaf, an ungated
517
+ // ancestor transition is holding the subtree (missing loading() fallback).
518
+ if (isBrowserDebugEnabled()) {
519
+ debugLog("[VT-DIAG] commit", {
520
+ mode: mode.type,
521
+ hasTransition,
522
+ withTransition: reconciled.segments
523
+ .filter((s) => s.transition)
524
+ .map((s) => s.id),
525
+ all: reconciled.segments.map((s) => s.id),
526
+ });
527
+ }
540
528
  const scrollPayload = toScrollPayload(navScroll);
541
529
 
542
530
  if (mode.type === "action" || mode.type === "stale-revalidation") {
@@ -562,6 +550,20 @@ export function createPartialUpdater(
562
550
  });
563
551
  });
564
552
  } else {
553
+ // Normal commit (cold/partial nav AND fully-prefetched nav). For a
554
+ // fully-prefetched nav, renderOptions.forceAwait (above) unwrapped the
555
+ // already-resolved ROUTER loader data AND route content during render, so
556
+ // the new tree carries it inline with no loading()/fallback frame — yet we
557
+ // still commit NORMALLY here rather than in a transition. A transition
558
+ // holds the OLD UI until ALL suspense in the new tree settles, including a
559
+ // CLIENT component that starts its own data request only when mounted
560
+ // (post-commit) under a persistent boundary; that would retain the
561
+ // previous page indefinitely with no feedback. A normal commit lets such
562
+ // client-initiated suspense reveal a fallback (correct) while the router
563
+ // data — genuinely ready — never flashes. Cold/partial navs
564
+ // (fullyPrefetched=false) do not forceAwait, so they stream their
565
+ // fallbacks. Explicit transition() routes keep the broader content-hold
566
+ // via the hasTransition branch above (the documented opt-in).
565
567
  onUpdate({
566
568
  root: newTree,
567
569
  metadata: payload.metadata!,
@@ -572,7 +574,6 @@ export function createPartialUpdater(
572
574
  debugLog("[Browser] Navigation complete");
573
575
  return;
574
576
  } else {
575
- // Full update (fallback)
576
577
  console.warn(`[Browser] Full update (fallback)`);
577
578
 
578
579
  const segments = payload.metadata?.segments || [];
@@ -603,6 +604,18 @@ export function createPartialUpdater(
603
604
 
604
605
  if (mode.type === "stale-revalidation") {
605
606
  await rawStreamComplete;
607
+ // Mirror the partial branch's history-key staleness guard (above): the
608
+ // await above is a real async suspension, so the user may have navigated
609
+ // away while this background revalidation was draining. Dropping a late
610
+ // full-update here prevents it from clobbering the freshly committed UI
611
+ // of the page the user moved to.
612
+ const historyKeyNow = store.getHistoryKey();
613
+ if (historyKeyNow !== historyKeyAtStart) {
614
+ debugLog(
615
+ `[Browser] Stale revalidation (full update): history key changed (${historyKeyAtStart} -> ${historyKeyNow}), skipping UI update`,
616
+ );
617
+ return;
618
+ }
606
619
  startTransition(() => {
607
620
  if (fullHasTransition && addTransitionType) {
608
621
  addTransitionType("action");
@@ -614,7 +627,7 @@ export function createPartialUpdater(
614
627
  });
615
628
  });
616
629
  } else if (mode.type === "action") {
617
- startTransition(async () => {
630
+ startTransition(() => {
618
631
  if (fullHasTransition && addTransitionType) {
619
632
  addTransitionType("action");
620
633
  }