@thomas.f/prnext 0.1.16-alpha → 0.1.18-alpha

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 (215) hide show
  1. package/AI.md +63 -0
  2. package/README.md +35 -503
  3. package/build/ADAPTERS.md +27 -2
  4. package/build/actions.mjs +6 -1
  5. package/build/analyze.mjs +340 -0
  6. package/build/app-config.mjs +20 -8
  7. package/build/app-static.mjs +41 -14
  8. package/build/app-styles.mjs +79 -0
  9. package/build/app.mjs +99 -31
  10. package/build/build-lock.mjs +6 -6
  11. package/build/bundle-runtime.mjs +39 -11
  12. package/build/cache-components.mjs +1 -1
  13. package/build/cache-handlers.mjs +1 -1
  14. package/build/check.mjs +56 -11
  15. package/build/compact-css.mjs +53 -0
  16. package/build/compilation-cache.mjs +269 -0
  17. package/build/compilation-store.mjs +142 -0
  18. package/build/compiler.mjs +51 -4
  19. package/build/concurrent.mjs +12 -0
  20. package/build/config.mjs +50 -13
  21. package/build/create.mjs +239 -0
  22. package/build/css-modules.mjs +65 -10
  23. package/build/deployment-assets.mjs +89 -0
  24. package/build/deployment-config.mjs +18 -0
  25. package/build/dev-compiler-health.mjs +11 -0
  26. package/build/dev-css-update.mjs +85 -0
  27. package/build/dev-dependencies.mjs +44 -0
  28. package/build/dev-discovery.mjs +28 -0
  29. package/build/dev-worker.mjs +14 -5
  30. package/build/dev.mjs +64 -6
  31. package/build/dynamic.mjs +1 -1
  32. package/build/edge.mjs +5 -4
  33. package/build/fonts.mjs +1 -1
  34. package/build/framework-imports.mjs +50 -0
  35. package/build/html-only-config.mjs +53 -0
  36. package/build/html-only-graph.mjs +67 -0
  37. package/build/incremental.mjs +8 -4
  38. package/build/index.mjs +140 -112
  39. package/build/inspect.mjs +3 -0
  40. package/build/inspection-summary.mjs +37 -4
  41. package/build/instrumentation.mjs +30 -0
  42. package/build/loader-cache.mjs +53 -26
  43. package/build/metadata-routes.mjs +3 -4
  44. package/build/migrate.mjs +60 -57
  45. package/build/migration-audit.mjs +1 -1
  46. package/build/native-type-config.mjs +37 -0
  47. package/build/npm-packages.mjs +3 -18
  48. package/build/package-manager.mjs +308 -0
  49. package/build/persistent-cache.mjs +112 -0
  50. package/build/precompress.mjs +62 -30
  51. package/build/prerender-cache.mjs +70 -0
  52. package/build/prerender-config.mjs +29 -0
  53. package/build/prerender-pages.mjs +100 -0
  54. package/build/prerender-pool.mjs +165 -0
  55. package/build/prerender-process-worker.mjs +58 -0
  56. package/build/prerender-worker.mjs +10 -0
  57. package/build/production-builder.mjs +129 -0
  58. package/build/react-compiler-report.mjs +225 -0
  59. package/build/react-compiler.mjs +139 -0
  60. package/build/runtime-contract.mjs +2 -2
  61. package/build/scan.mjs +4 -4
  62. package/build/source-analysis.mjs +42 -0
  63. package/build/static-adapter.mjs +13 -5
  64. package/build/static-export.mjs +4 -4
  65. package/build/static-input.mjs +48 -0
  66. package/build/styles.mjs +17 -3
  67. package/build/transform-cache.mjs +37 -4
  68. package/build/transform.mjs +1 -1
  69. package/build/type-command.mjs +41 -0
  70. package/build/typed-routes.mjs +95 -0
  71. package/build/typescript-dev-worker.mjs +18 -0
  72. package/build/typescript-dev.mjs +49 -0
  73. package/build/typescript-native-worker.mjs +55 -0
  74. package/build/typescript-native.mjs +18 -0
  75. package/build/typescript-toolchain.mjs +19 -0
  76. package/build/typescript-watch.mjs +22 -0
  77. package/build/typescript.mjs +11 -8
  78. package/build/webpack-graph.mjs +170 -30
  79. package/build/worker-memory.mjs +9 -0
  80. package/build/worker-preload.mjs +9 -0
  81. package/cli.mjs +162 -122
  82. package/compat/activity-cache.cjs +26 -0
  83. package/compat/after.cjs +6 -4
  84. package/compat/app-layout-context.cjs +52 -2
  85. package/compat/base64.cjs +10 -0
  86. package/compat/cache-handlers.cjs +12 -2
  87. package/compat/cache-wire.cjs +28 -0
  88. package/compat/cache.cjs +2 -1
  89. package/compat/cache.d.cts +4 -0
  90. package/compat/cookies.cjs +21 -5
  91. package/compat/data-cache.cjs +12 -8
  92. package/compat/flight-delta.cjs +150 -0
  93. package/compat/headers.cjs +4 -2
  94. package/compat/incremental-cache.cjs +20 -7
  95. package/compat/index.d.ts +89 -10
  96. package/compat/link.cjs +12 -9
  97. package/compat/link.d.cts +4 -3
  98. package/compat/navigation-authorization.cjs +9 -0
  99. package/compat/navigation-stages.cjs +19 -0
  100. package/compat/navigation.d.cts +4 -3
  101. package/compat/offline-store.cjs +11 -0
  102. package/compat/offline.cjs +5 -0
  103. package/compat/offline.d.cts +2 -0
  104. package/compat/redis-cache.cjs +201 -0
  105. package/compat/redis-cache.d.cts +37 -0
  106. package/compat/router.d.cts +4 -3
  107. package/compat/server-primitives.cjs +31 -11
  108. package/compat/server.d.cts +15 -2
  109. package/compat/static-generation.cjs +7 -0
  110. package/compat/typed-route.d.cts +17 -0
  111. package/compat/use-cache.cjs +3 -0
  112. package/docs/ai/compatibility.md +275 -0
  113. package/docs/ai/data.md +288 -0
  114. package/docs/ai/deployment.md +269 -0
  115. package/docs/ai/manifest.json +11 -0
  116. package/docs/ai/quickstart.md +171 -0
  117. package/docs/ai/routing.md +214 -0
  118. package/docs/ai/troubleshooting.md +155 -0
  119. package/index.mjs +2 -0
  120. package/native/diagnostics.mjs +34 -0
  121. package/native/resolve.mjs +7 -2
  122. package/package.json +44 -7
  123. package/release/ai-docs.mjs +48 -0
  124. package/release/verify.mjs +2 -0
  125. package/runtime/action-client.mjs +10 -4
  126. package/runtime/action-server.mjs +2 -0
  127. package/runtime/ai-docs.mjs +202 -0
  128. package/runtime/app-client.mjs +226 -68
  129. package/runtime/app-content.mjs +4 -3
  130. package/runtime/app-partial-client.mjs +3 -0
  131. package/runtime/app-partial.mjs +1 -1
  132. package/runtime/app-prefetch-constants.mjs +4 -0
  133. package/runtime/app-prefetch.mjs +2 -3
  134. package/runtime/app-render-key.mjs +5 -0
  135. package/runtime/app-render.mjs +100 -28
  136. package/runtime/app-routing.mjs +22 -9
  137. package/runtime/app-static.mjs +18 -8
  138. package/runtime/app-stream-html.mjs +10 -53
  139. package/runtime/app-style-selection.mjs +34 -0
  140. package/runtime/app-styles.mjs +154 -0
  141. package/runtime/cache-components-rsc.mjs +4 -1
  142. package/runtime/cache-request.mjs +2 -1
  143. package/runtime/cloud/archive.mjs +5 -6
  144. package/runtime/compile-cache.mjs +8 -0
  145. package/runtime/deployment-client.mjs +19 -0
  146. package/runtime/dev-ai-brands.mjs +26 -0
  147. package/runtime/dev-ai-style.mjs +99 -0
  148. package/runtime/dev-build-reasons.mjs +25 -0
  149. package/runtime/dev-cache-control.mjs +53 -0
  150. package/runtime/dev-client.mjs +245 -46
  151. package/runtime/dev-compiler-broker.mjs +42 -0
  152. package/runtime/dev-console-copy.mjs +19 -0
  153. package/runtime/dev-console.mjs +106 -0
  154. package/runtime/dev-dependency-graph.mjs +40 -0
  155. package/runtime/dev-element-picker-style.mjs +27 -0
  156. package/runtime/dev-element-picker.mjs +213 -0
  157. package/runtime/dev-error-overlay-style.mjs +41 -0
  158. package/runtime/dev-error-overlay.mjs +167 -0
  159. package/runtime/dev-error-report.mjs +121 -0
  160. package/runtime/dev-file-changes.mjs +1 -1
  161. package/runtime/dev-gateway.mjs +44 -0
  162. package/runtime/dev-host-relay.mjs +66 -0
  163. package/runtime/dev-indicator.mjs +191 -0
  164. package/runtime/dev-issue-motion.mjs +214 -0
  165. package/runtime/dev-lazy-tool.mjs +34 -0
  166. package/runtime/dev-logo.mjs +2 -0
  167. package/runtime/dev-notification.mjs +81 -0
  168. package/runtime/dev-popout.mjs +34 -0
  169. package/runtime/dev-position.mjs +107 -0
  170. package/runtime/dev-preview-touch.mjs +122 -0
  171. package/runtime/dev-runtime-pool.mjs +45 -0
  172. package/runtime/dev-runtime-spare.mjs +57 -0
  173. package/runtime/dev-tools-client.mjs +155 -0
  174. package/runtime/dev-tools-mcp.mjs +126 -0
  175. package/runtime/dev-tools-metrics.mjs +31 -0
  176. package/runtime/dev-tools-model.mjs +42 -0
  177. package/runtime/dev-tools-panel.mjs +341 -0
  178. package/runtime/dev-tools-result.mjs +37 -0
  179. package/runtime/dev-tools-server.mjs +256 -0
  180. package/runtime/dev-tools-source.mjs +104 -0
  181. package/runtime/dev-tooltip.mjs +94 -0
  182. package/runtime/dev-ui-icons.mjs +8 -0
  183. package/runtime/dev-workbench-style.mjs +106 -0
  184. package/runtime/dev-workbench.mjs +428 -0
  185. package/runtime/dev-worker-logs.mjs +31 -0
  186. package/runtime/development.mjs +324 -0
  187. package/runtime/document.mjs +1 -1
  188. package/runtime/edge.mjs +6 -5
  189. package/runtime/env.mjs +2 -1
  190. package/runtime/fetch-cache.mjs +10 -8
  191. package/runtime/flight-delta.mjs +50 -0
  192. package/runtime/flight-tap.mjs +28 -0
  193. package/runtime/html-only.mjs +27 -0
  194. package/runtime/http.mjs +2 -0
  195. package/runtime/incremental-cache.mjs +25 -4
  196. package/runtime/instrumentation.mjs +49 -0
  197. package/runtime/navigation-recovery.mjs +36 -0
  198. package/runtime/navigation.mjs +7 -0
  199. package/runtime/node-html-stream.mjs +47 -0
  200. package/runtime/pages-client.mjs +24 -8
  201. package/runtime/pages-navigation.mjs +54 -64
  202. package/runtime/persistent/client.mjs +6 -5
  203. package/runtime/persistent/daemon.mjs +66 -56
  204. package/runtime/persistent/native-proxy.mjs +63 -0
  205. package/runtime/prefetch-scheduler.mjs +110 -0
  206. package/runtime/preload.mjs +12 -0
  207. package/runtime/profile.mjs +14 -0
  208. package/runtime/profiles.json +2 -2
  209. package/runtime/render.mjs +3 -0
  210. package/runtime/route-pattern.mjs +44 -0
  211. package/runtime/rsc-worker.mjs +41 -12
  212. package/runtime/trace.mjs +78 -0
  213. package/runtime/typed-route.mjs +19 -0
  214. package/runtime/watch-project.mjs +29 -18
  215. package/runtime/worker.mjs +102 -24
@@ -1,7 +1,7 @@
1
+ import { deploymentHeaders, assertDeploymentResponse } from './deployment-client.mjs';
1
2
  import React, { startTransition, useLayoutEffect } from 'react';
2
3
  import { preload } from 'react-dom';
3
- import { partialModel } from './app-partial-model.mjs';
4
- import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES, readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
4
+ import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
5
5
  import { isDynamicBailout } from '../compat/dynamic-bailout.cjs';
6
6
  import { createRoot, hydrateRoot } from 'react-dom/client';
7
7
  import { AppRouterProvider } from '../compat/app-context.cjs';
@@ -13,6 +13,14 @@ import { flightResponseURL, publicNavigationURL, isApplicationURL } from './clie
13
13
  import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
14
14
  import GlobalErrorBoundary, { DefaultGlobalError, GlobalErrorTrigger } from '../compat/app-global-error-boundary.cjs';
15
15
  import { loadBeforeInteractive } from '../compat/script-loader.cjs';
16
+ import { createPrefetchScheduler, prefetchCacheVictim } from './prefetch-scheduler.mjs';
17
+ import { appStyleManager } from './app-styles.mjs';
18
+ import { retainAppStyles } from './app-style-selection.mjs';
19
+ import { isHtmlDocumentRoute } from './route-pattern.mjs';
20
+ import {recoverNavigation} from './navigation-recovery.mjs';
21
+ import { createActivityCache } from '../compat/activity-cache.cjs';
22
+ import { createFlightTransport, TYPE as FLIGHT_DELTA_TYPE } from '../compat/flight-delta.cjs';
23
+ import {decodeBase64} from '../compat/base64.cjs';
16
24
 
17
25
  const PREFETCH_LIMIT = 8;
18
26
  const PREFETCH_LIFETIME = 30_000;
@@ -88,8 +96,7 @@ function embeddedFlight() {
88
96
  document.removeEventListener('DOMContentLoaded', onComplete);
89
97
  controller.close();
90
98
  } else {
91
- const binary = atob(value);
92
- controller.enqueue(Uint8Array.from(binary, character => character.charCodeAt(0)));
99
+ controller.enqueue(decodeBase64(value));
93
100
  }
94
101
  };
95
102
  onComplete = () => {
@@ -108,28 +115,27 @@ function embeddedFlight() {
108
115
  }
109
116
  const element = document.getElementById('__PRNEXT_FLIGHT__');
110
117
  if (!element) throw new Error('PRNext App Router Flight payload is missing');
111
- const binary = atob(element.textContent.trim());
112
- const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
118
+ const bytes = decodeBase64(element.textContent.trim());
113
119
  element.remove();
114
120
  return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
115
121
  }
116
122
 
117
123
  function NormalStyles({ css, fonts }) {
118
- const [styles, setStyles] = React.useState([]);
119
124
  useLayoutEffect(() => {
120
125
  for (const font of fonts || []) preload(font.href, { as: 'font', type: font.type, crossOrigin: 'anonymous' });
121
- // Fatal global renders never commit this branch. Wait for a successful
122
- // normal/local-error mount, then restore sheets omitted from its empty SSR
123
- // document. Existing plain SSR links are already loaded and need no copy.
124
- const loaded = new Set([...document.querySelectorAll('link[rel="stylesheet"]')].map(node => node.href));
125
- const missing = (css || []).filter(href => !loaded.has(new URL(href, window.location.href).href));
126
- if (missing.length) setStyles(current => [...new Set([...current, ...missing])]);
126
+ const styles = appStyleManager(document);
127
+ styles.commit(css || []);
128
+ return () => styles.release();
127
129
  }, [css, fonts]);
128
- return styles.map(href => React.createElement('link', { key: href, href, rel: 'stylesheet', precedence: 'prnext-app' }));
130
+ return null;
129
131
  }
130
132
 
131
- function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss }) {
133
+ function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss, retentionEpoch, retentionAuthorizationEpoch }) {
132
134
  const [hydrated, setHydrated] = React.useState(false);
135
+ const cache = React.useRef(null);
136
+ if (!cache.current) cache.current = createActivityCache({ limit: controller._navigationCache?.maxEntries, ttl: controller._navigationCache?.ttlMs });
137
+ const activityCache = React.useMemo(() => ({ cache: cache.current, epoch: retentionEpoch, authorizationEpoch: retentionAuthorizationEpoch,
138
+ authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey]);
133
139
  const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
134
140
  useLayoutEffect(() => {
135
141
  if (process.env.NODE_ENV === 'development' && model.instantDiagnostics?.length) globalThis.__PRNEXT_DEV__?.reportError(Object.assign(new Error(model.instantDiagnostics.join('\n')), { digest: 'PRNEXT_INSTANT_BLOCKING' }));
@@ -145,10 +151,11 @@ function AppRoot({ model, controller, committed, url, globalErrorComponent, glob
145
151
  return React.createElement(AppRouterProvider, { router, controller, ...(model.partialPreview ? { prerenderSearch: previewDynamic, ...(model.partialPreview.unknownParams ? { prerenderParams: previewDynamic } : {}) } : {}) },
146
152
  React.createElement(GlobalErrorBoundary, { component: globalErrorComponent, css: globalErrorCss, resetKey: model },
147
153
  model.globalError ? React.createElement(GlobalErrorTrigger, { error: model.globalError })
148
- : appContent(model.tree, { layoutCache: model.layoutCache || null, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
154
+ : appContent(model.tree, { layoutCache: model.layoutCache || null, activityCache, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
149
155
  }
150
156
 
151
157
  function prepareLayoutCache(model, previous) {
158
+ retainAppStyles(model, previous);
152
159
  if (!model.routing || model.layoutCache) return model;
153
160
  const active = new Set(Object.keys(model.routing.slots));
154
161
  model.layoutCache = {
@@ -166,7 +173,7 @@ function AppView(props) {
166
173
  }
167
174
 
168
175
  function appView(props) {
169
- return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, props));
176
+ return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, { ...props, retentionEpoch: props.controller?._retentionEpoch || 0, retentionAuthorizationEpoch: props.controller?._authorizationEpoch || 0 }));
170
177
  }
171
178
 
172
179
  function historyState(key, scroll, routing) {
@@ -179,7 +186,7 @@ function scrollToTarget(url, saved) {
179
186
  if (url.hash) {
180
187
  let name = url.hash.slice(1);
181
188
  try { name = decodeURIComponent(name); } catch { /* A literal malformed escape can be an element ID. */ }
182
- const anchor = document.getElementById(name) || document.getElementsByName(name)[0];
189
+ const anchor = [...document.querySelectorAll('[id], [name]')].find(element => (element.id === name || element.getAttribute('name') === name) && element.getClientRects().length);
183
190
  if (anchor) { anchor.scrollIntoView(); return; }
184
191
  }
185
192
  window.scrollTo(0, 0);
@@ -191,7 +198,7 @@ function assertModel(model) {
191
198
  return model;
192
199
  }
193
200
 
194
- async function startApp({ clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false } = {}) {
201
+ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false, partialPrefetching = false, navigationCache } = {}) {
195
202
  strictMode = enabledStrictMode;
196
203
  await loadBeforeInteractive();
197
204
  basePath = normalizeBasePath(basePath);
@@ -201,8 +208,18 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
201
208
  await globalThis.__webpack_chunk_load__(globalErrorId);
202
209
  return { default: globalThis.__webpack_require__(globalErrorId).default };
203
210
  }) : DefaultGlobalError;
211
+ // The production browser graph fixes this feature at build time. The
212
+ // fallback also keeps direct, unbundled runtime consumers functional.
213
+ const partialRuntime = typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined'
214
+ ? cacheComponents ? import('./app-partial-client.mjs') : null
215
+ : __PRNEXT_CACHE_COMPONENTS__ ? import('./app-partial-client.mjs') : null;
204
216
  // The Flight adapter reads __webpack_require__.u during module evaluation.
205
- const flightClient = await import('react-server-dom-webpack/client.browser');
217
+ // Both imports start concurrently and receive rejection handlers in this
218
+ // turn. A rejected PPR chunk must not wait unhandled for a slow Flight chunk.
219
+ const [flightClient, partial] = await Promise.all([import('react-server-dom-webpack/client.browser'), partialRuntime]);
220
+ // Ordinary App routes do not need the resumable PPR model. Keep it out of
221
+ // their initial module graph; Cache Components still loads it before use.
222
+ const { partialModel, readPartialPrefetch, partialFlightStream } = partial || {};
206
223
  const { createFromFetch, createFromReadableStream, createServerReference, createTemporaryReferenceSet, encodeReply } = flightClient.default || flightClient;
207
224
  let root;
208
225
  let actionQueue = Promise.resolve();
@@ -222,16 +239,15 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
222
239
  let currentUrl = new URL(window.location.href);
223
240
  let currentRouting = initialModel.routing;
224
241
  let currentModel = initialModel;
225
- const historyModels = new Map();
242
+ const historyFocus = new Map();
226
243
  let currentKey = window.history.state?.__prnext?.key || 0;
227
244
  let nextKey = currentKey + 1;
228
245
  let navigation = 0;
229
- let activeRequest;
230
- const prefetches = new Map();
231
- const partialSegments = new Map();
246
+ let activeRequest, finishNavigation, pageHidden = false, notifyingPrefetch = false;
247
+ const prefetches = new Map(), scheduler = createPrefetchScheduler();
248
+ const partialSegments = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? new Map() : null;
232
249
  let segmentBytes = 0;
233
250
 
234
- if (currentRouting) historyModels.set(currentKey, initialModel);
235
251
  window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
236
252
  window.history.scrollRestoration = 'manual';
237
253
 
@@ -239,42 +255,62 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
239
255
  // popstate has already changed location; never overwrite its destination.
240
256
  if (window.location.href === currentUrl.href) {
241
257
  window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
258
+ if (typeof WeakRef !== 'undefined' && document.activeElement && document.activeElement !== document.body) historyFocus.set(currentKey, new WeakRef(document.activeElement));
259
+ while (historyFocus.size > 16) historyFocus.delete(historyFocus.keys().next().value);
242
260
  }
243
261
  }
244
262
 
245
- async function requestFlight(url, signal, routing = currentRouting, onPartial) {
263
+ const flightTransport = createFlightTransport();
264
+ async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false,fullFlight=false}={}) {
265
+ if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
246
266
  const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
247
- const target = exported ? new URL(basePath + '/_prnext/flight' + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
267
+ const base = !exported && !fullFlight ? flightTransport.snapshot() : undefined;
268
+ const target = exported ? new URL(basePath + '/_prnext/flight' + (deploymentId ? '/' + encodeURIComponent(deploymentId) : '') + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
248
269
  const response = await fetch(target.href, {
249
- headers: { RSC: '1', Accept: 'text/x-component', ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
270
+ headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: 'text/x-component', ...(!exported&&!fullFlight?{'x-prnext-flight-accept':'v1',...(base?{'x-prnext-flight-base':base.token}:{})}:{}), ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
250
271
  credentials: 'same-origin',
251
272
  signal,
252
273
  });
274
+ if (!exported) await assertDeploymentResponse(response, deploymentId);
253
275
  let destination;
254
276
  try { destination = exported ? url : flightResponseURL(response, url); }
255
277
  catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
256
- if (exported ? !response.ok : !response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
278
+ const delta = response.headers.get('content-type')?.startsWith(FLIGHT_DELTA_TYPE);
279
+ if (exported ? !response.ok : !(delta||response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component'))) {
257
280
  await response.body?.cancel();
258
281
  throw new Error('The navigation destination requires a document request');
259
282
  }
260
- onPartial?.(response, destination);
261
- const model = assertModel(await createFromFetch(Promise.resolve(response), flightOptions));
283
+ await onPartial?.(response, destination);
284
+ let model;
285
+ try { model = assertModel(await createFromFetch(Promise.resolve(exported?response:await flightTransport.response(response,base)), flightOptions)); }
286
+ catch(error) {
287
+ if(!delta||fullFlight||signal?.aborted)throw error;
288
+ flightTransport.clear();
289
+ return requestFlight(url,signal,routing,onPartial,{skipEnsure:true,fullFlight:true});
290
+ }
262
291
  model.router.basePath = basePath;
263
292
  const rewrite = readRewriteHeader(response);
264
293
  if (rewrite) model.rewrite = rewrite;
265
294
  return { model, url: destination };
266
295
  }
267
296
 
268
- async function requestPartial(url, signal) {
297
+ const navigateFlight=(url,signal,...args)=>recoverNavigation(recoverySignal=>requestFlight(url,recoverySignal,...args),{signal});
298
+
299
+ const requestPartial = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? async function requestPartial(url, signal, full = false) {
269
300
  const response = await fetch(url.href, { credentials: 'same-origin', signal,
270
- headers: { RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
301
+ headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
302
+ ...(partialPrefetching ? { 'x-prnext-prefetch-stage': full ? 'prefetch' : 'shell' } : {}),
271
303
  ...(partialSegments.size ? { 'x-prnext-prefetch-known': [...partialSegments.keys()].join(',') } : {}) } });
272
- const destination = flightResponseURL(response, url);
304
+ await assertDeploymentResponse(response, deploymentId);
305
+ let destination;
306
+ try { destination = flightResponseURL(response, url); }
307
+ catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
273
308
  if (!response.headers.get('content-type')?.startsWith(PARTIAL_PREFETCH_TYPE)) {
274
309
  await response.body?.cancel();
275
310
  return null;
276
311
  }
277
312
  const value = await readPartialPrefetch(response);
313
+ if (signal.aborted) return null;
278
314
  let segment = partialSegments.get(value.id);
279
315
  if (!segment && value.flight) {
280
316
  const bytes = value.flight.length;
@@ -293,11 +329,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
293
329
  // No visitor data is decoded here. Explicit postponed errors become pending
294
330
  // boundaries until the navigation's independently authorized Flight arrives.
295
331
  const suspended = new Promise(() => {});
296
- const model = partialModel(assertModel(await segment.model), { keyMap: new Map(value.keys), suspended });
332
+ const decoded = assertModel(await segment.model);
333
+ if (signal.aborted) return null;
334
+ const model = partialModel(decoded, { keyMap: new Map(value.keys), suspended });
297
335
  model.router = { ...value.router, basePath };
298
336
  model.partialPreview = { unknownParams: value.unknownParams, suspended };
299
337
  return { model, id: value.id, url: destination, partial: true };
300
- }
338
+ } : null;
301
339
 
302
340
  function dropPrefetch(key, invalidate = false) {
303
341
  const entry = prefetches.get(key);
@@ -306,13 +344,22 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
306
344
  clearTimeout(entry.timer);
307
345
  if (invalidate) {
308
346
  entry.abort.abort();
309
- entry.onInvalidate?.();
347
+ const callbacks = entry.invalidations; entry.invalidations = undefined;
348
+ // Callbacks may rearm prefetch. Deliver after cache/queue mutations so a
349
+ // replacement cannot be evicted inside the same invalidation transaction.
350
+ if (callbacks?.size) queueMicrotask(() => {
351
+ for (const callback of callbacks) {
352
+ notifyingPrefetch = true;
353
+ try { callback(); } catch (error) { console.error(error); }
354
+ finally { notifyingPrefetch = false; }
355
+ }
356
+ });
310
357
  }
311
358
  }
312
359
 
313
360
  function invalidatePrefetches() {
314
- partialSegments.clear(); segmentBytes = 0;
315
- for (const key of prefetches.keys()) dropPrefetch(key, true);
361
+ if (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) { partialSegments.clear(); segmentBytes = 0; }
362
+ for (const [key, entry] of [...prefetches]) if (prefetches.get(key) === entry) dropPrefetch(key, true);
316
363
  }
317
364
 
318
365
  function callServer(id, args) {
@@ -320,7 +367,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
320
367
  const token = ++navigation;
321
368
  const target = new URL(window.location.href);
322
369
  activeRequest?.abort();
370
+ appStyleManager(document).cancel();
371
+ finishNavigation?.(); finishNavigation = undefined;
323
372
  activeRequest = undefined;
373
+ purgeRetainedPages();
324
374
  // Mutations are dispatched in order, including Set-Cookie effects required
325
375
  // by a subsequent action. Capture their route priority when invoked: a
326
376
  // queued action must not cancel a newer user navigation when it starts.
@@ -330,12 +380,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
330
380
  }
331
381
 
332
382
  async function dispatchAction(id, args, token, target) {
383
+ const release = scheduler.navigation();
384
+ try { return await performAction(id, args, token, target); } finally { release(); }
385
+ }
386
+
387
+ async function performAction(id, args, token, target) {
333
388
  invalidatePrefetches();
334
389
  const temporaryReferences = createTemporaryReferenceSet();
335
390
  const body = await encodeReply(args, { temporaryReferences });
336
391
  const response = await fetch(target.href, {
337
392
  method: 'POST',
338
- headers: { 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
393
+ headers: { ...deploymentHeaders(deploymentId), 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
339
394
  credentials: 'same-origin',
340
395
  // RPC redirects arrive as Flight data. Never replay a mutation through an
341
396
  // unexpected HTTP 307/308 or redirect an authenticated POST off origin.
@@ -343,11 +398,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
343
398
  body,
344
399
  });
345
400
  invalidatePrefetches();
401
+ await assertDeploymentResponse(response, deploymentId);
346
402
  if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
347
403
  await response.body?.cancel();
348
404
  throw new Error(`Server Action request failed (${response.status})`);
349
405
  }
350
406
  const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
407
+ controller._retentionEpoch++;
351
408
  if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
352
409
  if (token === navigation) window.location.replace(target.href);
353
410
  return model.actionResult;
@@ -363,9 +420,12 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
363
420
  const rewrite = readRewriteHeader(response);
364
421
  if (rewrite) model.rewrite = rewrite;
365
422
  prepareLayoutCache(model, currentModel);
423
+ try { await appStyleManager(document).prepare(model.globalError ? [] : model.css || []); }
424
+ catch (error) { if (token !== navigation) return model.actionResult; throw error; }
425
+ if (token !== navigation) return model.actionResult;
366
426
  currentRouting = model.routing;
367
427
  currentModel = model;
368
- if (currentRouting) historyModels.set(currentKey, model);
428
+ appStyleManager(document).handoff(model.globalError ? [] : model.css || []);
369
429
  startTransition(() => root.render(appView({ model, controller, url: target.href, globalErrorComponent, globalErrorCss })));
370
430
  }
371
431
  if (model?.actionError) {
@@ -382,14 +442,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
382
442
  }
383
443
 
384
444
  async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
445
+ if (pageHidden) return;
385
446
  const target = navigationUrl(href);
386
- if (!isApplicationURL(target, window.location.origin, basePath)) {
447
+ if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) {
387
448
  if (replace) window.location.replace(target.href);
388
449
  else window.location.assign(target.href);
389
450
  return;
390
451
  }
391
452
  const token = ++navigation;
392
453
  activeRequest?.abort();
454
+ appStyleManager(document).cancel();
455
+ finishNavigation?.(); finishNavigation = undefined;
393
456
  activeRequest = undefined;
394
457
  saveScroll();
395
458
 
@@ -413,90 +476,156 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
413
476
  return;
414
477
  }
415
478
 
479
+ const cacheKey = `${target.origin}${target.pathname}${target.search}`;
480
+ const candidate = !refresh && prefetches.get(cacheKey);
481
+ const prefetched = candidate && !candidate.abort.signal.aborted ? candidate : undefined;
482
+ if (candidate && !prefetched) dropPrefetch(cacheKey, true);
483
+ const releaseNavigation = scheduler.navigation(prefetched?.task);
484
+ finishNavigation = releaseNavigation;
416
485
  try {
417
- const cacheKey = `${target.origin}${target.pathname}${target.search}`;
418
- const prefetched = !refresh && prefetches.get(cacheKey);
419
486
  let result;
420
487
  let historyUpdated = false;
421
- const historyModel = pop && savedRouting && historyModels.get(window.history.state?.__prnext?.key);
422
- if (historyModel) result = { model: historyModel, url: target };
423
- else if (prefetched) {
488
+ // Always authorize history navigation again. Mounted branches can retain
489
+ // UI state while fresh Flight verifies cookies, middleware and rewrites.
490
+ if (prefetched) {
424
491
  activeRequest = prefetched.abort;
425
492
  dropPrefetch(cacheKey);
426
- result = await prefetched.promise;
493
+ try { result = await prefetched.promise; }
494
+ catch (error) { if (token !== navigation || (prefetched.abort.signal.aborted && !prefetched.failed)) throw error; result = null; }
427
495
  if (result && !result.partial) {
428
- result = { ...result, url: new URL(result.url.href) };
429
- if (!result.url.hash) result.url.hash = target.hash;
496
+ if (result.model.authorizationKey) {
497
+ // A full private prefetch warmed modules/data, not authorization.
498
+ // HttpOnly cookies may have changed since it was produced.
499
+ const abort = new AbortController(); activeRequest = abort;
500
+ result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : currentRouting);
501
+ } else {
502
+ result = { ...result, url: new URL(result.url.href) };
503
+ if (!result.url.hash) result.url.hash = target.hash;
504
+ }
430
505
  } else {
431
506
  const preview = result;
432
507
  const abort = new AbortController();
433
508
  activeRequest = abort;
434
- result = await requestFlight(target, abort.signal, currentRouting, (response, destination) => {
509
+ result = await navigateFlight(target, abort.signal, currentRouting, async (response, destination) => {
435
510
  if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
436
511
  (preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
437
512
  // Auth, middleware and rewrites ran again. A changed target or a
438
513
  // redirect cannot reveal an obsolete protected prefetched segment.
439
514
  if (destination.pathname !== preview.url.pathname || destination.search !== preview.url.search) return;
515
+ prepareLayoutCache(preview.model, currentModel);
516
+ await appStyleManager(document).prepare(preview.model.css || [], abort.signal);
517
+ if (token !== navigation || abort.signal.aborted) return;
440
518
  updateHistory(destination); historyUpdated = true;
519
+ appStyleManager(document).handoff(preview.model.css || []);
441
520
  startTransition(() => root.render(appView({ model: preview.model, controller, url: destination.href, globalErrorComponent, globalErrorCss })));
442
521
  });
443
522
  }
444
523
  } else {
445
524
  const abort = new AbortController();
446
525
  activeRequest = abort;
447
- result = await requestFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting);
526
+ // A refresh is already inside the compiler's apply transaction. Calling
527
+ // ensureRoute from here can wait for that same transaction forever when
528
+ // this tree has no routing state (the ordinary single-route case).
529
+ result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
448
530
  }
449
531
  if (token !== navigation) return;
450
532
  if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
451
533
  if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
452
534
  prepareLayoutCache(result.model, currentModel);
535
+ await appStyleManager(document).prepare(result.model.globalError ? [] : result.model.css || [], activeRequest?.signal);
536
+ if (token !== navigation) return;
453
537
  currentRouting = result.model.routing;
454
538
  currentModel = result.model;
455
539
  if (currentRouting) invalidatePrefetches();
456
540
  if (!historyUpdated) updateHistory(result.url);
457
- if (currentRouting) historyModels.set(currentKey, currentModel);
458
- while (historyModels.size > 16) historyModels.delete(historyModels.keys().next().value);
459
541
  const committed = () => {
460
- if (token === navigation && scroll && !refresh) scrollToTarget(result.url, savedScroll);
542
+ if (token === navigation && scroll && !refresh) {
543
+ scrollToTarget(result.url, savedScroll);
544
+ const focus = pop && historyFocus.get(currentKey)?.deref();
545
+ if (focus?.isConnected && focus.getClientRects().length) focus.focus({ preventScroll: true });
546
+ }
461
547
  };
462
- startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
548
+ appStyleManager(document).handoff(result.model.globalError ? [] : result.model.css || []);
549
+ if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>startTransition(()=>root.render(appView({model:result.model,controller,committed:()=>{committed();resolve();},url:result.url.href,globalErrorComponent,globalErrorCss}))));
550
+ else startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
463
551
  } catch (error) {
464
552
  if (token !== navigation || error?.name === 'AbortError') return;
553
+ if(error?.code==='PRNEXT_NAVIGATION_NETWORK'){
554
+ window.dispatchEvent(new CustomEvent('prnext:navigation-error',{detail:{message:error.message}}));
555
+ return;
556
+ }
465
557
  // Pages Router routes, public files, and unavailable Flight endpoints use
466
558
  // a normal document navigation, including the browser's redirect handling.
467
559
  if (replace || pop || refresh) window.location.replace(target.href);
468
560
  else window.location.assign(target.href);
561
+ } finally {
562
+ if (finishNavigation === releaseNavigation) finishNavigation = undefined;
563
+ releaseNavigation();
469
564
  }
470
565
  }
471
566
 
567
+ function purgeRetainedPages() {
568
+ controller._retentionEpoch++;
569
+ historyFocus.clear();
570
+ startTransition(() => root?.render(appView({ model: currentModel, controller, url: currentUrl.href, globalErrorComponent, globalErrorCss })));
571
+ }
572
+
472
573
  const controller = {
473
- partialPrefetch: cacheComponents,
574
+ _retentionEpoch: 0,
575
+ _authorizationEpoch: 0,
576
+ _navigationCache: navigationCache,
577
+ partialPrefetch: (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__),
474
578
  push: (href, options) => { void navigate(navigationUrl(href), options); },
475
579
  replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
476
580
  refresh: () => {
477
581
  invalidatePrefetches();
582
+ purgeRetainedPages();
478
583
  void navigate(new URL(window.location.href), { refresh: true });
479
584
  },
480
585
  back: () => { saveScroll(); window.history.back(); },
481
586
  forward: () => { saveScroll(); window.history.forward(); },
482
587
  prefetch(href, options = {}) {
588
+ if (pageHidden) return;
483
589
  const target = navigationUrl(href);
484
- if (!isApplicationURL(target, window.location.origin, basePath)) return;
590
+ if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) return;
485
591
  target.hash = '';
486
592
  const key = target.href;
487
- if (prefetches.has(key)) return;
488
- while (prefetches.size >= PREFETCH_LIMIT) dropPrefetch(prefetches.keys().next().value, true);
593
+ const priority = options.priority === 'viewport' ? 0 : 1;
594
+ const previous = prefetches.get(key);
595
+ if (previous && !previous.abort.signal.aborted && (!options.full || previous.full)) {
596
+ previous.priority = Math.max(previous.priority, priority);
597
+ previous.task.promote(options.priority);
598
+ if (typeof options.onInvalidate === 'function') previous.invalidations.add(options.onInvalidate);
599
+ return;
600
+ }
601
+ if (previous) dropPrefetch(key, true);
602
+ // Self-rearming callbacks may fill free slots, but cannot keep evicting
603
+ // each other forever when more warm routes compete for a bounded cache.
604
+ if (notifyingPrefetch && prefetches.size >= PREFETCH_LIMIT) return;
605
+ const victim = prefetches.size >= PREFETCH_LIMIT ? prefetchCacheVictim(prefetches, priority) : undefined;
606
+ if (prefetches.size >= PREFETCH_LIMIT && victim === undefined) return;
489
607
  const abort = new AbortController();
490
- const entry = { abort, onInvalidate: options.onInvalidate };
491
- entry.promise = cacheComponents && !window.__PRNEXT_STATIC_EXPORT__ ? requestPartial(target, abort.signal) : requestFlight(target, abort.signal);
608
+ const entry = { abort, priority, full: options.full === true, invalidations: new Set(typeof options.onInvalidate === 'function' ? [options.onInvalidate] : []) };
609
+ entry.task = scheduler.schedule(() => (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) && !window.__PRNEXT_STATIC_EXPORT__
610
+ ? options.full && !partialPrefetching ? requestFlight(target, abort.signal) : requestPartial(target, abort.signal, options.full) : requestFlight(target, abort.signal), { abort, priority: options.priority });
611
+ if (!entry.task) return;
612
+ if (victim !== undefined) dropPrefetch(victim, true);
613
+ entry.promise = entry.task.promise;
492
614
  // A failed speculative fetch must never produce an unhandled rejection.
493
- entry.promise.catch(() => { if (prefetches.get(key) === entry) dropPrefetch(key, true); });
615
+ entry.promise.catch(error => {
616
+ // An intentional cancellation is not a cache miss to retry. In
617
+ // particular pagehide must never create a replacement Flight request.
618
+ entry.failed = !abort.signal.aborted && error?.name !== 'AbortError';
619
+ if (prefetches.get(key) === entry) dropPrefetch(key, true);
620
+ });
494
621
  entry.timer = setTimeout(() => dropPrefetch(key, true), PREFETCH_LIFETIME);
495
622
  prefetches.set(key, entry);
496
623
  },
497
624
  };
498
625
 
499
- const initialView = appView({ model: initialModel, controller, globalErrorComponent, globalErrorCss });
626
+ let resolveDevReady;
627
+ const devReady=process.env.NODE_ENV==='development'?new Promise(resolve=>{resolveDevReady=resolve;}):undefined;
628
+ const initialView = appView({ model: initialModel, controller, globalErrorComponent, globalErrorCss, ...(devReady?{committed:resolveDevReady}:{}) });
500
629
  const rootOptions = {
501
630
  formState: initialModel.formState ?? null,
502
631
  onCaughtError(error) {
@@ -506,7 +635,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
506
635
  }
507
636
  },
508
637
  onRecoverableError(error) {
509
- if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause)) console.error(error);
638
+ if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause)) {
639
+ console.error(error);
640
+ if (process.env.NODE_ENV === 'development') globalThis.__PRNEXT_DEV__?.reportRecoverableError(error);
641
+ }
510
642
  },
511
643
  };
512
644
  if (document.documentElement.id === '__prnext_error__') {
@@ -518,15 +650,36 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
518
650
  window.addEventListener('popstate', event => {
519
651
  void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
520
652
  });
521
- window.addEventListener('pagehide', saveScroll);
653
+ window.addEventListener('pagehide', () => { saveScroll(); pageHidden = true; navigation++; activeRequest?.abort(); appStyleManager(document).cancel(); finishNavigation?.(); finishNavigation = undefined; invalidatePrefetches(); });
654
+ window.addEventListener('pageshow', event => { pageHidden = false; if (event.persisted) controller.refresh(); });
655
+ window.addEventListener('prnext:invalidate-navigation', () => { controller._authorizationEpoch++; controller.refresh(); });
522
656
  let scrollFrame;
523
657
  window.addEventListener('scroll', () => {
524
658
  if (scrollFrame !== undefined) return;
525
659
  scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
526
660
  }, { passive: true });
661
+ function developmentStyles(model, routes) {
662
+ const route = routes?.find(route => route.pattern === model.styleRoute);
663
+ if (!route) return model;
664
+ const scopes = routes.flatMap(route => route.cssScopes || []);
665
+ const previous = { routing:model.routing, cssScopes:(model.cssScopes || []).map(scope => scopes.find(current => current.path === scope.path && current.kind === scope.kind) || scope) };
666
+ const next = { ...model, css:route.css, cssScopes:route.cssScopes };
667
+ retainAppStyles(next, previous);
668
+ return next;
669
+ }
527
670
  return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
671
+ devReady,
672
+ devStyles: routes => developmentStyles(currentModel, routes).css || [],
673
+ devPrepareStyles: routes => appStyleManager(document).prepare(developmentStyles(currentModel, routes).css || []),
674
+ devStylesApplied: (routes, { commit = true } = {}) => {
675
+ for (const model of [currentModel]) {
676
+ const next = developmentStyles(model, routes);
677
+ model.css = next.css; model.cssScopes = next.cssScopes;
678
+ }
679
+ if (commit) appStyleManager(document).commit(currentModel.globalError ? [] : currentModel.css || []);
680
+ },
528
681
  devPrepare: options => moduleLoader.updateModules(options.clientModules),
529
- devCommit: () => controller.refresh(),
682
+ devCommit: () => {invalidatePrefetches();purgeRetainedPages();return navigate(new URL(window.location.href),{refresh:true});},
530
683
  } : {}) };
531
684
  }
532
685
 
@@ -534,6 +687,11 @@ export function bootstrapApp(options) {
534
687
  if (process.env.NODE_ENV === 'development' && options.dev && globalThis.__PRNEXT_DEV__) {
535
688
  return globalThis.__PRNEXT_DEV__.bootstrap('app', options, () => startApp(options));
536
689
  }
537
- application ||= startApp(options);
690
+ if (!application) {
691
+ application = startApp(options);
692
+ // Generated entries intentionally do not await startup. Report a failed
693
+ // chunk once, while retaining the original rejection for explicit callers.
694
+ void application.catch(error => console.error('[PRNext] App startup failed:', error));
695
+ }
538
696
  return application;
539
697
  }
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { LayoutCacheContext } from '../compat/app-layout-context.cjs';
2
+ import { LayoutCacheContext, ActivityCacheContext } from '../compat/app-layout-context.cjs';
3
3
 
4
4
  // Preserve the same sibling positions during SSR, PPR and hydration. Even an
5
5
  // empty style slot changes React's useId tree encoding when it is omitted.
6
- export function appContent(tree, { layoutCache = null, styles = null } = {}) {
7
- return React.createElement(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles);
6
+ export function appContent(tree, { layoutCache = null, styles = null, activityCache = null } = {}) {
7
+ return React.createElement(ActivityCacheContext.Provider, { value: activityCache },
8
+ React.createElement(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles));
8
9
  }
@@ -0,0 +1,3 @@
1
+ // Loaded once, before hydration, only for Cache Components applications.
2
+ export { partialModel } from './app-partial-model.mjs';
3
+ export { readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
@@ -74,7 +74,7 @@ export async function prerenderPartialHtml(model, route, { strictMode = true, un
74
74
  if (!/<html(?:\s|>)/i.test(shell) || !/<body(?:\s|>)/i.test(shell) || !/<\/head>/i.test(shell)) {
75
75
  return null;
76
76
  }
77
- const styles = fontPreloads(route.fonts) + (route.css || []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
77
+ const styles = fontPreloads(route.fonts) + (model.css ?? route.css ?? []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
78
78
  if (styles) shell = shell.replace(/<\/head>/i, `${styles}</head>`);
79
79
  return { shell, postponed: rendered.postponed, clientDynamic };
80
80
  } catch (error) {
@@ -0,0 +1,4 @@
1
+ // Shared wire limits stay available without loading the optional PPR decoder.
2
+ export const PARTIAL_PREFETCH_TYPE = 'application/x-prnext-ppr+json';
3
+ export const PARTIAL_PREFETCH_BYTES = 768 * 1024;
4
+ export const PARTIAL_PREFETCH_CACHE_BYTES = 2 * 1024 * 1024;
@@ -1,8 +1,7 @@
1
1
  // Prefetch contains only prerendered segments. Request-dependent holes are
2
2
  // resolved by a fresh, authenticated navigation; they never enter this cache.
3
- export const PARTIAL_PREFETCH_TYPE = 'application/x-prnext-ppr+json';
4
- export const PARTIAL_PREFETCH_BYTES = 768 * 1024;
5
- export const PARTIAL_PREFETCH_CACHE_BYTES = 2 * 1024 * 1024;
3
+ import { PARTIAL_PREFETCH_BYTES } from './app-prefetch-constants.mjs';
4
+ export { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_BYTES, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
6
5
 
7
6
  export async function readPartialPrefetch(response) {
8
7
  const reader = response.body.getReader();