@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  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 +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: shell-manifest
3
+ description: Shell manifest pattern — replayed handles as cache metadata that live loaders read, e.g. a prerendered product list with batched live prices
4
+ argument-hint:
5
+ ---
6
+
7
+ # Shell Manifest — cache metadata for live loaders
8
+
9
+ Use this when a cached or prerendered shell has dynamic holes, and the live
10
+ data layer needs to know **what the shell actually contains** — which
11
+ products, which slots, which keys. The frozen render describes itself
12
+ through a handle; loaders (always live) read that description and fetch
13
+ exactly the dynamic data the shell needs, in one batch.
14
+
15
+ Canonical case: a prerendered product list where prices must stay live.
16
+
17
+ ## The problem this solves
18
+
19
+ Any cached-shell-plus-live-holes design has a coordination gap: how does the
20
+ live layer know what the holes need?
21
+
22
+ - **Per-hole fetching** (each `<Price>` component fetching for itself) is the
23
+ N+1 default — N visible products, N queries.
24
+ - **A loader that re-queries the list** ("current top products") drifts from
25
+ a stale shell — right prices attached to wrong products.
26
+
27
+ The shell manifest closes the gap with a consistency guarantee: the loader
28
+ reads the ids the shell _actually rendered_, replayed from the same stored
29
+ artifact, so the holes can never desync from the shell and the query is
30
+ batched.
31
+
32
+ ## The mechanism (three features composed)
33
+
34
+ 1. **Handles record data at render time.** The handler pushes to a handle
35
+ (`ctx.use(Handle)`) while it renders — at build time for `Prerender`, on
36
+ the cache miss for `cache()`.
37
+ 2. **Replay on every hit.** Handle data is stored with the Flight payload
38
+ and replayed into the handle store on cache/prerender hits — handler code
39
+ does not re-run, but its pushes do.
40
+ 3. **Loaders read after the render barrier.** A DSL loader can
41
+ `await ctx.rendered()` (waits for all non-loader segments to settle —
42
+ fresh render or replay alike), then `ctx.use(Handle)` returns the
43
+ **collected** handle data.
44
+
45
+ Loaders are live by default, so the read happens on every request even when
46
+ the shell is a hit.
47
+
48
+ ## Canonical example: prerendered list, live prices
49
+
50
+ ```tsx
51
+ // handles/rendered-products.ts
52
+ import { createHandle } from "@rangojs/router";
53
+
54
+ // TData = string (one push per product id), collected to a flat string[]
55
+ export const RenderedProducts = createHandle<string, string[]>((segments) =>
56
+ segments.flat(),
57
+ );
58
+ ```
59
+
60
+ ```tsx
61
+ // routes/products.tsx — the list is baked at build time; prices are not
62
+ import { Prerender } from "@rangojs/router";
63
+ import { RenderedProducts } from "../handles/rendered-products";
64
+ import { Price } from "../components/price";
65
+
66
+ export const ProductList = Prerender(
67
+ async () => [{ category: "espresso" }, { category: "filter" }],
68
+ async (ctx) => {
69
+ const products = await db.productsByCategory(ctx.params.category);
70
+ const track = ctx.use(RenderedProducts);
71
+ for (const p of products) track(p.id);
72
+ return (
73
+ <ul>
74
+ {products.map((p) => (
75
+ <li key={p.id}>
76
+ {p.name} <Price id={p.id} />
77
+ </li>
78
+ ))}
79
+ </ul>
80
+ );
81
+ },
82
+ );
83
+ ```
84
+
85
+ ```ts
86
+ // loaders/prices.ts — one batched query for exactly the rendered products
87
+ import { createLoader } from "@rangojs/router";
88
+ import { RenderedProducts } from "../handles/rendered-products";
89
+
90
+ export const PriceLoader = createLoader(async (ctx) => {
91
+ "use server";
92
+ await ctx.rendered();
93
+ const ids = ctx.use(RenderedProducts);
94
+ return db.pricesFor(ids); // Map<string, number> keyed by product id
95
+ });
96
+ ```
97
+
98
+ ```tsx
99
+ // urls.tsx — wire the route and register the loader
100
+ path("/products/:category", ProductList, { name: "products" }, () => [
101
+ loader(PriceLoader),
102
+ ]);
103
+ ```
104
+
105
+ ```tsx
106
+ // components/price.tsx — live hole in the frozen shell
107
+ "use client";
108
+ import { useLoader } from "@rangojs/router/client";
109
+ import { PriceLoader } from "../loaders/prices";
110
+
111
+ export function Price({ id }: { id: string }) {
112
+ const { data } = useLoader(PriceLoader);
113
+ return <span>{formatPrice(data[id])}</span>;
114
+ }
115
+ ```
116
+
117
+ Request flow on a hit: stored payload replays (handler never runs) → handle
118
+ data lands in the store → render barrier resolves → `PriceLoader` reads the
119
+ replayed ids → one query → prices stream into `<Price>` components.
120
+
121
+ ## Works with runtime cache() too
122
+
123
+ `Prerender` is build-time caching; the replay mechanism is identical for the
124
+ runtime segment cache. Wrap the route in `cache()` instead and the handler
125
+ pushes on the miss, replays on every hit:
126
+
127
+ ```tsx
128
+ cache({ ttl: 600, tags: ["products"] }, () => [
129
+ path("/products/:category", ProductList, { name: "products" }, () => [
130
+ loader(PriceLoader),
131
+ ]),
132
+ ]);
133
+ ```
134
+
135
+ ## Contract and gotchas
136
+
137
+ - **The manifest is exactly as fresh as the shell.** Replayed handle data is
138
+ frozen with the payload. To change _which_ products render, invalidate the
139
+ shell (`updateTag("products")`, TTL expiry, rebuild) — never treat the
140
+ loader as the refresh path for the list itself. This is the point:
141
+ shell and holes cannot desync because they share one artifact.
142
+ - **No request-scoped data in a manifest handle.** The handle data is baked
143
+ into a shared artifact — the same cross-user rule as any cached content.
144
+ Ids, slugs, slot names, variant keys: yes. Anything derived from
145
+ `cookies()`/`headers()`: no.
146
+ - **`ctx.rendered()` is experimental and DSL-loaders-only.** It throws in
147
+ fetchable/standalone loader calls that run outside a route render.
148
+ - **The reading loader serializes after the shell.** `await ctx.rendered()`
149
+ deliberately gives up loader/render parallelism — on a miss the loader
150
+ waits for segment resolution; on a hit (the common case for a cached
151
+ shell) replay is immediate and the wait is negligible. A
152
+ `debugPerformance` waterfall shows this loader after the render bar; for
153
+ this pattern that is the contract, not a regression.
154
+ - **`ctx.use(Handle)` before `await ctx.rendered()` throws** in a loader,
155
+ with an error saying to await the barrier first.
156
+ - **Deferred handle values are resolved before storage** (resolve-by-default),
157
+ so the manifest read always sees plain values, never promises.
158
+
159
+ ## Testing
160
+
161
+ `runLoader` seeds the barrier and the collected handle value directly —
162
+ matched by handle reference (the same seeding style as loader deps):
163
+
164
+ ```ts
165
+ import { runLoader } from "@rangojs/router/testing";
166
+
167
+ const prices = await runLoader(PriceLoader, {
168
+ rendered: true,
169
+ handles: [[RenderedProducts, ["widget-a", "widget-b"]]],
170
+ env: { DB: fakeDb },
171
+ });
172
+ ```
173
+
174
+ This tests the loader's post-barrier logic. The real
175
+ push → store → replay → barrier wiring is covered at the e2e tier (dev +
176
+ production), like every cache-path behavior.
177
+
178
+ ## Related
179
+
180
+ - `/prerender` — `Prerender`/`Passthrough`, build flow, passthrough fallback
181
+ - `/caching` — segment `cache()`, stores, tags
182
+ - `/loader` — loader context, `ctx.rendered()`, streaming
183
+ - `/hooks` — `useHandle` for reading handle data in client components
184
+ - `/rango` → "Passing data down the tree" — this pattern is the frozen→live
185
+ counterpart of that ladder
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: streams-and-websockets
3
+ description: Long-lived Response handlers — Server-Sent Events (SSE) via path.stream and WebSocket upgrades via path.any on Cloudflare Workers, including middleware interaction and runtime caveats.
4
+ argument-hint: "[sse | websocket | agents]"
5
+ ---
6
+
7
+ # Streams and WebSockets
8
+
9
+ Response routes can return long-lived responses — SSE streams and WebSocket
10
+ upgrades. Both require a `Response` that the router must forward through the
11
+ middleware chain without reconstruction.
12
+
13
+ ## When each fits
14
+
15
+ | Shape | Tag | Status | Body | Runtime |
16
+ | ----------- | --------------- | ------ | ------------------------------- | -------------------------------- |
17
+ | Server-Sent | `path.stream()` | 200 | `ReadableStream` (event-stream) | any runtime (Node, workerd, bun) |
18
+ | WebSocket | `path.any()` | 101 | `null` + `webSocket` property | Cloudflare Workers (workerd) |
19
+
20
+ - **SSE** is a regular 200 response with `content-type: text/event-stream`
21
+ and a `ReadableStream` body. Works everywhere, flows through middleware
22
+ normally.
23
+ - **WebSocket upgrades** produce a status-101 response with a non-standard
24
+ `webSocket` property (Cloudflare). The router detects these and forwards
25
+ them without reconstruction; `Vary` and `Server-Timing` are skipped, and
26
+ stub headers are merged in place on a best-effort basis.
27
+
28
+ ## Server-Sent Events (SSE)
29
+
30
+ Use `path.stream()` (or `path.any()` if you need full control) to return a
31
+ `ReadableStream`. Each chunk is an `event-stream` frame:
32
+
33
+ ```typescript
34
+ import { urls } from "@rangojs/router";
35
+
36
+ export const urlpatterns = urls(({ path }) => [
37
+ path.stream(
38
+ "/events/ticks",
39
+ (ctx) => {
40
+ const encoder = new TextEncoder();
41
+
42
+ const stream = new ReadableStream({
43
+ async start(controller) {
44
+ let count = 0;
45
+ const interval = setInterval(() => {
46
+ controller.enqueue(
47
+ encoder.encode(`event: tick\ndata: ${++count}\n\n`),
48
+ );
49
+ }, 1000);
50
+
51
+ // Honor client disconnect — signal comes from ctx.request.signal
52
+ ctx.request.signal.addEventListener("abort", () => {
53
+ clearInterval(interval);
54
+ controller.close();
55
+ });
56
+ },
57
+ });
58
+
59
+ return new Response(stream, {
60
+ headers: {
61
+ "content-type": "text/event-stream",
62
+ "cache-control": "no-store",
63
+ // Disable proxy buffering on Nginx/Traefik deployments
64
+ "x-accel-buffering": "no",
65
+ },
66
+ });
67
+ },
68
+ { name: "ticks" },
69
+ ),
70
+ ]);
71
+ ```
72
+
73
+ ### Client
74
+
75
+ ```typescript
76
+ "use client";
77
+ const source = new EventSource("/events/ticks");
78
+ source.addEventListener("tick", (e) => console.log("tick", e.data));
79
+ ```
80
+
81
+ ### SSE caveats
82
+
83
+ - **Never wrap SSE routes in `cache()`** — a cached `ReadableStream` is read
84
+ once and would replay an empty body on the next hit. `path.stream` is
85
+ already excluded from response-route caching, but don't layer a custom
86
+ cache() middleware on top.
87
+ - **Middleware is fine.** Global/route middleware rewraps the SSE `Response`
88
+ as `new Response(response.body, { status, headers })` to merge stub headers.
89
+ The `ReadableStream` body is passed by reference, not consumed, so the
90
+ client sees the stream unchanged. (WebSocket upgrades are the exception —
91
+ those bypass rewrap entirely; see below.)
92
+ - **Honor `ctx.request.signal`.** Without wiring abort to your source
93
+ (timer, DB cursor, upstream fetch), the stream leaks when the client
94
+ disconnects.
95
+ - **Disable Nginx/CDN buffering** via `x-accel-buffering: no` and ensure
96
+ no intermediate proxy rebuffers. On Cloudflare Workers this is a non-issue.
97
+
98
+ ## WebSockets (Cloudflare Workers)
99
+
100
+ WebSocket upgrades on workerd produce a response with `status: 101` and a
101
+ non-standard `webSocket` property. The router detects this shape and forwards
102
+ the `Response` without reconstruction — the 101 status and the `webSocket`
103
+ property are preserved. `Vary` and `Server-Timing` writes are skipped, and
104
+ stub-header merging (cookies/custom headers set via `ctx.header()` or
105
+ `cookies().set()`) is best-effort: the router attempts to apply them in
106
+ place, but silently skips any write rejected by a runtime that exposes
107
+ immutable upgrade headers.
108
+
109
+ ### Minimal upgrade handler
110
+
111
+ ```typescript
112
+ import { urls } from "@rangojs/router";
113
+
114
+ export const urlpatterns = urls(({ path }) => [
115
+ path.any(
116
+ "/ws",
117
+ (ctx) => {
118
+ // Manual WebSocketPair on workerd
119
+ const upgrade = ctx.request.headers.get("upgrade");
120
+ if (upgrade !== "websocket") {
121
+ return new Response("expected upgrade: websocket", { status: 426 });
122
+ }
123
+
124
+ const { 0: client, 1: server } = new WebSocketPair();
125
+ server.accept();
126
+ server.addEventListener("message", (e) => {
127
+ server.send(`echo: ${e.data}`);
128
+ });
129
+
130
+ return new Response(null, {
131
+ status: 101,
132
+ webSocket: client,
133
+ } as ResponseInit);
134
+ },
135
+ { name: "ws" },
136
+ ),
137
+ ]);
138
+ ```
139
+
140
+ ### Durable Object pattern
141
+
142
+ Route into a Durable Object that owns the connection:
143
+
144
+ ```typescript
145
+ export const urlpatterns = urls(({ path }) => [
146
+ path.any(
147
+ "/rooms/:roomId",
148
+ async (ctx) => {
149
+ const id = ctx.env.ROOMS.idFromName(ctx.params.roomId);
150
+ const stub = ctx.env.ROOMS.get(id);
151
+ // The DO's fetch handler calls handleWebSocketUpgrade(request)
152
+ // and returns the 101 Response. We forward it unchanged.
153
+ return stub.fetch(ctx.request);
154
+ },
155
+ { name: "room" },
156
+ ),
157
+ ]);
158
+ ```
159
+
160
+ ### Using the `agents` library
161
+
162
+ `routeAgentRequest` from `agents` returns a 101 `Response` targeted at a
163
+ Durable Object. Return it directly from `path.any()`:
164
+
165
+ ```typescript
166
+ import { routeAgentRequest } from "agents";
167
+ import { urls } from "@rangojs/router";
168
+
169
+ export const urlpatterns = urls(({ path }) => [
170
+ path.any("/agents/*", async (ctx) => {
171
+ const response = await routeAgentRequest(ctx.request, ctx.env);
172
+ if (!response) {
173
+ return new Response("not found", { status: 404 });
174
+ }
175
+ return response;
176
+ }),
177
+ ]);
178
+ ```
179
+
180
+ ## Middleware interaction
181
+
182
+ ### Forwarded, not reconstructed
183
+
184
+ When a middleware is matched for the upgrade URL, the middleware still runs
185
+ **before** `next()` — but the Response from `next()` is forwarded as-is
186
+ rather than re-wrapped. This preserves:
187
+
188
+ - The 101 status (which would otherwise throw `RangeError: Responses may
189
+ only be constructed with status codes in the range 200 to 599, inclusive`
190
+ on standards-compliant runtimes).
191
+ - The Cloudflare `webSocket` property (which would otherwise be silently
192
+ dropped by `new Response(body, ...)` on workerd).
193
+
194
+ ```typescript
195
+ // This works — logger runs, but the 101 flows through unchanged.
196
+ router.use(async (ctx, next) => {
197
+ console.log("ws request", ctx.url.pathname);
198
+ return next();
199
+ });
200
+ ```
201
+
202
+ ### Don't try to set cookies on an upgrade
203
+
204
+ Stub cookie/header writes made before `await next()` are applied to the
205
+ upgrade response on a best-effort basis — the router attempts an in-place
206
+ merge and skips any write rejected by runtimes that expose immutable 101
207
+ headers. Either way, a browser completing a WS handshake never reads them.
208
+ Do not rely on this for auth or state propagation: set cookies via a prior
209
+ HTTP request instead (e.g. during login), then read them at upgrade time
210
+ via `ctx.request.headers.get("cookie")`.
211
+
212
+ ```typescript
213
+ // Avoid: this cookie may not land on the upgrade response, and the client
214
+ // never reads it during the handshake regardless.
215
+ router.use(async (ctx, next) => {
216
+ cookies().set("last-ws-at", Date.now().toString());
217
+ return next();
218
+ });
219
+
220
+ // Prefer: authenticate by reading a cookie set on a prior HTTP request.
221
+ path.any("/ws", (ctx) => {
222
+ const session = parseCookie(ctx.request.headers.get("cookie"))?.session;
223
+ if (!verify(session)) return new Response("unauthorized", { status: 401 });
224
+ // ...upgrade
225
+ });
226
+ ```
227
+
228
+ ### Short-circuit before upgrade
229
+
230
+ Middleware can return a non-101 Response to deny the upgrade outright:
231
+
232
+ ```typescript
233
+ router.use(async (ctx, next) => {
234
+ if (!isAllowed(ctx.request)) {
235
+ return new Response("forbidden", { status: 403 });
236
+ }
237
+ return next();
238
+ });
239
+ ```
240
+
241
+ ## Caching
242
+
243
+ - **SSE** — do not combine with `cache()` (streams can't be replayed).
244
+ - **WebSocket** — `cache()` is inert because only `status === 200` is cacheable.
245
+
246
+ ## Runtime caveats
247
+
248
+ | Runtime | SSE | WebSocket upgrade (101) |
249
+ | -------------------------------------- | --- | ---------------------------------------------------- |
250
+ | Cloudflare Workers (workerd) | OK | OK (native `WebSocketPair`, DO, `agents`) |
251
+ | Node (undici fetch) | OK | N/A — Node's HTTP server must upgrade |
252
+ | Bun | OK | Bun's native `upgrade()` — not a Response-based path |
253
+ | Dev (Vite + `@cloudflare/vite-plugin`) | OK | OK via workerd emulation |
254
+
255
+ When running in pure Node without workerd, a `status: 101` Response cannot
256
+ even be constructed (`new Response(null, { status: 101 })` throws). For
257
+ tests, fabricate upgrade-style responses by overriding `.status` on a real
258
+ Response instance:
259
+
260
+ ```typescript
261
+ const upgrade = new Response(null, { status: 200 });
262
+ Object.defineProperty(upgrade, "status", { value: 101, configurable: true });
263
+ // optional: attach a webSocket stub
264
+ Object.defineProperty(upgrade, "webSocket", {
265
+ value: { stub: "ws" },
266
+ configurable: true,
267
+ enumerable: true,
268
+ });
269
+ ```
270
+
271
+ ## Testing
272
+
273
+ - Unit tests: `isWebSocketUpgradeResponse` and `executeMiddleware` passthrough
274
+ cases live in `src/rsc/__tests__/helpers.test.ts` and
275
+ `src/router/middleware.test.ts`.
276
+ - E2E: cover both dev and production modes against a workerd target. SSE
277
+ can be tested on any runtime; WS upgrades need workerd (use
278
+ `@cloudflare/vite-plugin` or `wrangler dev`).
279
+
280
+ ## See also
281
+
282
+ - `response-routes` — the parent skill for `path.json/text/html/stream/any`.
283
+ - `middleware` — how global and route-level middleware compose with handlers.
@@ -37,7 +37,11 @@ export default defineConfig({
37
37
 
38
38
  ## Document Component
39
39
 
40
- Import the CSS file with `?url` to get a hashed URL, then preload and link it in `<head>`:
40
+ Import the CSS file with `?url` to get a hashed URL, then preload and link it in
41
+ `<head>`. Give the `<link rel="stylesheet">` a `precedence` prop so React 19
42
+ manages it as a resource — de-duped by `href`, ordered, and loaded before paint
43
+ (no flash of unstyled content). See
44
+ [Stylesheets and cross-app navigation](#stylesheets-and-cross-app-navigation):
41
45
 
42
46
  ```tsx
43
47
  // src/document.tsx
@@ -51,8 +55,8 @@ export function Document({ children }: { children: ReactNode }) {
51
55
  return (
52
56
  <html lang="en">
53
57
  <head>
54
- <link rel="preload" href={styles} as="style" />
55
- <link rel="stylesheet" href={styles} />
58
+ <link rel="preload" href={styles} as="style" precedence="default" />
59
+ <link rel="stylesheet" href={styles} precedence="default" />
56
60
  <MetaTags />
57
61
  </head>
58
62
  <body className="font-sans antialiased text-slate-900 bg-slate-50">
@@ -63,6 +67,26 @@ export function Document({ children }: { children: ReactNode }) {
63
67
  }
64
68
  ```
65
69
 
70
+ ## Stylesheets and cross-app navigation
71
+
72
+ The `precedence` prop opts a `<link rel="stylesheet">` into React 19's managed
73
+ stylesheet model — React de-duplicates it by `href`, orders it by precedence, and
74
+ loads it before paint (avoiding a flash of unstyled content). It is the
75
+ recommended way to render a stylesheet link, which is why the example above uses
76
+ it. (A bare side-effect `import "./index.css"` also produces managed CSS via
77
+ `@vitejs/plugin-rsc`, but carries an SSR-streaming caveat — prefer the `?url` +
78
+ `<link precedence>` form for document CSS. See `/css`.)
79
+
80
+ For **host-router** apps (`/host-router`), a client-side navigation that crosses
81
+ an app boundary is a **full document load**, not a soft swap — the framework
82
+ redirects on an app switch. So each app's document (its stylesheets, theme, meta)
83
+ is always re-established cleanly by the target app's own load; you do not have to
84
+ coordinate stylesheet `href`s or `precedence` across apps. (This replaced an
85
+ earlier soft cross-app swap, under which a stylesheet shared across apps — every
86
+ app's `@import "tailwindcss"` compiles to one hashed asset — could be dropped by
87
+ React's by-`href` resource dedup if the apps disagreed on `precedence`. The full
88
+ reload removes that footgun entirely.)
89
+
66
90
  The `?url` suffix tells Vite to return the processed CSS file's URL instead of injecting it as a side effect. This gives you a stable, hashed asset path that works in both development and production.
67
91
 
68
92
  ## Customizing the Theme