@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,51 +1,124 @@
1
1
  import Refresh from 'react-refresh/runtime';
2
+ import { createDevIndicator } from './dev-indicator.mjs';
3
+ import { createConsoleCapture, isDevicePreview } from './dev-console.mjs';
4
+ import { createDevToolsClient } from './dev-tools-client.mjs';
5
+ import { createDevErrorOverlay } from './dev-error-overlay.mjs';
6
+ import { collectDevIssues, formatDevIssuesForAI } from './dev-error-report.mjs';
2
7
 
3
8
  const state = globalThis.__PRNEXT_DEV__ ||= {
4
9
  applications: new Map(), modules: new Map(), buildId: null, manifest: null,
5
10
  pending: null, applying: false, unsafe: false, queued: null, source: null,
6
11
  };
12
+ // Install before application bootstrap so initial renders/effects are captured.
13
+ if (!state.consoleCapture && !state.consoleDisabled) state.consoleCapture = createConsoleCapture();
7
14
  if (!state.injected) { Refresh.injectIntoGlobalHook(globalThis); state.injected = true; }
8
15
  export const register = (type, id) => Refresh.register(type, id);
9
16
  export const signature = () => Refresh.createSignatureFunctionForTransform();
10
17
  export function registerModule(id, hash, boundary) {
11
18
  const previous = state.modules.get(id);
19
+ if (state.applying && (!previous || previous.hash !== hash)) state.refreshSourceChanged = true;
12
20
  if (previous && previous.hash !== hash && (!previous.boundary || !boundary)) state.unsafe = true;
13
21
  state.modules.set(id, { hash, boundary });
14
22
  }
15
23
 
16
- function clearError() { document.getElementById('__prnext_dev_error__')?.remove(); state.overlayError = undefined; }
24
+ function clearError({ dismiss = false } = {}) {
25
+ state.errorOverlay?.dispose();state.errorOverlay=undefined;
26
+ document.getElementById('__prnext_dev_error__')?.remove(); state.overlayError = undefined;
27
+ clearTimeout(state.errorCopyTimer);state.errorCopyTimer=undefined;
28
+ state.errorEntry=undefined;state.errorReport=undefined;state.errorSource=undefined;
29
+ state.overlaySelectedId=undefined;state.overlayKind=undefined;state.overlayIssues=undefined;state.errorSources=undefined;
30
+ if (!dismiss) state.issue = undefined;
31
+ state.indicator?.update();
32
+ }
33
+ function dismissError() {
34
+ if(state.overlayError&&typeof state.overlayError==='object')(state.dismissedErrors ||= new WeakSet()).add(state.overlayError);
35
+ const key=errorKey(state.overlayError);
36
+ if(key){const entries=state.dismissedDigests ||= new Set();if(entries.size>=32)entries.delete(entries.values().next().value);entries.add(key);}
37
+ if(state.overlayKind==='typescript')state.dismissedTypeDiagnostic=state.lastTypeDiagnostic;
38
+ clearError({dismiss:true});
39
+ let focused=document.activeElement;
40
+ while(focused?.shadowRoot?.activeElement)focused=focused.shadowRoot.activeElement;
41
+ if(!focused||focused===document.body||focused===document.documentElement||focused.closest?.('[inert]'))document.getElementById('__prnext_dev_tools__')?.shadowRoot?.querySelector('.trigger')?.focus({preventScroll:true});
42
+ }
17
43
  function errorKey(error) {
18
44
  return typeof error?.digest === 'string' ? `${error.digest.slice(0, 256)}:${String(error.message || '').slice(0, 512)}` : null;
19
45
  }
20
- function showError(error) {
46
+ function reportRecoverableError(error, info) {
47
+ const message=String(error?.message||error).slice(0,48*1024);
48
+ const key=message.replace(/^(?:Uncaught\s+)?Error:\s*/,'').split(/\n\s+at\s+/)[0].replace(/\r\n/g,'\n').trim();
49
+ if(state.recoverableKey===key)return;
50
+ // Keep one current diagnostic; the console retains the complete history.
51
+ // Never replace a blocking issue or open an overlay during successful recovery.
52
+ state.recoverableKey=key;
53
+ state.recoverableIssue={name:String(error?.name||'Error'),message,stack:String(error?.stack||message+(info?.componentStack||'')).slice(0,48*1024)};
54
+ if(state.errorOverlay)syncErrorOverlay();
55
+ state.indicator?.update();
56
+ }
57
+ function showError(error, { retain = true } = {}) {
58
+ if(error===null||error===undefined)error=new Error(String(error));
21
59
  const key = errorKey(error);
22
60
  if (error && typeof error === 'object' && (state.dismissedErrors?.has(error) || (key && state.dismissedDigests?.has(key)))) return;
23
- state.overlayError = error;
24
- let host = document.getElementById('__prnext_dev_error__');
25
- if (!host) {
26
- host = document.createElement('div'); host.id = '__prnext_dev_error__';
27
- Object.assign(host.style, { position: 'fixed', inset: '0', zIndex: '2147483647' });
28
- const shadow = host.attachShadow({ mode: 'open' });
29
- const style = document.createElement('style');
30
- style.textContent = ':host{color-scheme:dark}section{box-sizing:border-box;overflow:auto;height:100%;padding:32px;background:#17191fee;color:#f4f5f7;font:14px/1.6 ui-monospace,monospace}h1{font:600 22px system-ui;color:#ff7474}pre{white-space:pre-wrap;overflow-wrap:anywhere}button{position:fixed;right:24px;top:24px;padding:8px 14px;border:1px solid #697080;border-radius:6px;background:#292e39;color:white;cursor:pointer}';
31
- const section = document.createElement('section'); section.setAttribute('role', 'alertdialog'); section.setAttribute('aria-label', 'PRNext development error');
32
- const heading = document.createElement('h1'); heading.textContent = 'PRNext — development error';
33
- const close = document.createElement('button'); close.textContent = 'Dismiss'; close.onclick = () => {
34
- // React may report the same failed model again while its boundary retries.
35
- // Closing that diagnostic must not block the recovered application.
36
- if (state.overlayError && typeof state.overlayError === 'object') (state.dismissedErrors ||= new WeakSet()).add(state.overlayError);
37
- const key = errorKey(state.overlayError);
38
- if (key) {
39
- const entries = state.dismissedDigests ||= new Set();
40
- if (entries.size >= 32) entries.delete(entries.values().next().value);
41
- entries.add(key);
42
- }
43
- clearError();
44
- };
45
- const details = document.createElement('pre'); details.id = 'details';
46
- section.append(heading, close, details); shadow.append(style, section); document.documentElement.append(host);
61
+ if(retain){state.issue=error;if(error!==state.lastTypeDiagnostic)state.issueKind=state.devBuildState==='error'?'build':'runtime';}
62
+ state.indicator?.update();
63
+ syncErrorOverlay({preferredError:error,open:true});
64
+ }
65
+ function hasCriticalIssue(){return state.issue!==undefined&&state.issue!==state.lastTypeDiagnostic;}
66
+ function syncErrorOverlay({preferredError,selectedId,open=false}={}) {
67
+ if(!open&&!state.errorOverlay)return;
68
+ const {issues,omitted}=collectDevIssues({critical:hasCriticalIssue()?state.issue:undefined,criticalKind:state.issueKind||'runtime',typeErrors:state.typeErrors,recoverable:state.recoverableIssue});
69
+ if(!issues.length){clearError();return;}
70
+ const selected=issues.find(issue=>issue.id===selectedId)
71
+ ||issues.find(issue=>issue.error===preferredError)
72
+ ||(preferredError===state.lastTypeDiagnostic&&preferredError!==undefined?issues.find(issue=>issue.kind==='typescript'&&issue.id===state.overlaySelectedId)||issues.find(issue=>issue.kind==='typescript'):undefined)
73
+ ||issues.find(issue=>issue.id===state.overlaySelectedId)||issues[0];
74
+ const changed=!state.errorOverlay?.host.isConnected||!state.errorEntry||state.errorEntry.id!==selected.id||state.errorEntry.error!==selected.error||state.errorReport.message!==selected.report.message||state.errorReport.stack!==selected.report.stack||state.overlayKind!==selected.kind;
75
+ state.overlayIssues={issues,omitted};state.overlaySelectedId=selected.id;state.overlayKind=selected.kind;state.overlayError=selected.error;
76
+ const sources=state.errorSources ||= new Map();for(const [id,cached]of sources)if(!issues.some(issue=>issue.id===id&&issue.error===cached.error&&issue.report.message===cached.report?.message&&issue.report.stack===cached.report?.stack))sources.delete(id);
77
+ if(!state.errorOverlay?.host.isConnected){
78
+ state.errorOverlay?.dispose();
79
+ state.errorOverlay=createDevErrorOverlay({onDismiss:dismissError,onSendToAI(){
80
+ const detail={kind:'error',error:state.errorReport,source:state.errorSource};
81
+ dismissError();
82
+ globalThis.dispatchEvent(new CustomEvent('prnext:devtools:ai',{detail}));
83
+ },onSelectIssue(id){syncErrorOverlay({selectedId:id});},onCopyForAI(){void copyErrorsForAI();}});
47
84
  }
48
- host.shadowRoot.getElementById('details').textContent = String(error?.stack || error?.message || error).slice(0, 48 * 1024);
85
+ const overlay=state.errorOverlay;
86
+ if(changed){state.errorEntry=selected;state.errorReport=selected.report;state.errorSource=undefined;}
87
+ overlay.render(state.errorReport,{recoverable:selected.recoverable,kind:selected.kind,issues,selectedId:selected.id,omitted});
88
+ if(!changed)return;
89
+ const entry=state.errorEntry,cached=sources.get(selected.id);
90
+ if(cached){state.errorSource=cached.source;overlay.setSource(cached.source);overlay.setSourceStatus(cached.source.mapped?'':'Original source location.');return;}
91
+ overlay.setSourceStatus(state.tools?'Locating original source…':'');
92
+ if(state.tools)void state.tools.call('dev_source',{stack:state.errorReport.stack,error:state.errorReport}).then(source=>{
93
+ if(state.errorEntry!==entry||state.errorOverlay!==overlay||!overlay.host.isConnected)return;
94
+ sources.set(selected.id,{error:selected.error,report:entry.report,source});
95
+ state.errorSource=source;overlay.setSource(source);
96
+ overlay.setSourceStatus(source.mapped?'':'Original source location.');
97
+ }).catch(()=>{if(state.errorEntry===entry&&state.errorOverlay===overlay&&overlay.host.isConnected)overlay.setSourceStatus('Original source unavailable. Full details remain below.');});
98
+ }
99
+ async function copyErrorsForAI(){
100
+ const overlay=state.errorOverlay,current=state.overlayIssues;if(!overlay||!current)return;
101
+ try{
102
+ const sourceById=new Map([...(state.errorSources||[])].map(([id,cached])=>[id,cached.source]));
103
+ const prompt=formatDevIssuesForAI(current.issues,{route:location.pathname,sourceById,omitted:current.omitted});
104
+ if(!navigator.clipboard?.writeText)throw Error('Clipboard unavailable');
105
+ await navigator.clipboard.writeText(prompt);
106
+ if(state.errorOverlay===overlay){overlay.setCopyStatus('copied');clearTimeout(state.errorCopyTimer);state.errorCopyTimer=setTimeout(()=>{if(state.errorOverlay===overlay)overlay.setCopyStatus('idle');},1800);}
107
+ }catch{if(state.errorOverlay===overlay)overlay.setCopyStatus('error');}
108
+ }
109
+
110
+ function reload(reason) {
111
+ try{sessionStorage.setItem('__prnext_reload_reason__',reason);}catch{}
112
+ void state.tools?.browser('reload',{reason});
113
+ globalThis.dispatchEvent(new CustomEvent('prnext:reload',{detail:{reason}}));
114
+ location.reload();
115
+ }
116
+ function showReloadReason(){
117
+ let reason;try{reason=sessionStorage.getItem('__prnext_reload_reason__');sessionStorage.removeItem('__prnext_reload_reason__');}catch{}
118
+ if(!reason)return;
119
+ const note=document.createElement('div');note.id='__prnext_reload_reason__';note.setAttribute('role','status');note.textContent=reason;
120
+ Object.assign(note.style,{position:'fixed',bottom:'76px',left:'18px',maxWidth:'min(360px,calc(100vw - 36px))',padding:'10px 14px',border:'1px solid #292929',borderRadius:'10px',background:'#080808',color:'#ddd',font:'12px/1.5 system-ui',zIndex:'2147483646'});
121
+ document.documentElement.append(note);setTimeout(()=>note.remove(),6500);
49
122
  }
50
123
 
51
124
  async function updateStyles(urls) {
@@ -62,12 +135,54 @@ async function updateStyles(urls) {
62
135
  for (const link of old) if (!wanted.has(link.href)) link.remove();
63
136
  }
64
137
 
65
- async function applyUpdate(message) {
66
- if (state.applying) { state.queued = message; return; }
67
- if (!message.clientManifest) { location.reload(); return; }
138
+ function typeDiagnostics(errors) {
139
+ state.typeErrors=errors;
140
+ if(!errors.length){
141
+ if(state.lastTypeDiagnostic!==undefined&&state.issue===state.lastTypeDiagnostic){
142
+ state.issue=undefined;
143
+ }
144
+ state.lastTypeDiagnostic=undefined;state.dismissedTypeDiagnostic=undefined;if(state.errorOverlay)syncErrorOverlay();state.indicator?.update();return;
145
+ }
146
+ const text=errors.join('\n');
147
+ const previous=state.lastTypeDiagnostic;
148
+ const critical=state.issue!==undefined&&state.issue!==previous;
149
+ state.lastTypeDiagnostic=text;
150
+ if(!critical)state.issue=text;
151
+ if(!critical&&state.dismissedTypeDiagnostic!==text&&(!state.overlayError||state.overlayKind==='typescript'))showError(text);
152
+ else if(state.errorOverlay)syncErrorOverlay();
153
+ state.indicator?.update();
154
+ }
155
+ async function applyUpdate(message, {navigation=false}={}) {
156
+ if(message.session&&state.session&&message.session!==state.session){reload('Development server restarted. Page reloaded.');return;}
157
+ if(message.sequence&&message.sequence<=(state.sequence||0))return;
158
+ if (state.applying) {
159
+ if(!state.queued||(message.sequence||0)>=(state.queued.sequence||0))state.queued={...message,navigation};
160
+ const pending=state.applied;await pending;
161
+ if((message.sequence||0)>(state.sequence||0)&&state.applying)await state.applied;
162
+ return;
163
+ }
164
+ if (!message.clientManifest) { reload('Refresh metadata changed. Page reloaded.'); return; }
68
165
  const changed = message.changed || [];
69
- if (changed.some(file => /(?:^|\/)(?:(?:next|prnext)\.config\.|package(?:-lock)?\.json|\.env|pages\/_document\.)/.test(file))) { location.reload(); return; }
70
- state.applying = true; state.unsafe = false; state.pending = null;
166
+ if (changed.some(file => /(?:^|\/)(?:(?:next|prnext)\.config\.|package(?:-lock)?\.json|\.env|pages\/_document\.)/.test(file))) { reload('Project configuration changed. Page reloaded.'); return; }
167
+ // An initial dynamic import/bootstrap failure has no mounted application to
168
+ // update. Retry once on a newer successfully compiled build, not repeatedly
169
+ // on reconnects or readiness messages for the same failed generation.
170
+ if(state.bootstrapFailed&&message.buildId!==state.bootstrapFailed.buildId){
171
+ state.bootstrapFailed=undefined;
172
+ reload('Application startup failed. Page reloaded after a successful rebuild.');return;
173
+ }
174
+ const refreshStarted=performance.now();
175
+ const localPath=location.pathname.slice((state.basePath||'').length)||'/';
176
+ const affected=message.affectedRoutes;
177
+ if(state.confirmed&&affected&&!affected.some(pattern=>pattern.includes('[')||pattern===localPath||pattern+'/'===localPath)){
178
+ state.buildId=message.buildId;state.sequence=message.sequence;state.lastRefresh={buildId:message.buildId,durationMs:0,changed,skipped:true};
179
+ document.documentElement.dataset.prnextDevBuild=message.buildId;
180
+ if(hasCriticalIssue()&&state.issueKind==='build'){clearError();if(state.typeErrors)typeDiagnostics(state.typeErrors);}
181
+ state.indicator?.update();
182
+ globalThis.dispatchEvent(new CustomEvent('prnext:refresh',{detail:state.lastRefresh}));return;
183
+ }
184
+ state.applying = true; state.confirmed=false; state.unsafe = false; state.refreshSourceChanged = false; state.pending = null; state.indicator?.update();
185
+ let applied;state.applied=new Promise(resolve=>{applied=resolve;});
71
186
  state.dismissedErrors = new WeakSet();
72
187
  state.dismissedDigests = new Set();
73
188
  try {
@@ -79,51 +194,135 @@ async function applyUpdate(message) {
79
194
  const application = await state.applications.get(kind);
80
195
  const route = kind === 'pages' ? manifest.pages.find(route => route.pattern === application.router.snapshot().pathname) : null;
81
196
  const entry = kind === 'pages' ? route?.client : manifest.app?.client;
82
- if (!entry) { location.reload(); return; }
83
- await import(/* webpackIgnore: true */ entry);
197
+ if (!entry) { reload('The current route changed. Page reloaded.'); return; }
198
+ if(!message.cssOnly)await import(/* webpackIgnore: true */ entry);
84
199
  const pending = state.pending;
85
200
  if (pending) await application.devPrepare?.(pending.options);
86
- if (state.unsafe) { location.reload(); return; }
87
- await updateStyles(kind === 'pages' ? route.css : manifest.app.css);
88
- clearError();
89
- Refresh.performReactRefresh();
90
- if (pending) await application.devCommit?.(pending.options);
201
+ if (state.unsafe) { reload('An edited module exports values outside React components. Page reloaded.'); return; }
202
+ if (kind === 'app' && application.devPrepareStyles) await application.devPrepareStyles(manifest.app.routes);
203
+ else await updateStyles(kind === 'pages' ? route.css : application.devStyles?.(manifest.app.routes) || manifest.app.css);
204
+ if (kind === 'app') application.devStylesApplied?.(manifest.app.routes, { commit: !!message.cssOnly || !pending });
205
+ // An unrelated type-only edit may publish a new generation without changing
206
+ // any browser module. Keep its existing runtime/hydration issues and current
207
+ // selection until application code changes, rather than racing diagnostics.
208
+ if(state.refreshSourceChanged||navigation){
209
+ state.recoverableIssue=undefined;state.recoverableKey=undefined;
210
+ clearError();
211
+ }else if(hasCriticalIssue()&&(state.issueKind==='build'||errorKey(state.issue))){
212
+ clearError();
213
+ }
214
+ if(!message.cssOnly)Refresh.performReactRefresh();
215
+ if (pending) await (navigation ? application.devSync?.(pending.options) : application.devCommit?.(pending.options));
91
216
  state.buildId = message.buildId;
217
+ if(message.sequence)state.sequence=message.sequence;
92
218
  state.manifest = manifest;
93
219
  document.documentElement.dataset.prnextDevBuild = message.buildId;
94
- globalThis.dispatchEvent(new CustomEvent('prnext:refresh', { detail: { buildId: message.buildId } }));
220
+ state.lastRefresh={buildId:message.buildId,durationMs:Math.round(performance.now()-refreshStarted),changed};
221
+ await painted();state.confirmed=true;
222
+ globalThis.dispatchEvent(new CustomEvent('prnext:refresh', { detail: state.lastRefresh }));
223
+ globalThis.dispatchEvent(new CustomEvent('prnext:devtools:ready'));
224
+ if(state.typeErrors)typeDiagnostics(state.typeErrors);
95
225
  } catch (error) { showError(error); }
96
226
  finally {
97
- state.applying = false; state.pending = null;
98
- if (state.queued) { const queued = state.queued; state.queued = null; if (queued.buildId !== state.buildId) void applyUpdate(queued); }
227
+ state.applying = false; state.pending = null; state.indicator?.update();
228
+ applied();
229
+ if (state.queued) { const queued = state.queued; state.queued = null; if (queued.buildId !== state.buildId) void applyUpdate(queued,{navigation:queued.navigation}); }
99
230
  }
100
231
  }
101
232
 
102
233
  function connect(options) {
234
+ state.basePath=options.basePath||'';
103
235
  if (state.source) return;
104
236
  state.buildId = options.dev.buildId;
105
- const source = new EventSource(`${options.basePath || ''}/_prnext/dev`);
237
+ state.sequence=options.dev.sequence||0;state.session=options.dev.session;
238
+ state.clientId ||= crypto.randomUUID();
239
+ state.onDemand=options.dev.onDemand===true;
240
+ state.devConnection = 'connecting'; state.devBuildState = 'ready';
241
+ if (options.dev.indicator === false) { state.consoleCapture?.dispose(); state.consoleCapture = null; state.consoleDisabled = true; }
242
+ const snapshot=()=>({connection:state.devConnection,building:state.devBuildState==='building'||state.applying,confirmed:state.confirmed===true,router:state.currentKind,buildId:state.buildId,sequence:state.sequence,typeErrors:state.typeErrors?.length||0,issues:(state.typeErrors?.length||0)+(hasCriticalIssue()?1:0)+(state.recoverableIssue?1:0),criticalIssues:hasCriticalIssue()?1:0,lastRefresh:state.lastRefresh});
243
+ state.tools ||= createDevToolsClient({capture:state.consoleCapture,snapshot,config:options.dev.tools||options.devTools});
244
+ showReloadReason();
245
+ if (options.dev.indicator !== false && !isDevicePreview() && !state.indicator) state.indicator = createDevIndicator({
246
+ tools:state.tools,
247
+ position: options.dev.indicator?.position,
248
+ capture: state.consoleCapture,
249
+ snapshot: () => ({
250
+ connection: state.devConnection, building: state.devBuildState === 'building' || state.applying,
251
+ router: state.currentKind, buildId: state.buildId, sequence: state.sequence,
252
+ typeErrors: state.typeErrors?.length || 0,
253
+ issues: (state.typeErrors?.length || 0) + (hasCriticalIssue() ? 1 : 0) + (state.recoverableIssue ? 1 : 0),
254
+ criticalIssues: hasCriticalIssue() ? 1 : 0,
255
+ }),
256
+ showIssues() {
257
+ const critical=hasCriticalIssue();
258
+ const error = critical?state.issue:state.recoverableIssue||state.issue||state.lastTypeDiagnostic;
259
+ if (error===undefined||error===null) return;
260
+ if (typeof error === 'object') state.dismissedErrors?.delete(error);
261
+ const key = errorKey(error); if (key) state.dismissedDigests?.delete(key);
262
+ state.dismissedTypeDiagnostic = undefined; showError(error,{retain:error!==state.recoverableIssue});
263
+ state.errorOverlay?.focusDismiss();
264
+ },
265
+ });
266
+ if (isDevicePreview()) { try { parent.postMessage({type:'prnext:preview-ready'}, location.origin); } catch {} }
267
+ state.ensureRoute=async raw=>{
268
+ if(!state.onDemand)return;
269
+ const url=new URL(raw,location.href);if(url.origin!==location.origin)return;
270
+ state.ensuring=(state.ensuring||0)+1;
271
+ try{
272
+ const response=await fetch(`${options.basePath||''}/_prnext/ensure?client=${state.clientId}&path=${encodeURIComponent(url.pathname+url.search)}`,{cache:'no-store',credentials:'same-origin'});
273
+ if(!response.ok)throw Error(await response.text());
274
+ const update=await response.json();
275
+ if(update.buildId!==state.buildId)await applyUpdate(update,{navigation:true});
276
+ }finally{state.ensuring--;}
277
+ };
278
+ const source = new EventSource(`${options.basePath || ''}/_prnext/dev?client=${state.clientId}&path=${encodeURIComponent(location.pathname+location.search)}`);
106
279
  state.source = source;
280
+ source.onopen = () => { state.devConnection = 'connected'; state.indicator?.update(); };
281
+ source.onerror = () => { state.devConnection = 'reconnecting'; state.indicator?.update(); };
107
282
  source.onmessage = event => {
108
283
  let message; try { message = JSON.parse(event.data); } catch { return; }
284
+ if(message.session&&state.session&&message.session!==state.session){reload('Development server restarted. Page reloaded.');return;}
285
+ state.devBuildState = message.state; state.indicator?.update();
286
+ if(Array.isArray(message.typeErrors))typeDiagnostics(message.typeErrors);
109
287
  if (message.state === 'error') { showError(message.error || 'Build failed'); return; }
110
288
  if (message.state !== 'ready') return;
111
289
  if (message.buildId === state.buildId) return;
112
- void applyUpdate(message);
290
+ // A cold navigation may receive SSE readiness before its ensure response.
291
+ // Updating modules is safe; refreshing the old route would cancel the click.
292
+ // A retained branch can become cold after ensure() has returned. Its
293
+ // worker-triggered compilation is still part of the pending navigation;
294
+ // committing a refresh here would abort it and restore the previous URL.
295
+ void applyUpdate(message,{navigation:state.ensuring>0||message.reason?.code==='route-opened'});
113
296
  };
114
297
  globalThis.addEventListener('error', event => { if (event.error) showError(event.error); });
115
298
  globalThis.addEventListener('unhandledrejection', event => showError(event.reason));
116
- globalThis.addEventListener('pagehide', () => source.close(), { once: true });
299
+ globalThis.addEventListener('pagehide', () => {source.close();state.tools?.dispose();state.stopHydrationDiagnostics?.();}, { once: true });
117
300
  }
118
301
 
119
302
  state.reportError = showError;
303
+ state.reportRecoverableError = reportRecoverableError;
304
+ if(state.consoleCapture&&!state.stopHydrationDiagnostics){
305
+ let serial=0;
306
+ // Attribute-only hydration mismatches are reported by React through console,
307
+ // without invoking onRecoverableError. Match its diagnostic, not arbitrary logs.
308
+ state.stopHydrationDiagnostics=state.consoleCapture.subscribe(()=>{
309
+ for(const entry of state.consoleCapture?.snapshot().entries||[]){
310
+ if(entry.id<=serial)continue;serial=entry.id;
311
+ if((entry.level==='error'||entry.level==='warn')&&entry.text.includes('https://react.dev/link/hydration-mismatch')&&/Hydration failed|A tree hydrated/i.test(entry.text))reportRecoverableError(entry.text);
312
+ }
313
+ });
314
+ }
315
+ function painted(){return new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));}
120
316
  state.bootstrap = (kind, options, start) => {
121
317
  state.currentKind = kind;
122
318
  if (!state.applications.has(kind)) {
319
+ state.confirmed=false;
123
320
  const application = Promise.resolve().then(start);
124
321
  state.applications.set(kind, application);
125
322
  connect(options);
126
- application.catch(showError);
323
+ application.then(async current=>{await current.devReady;await painted();if(!state.applying){state.confirmed=true;globalThis.dispatchEvent(new CustomEvent('prnext:devtools:ready'));}},error=>{
324
+ state.bootstrapFailed={kind,buildId:options.dev.buildId};state.confirmed=false;showError(error);
325
+ }).catch(showError);
127
326
  } else if (state.applying) state.pending = { kind, options };
128
327
  return state.applications.get(kind);
129
328
  };
@@ -0,0 +1,42 @@
1
+ import http from 'node:http';
2
+ import {randomBytes,timingSafeEqual} from 'node:crypto';
3
+ import {readFile,realpath} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ // Private control plane. Request headers and bodies never cross this endpoint.
7
+ export async function createDevCompilerBroker({compile,maxPending=64}) {
8
+ const token=randomBytes(32).toString('base64url');let pending=0;
9
+ const server=http.createServer(async(req,res)=>{
10
+ const supplied=Buffer.from(req.headers.authorization||''),expected=Buffer.from('Bearer '+token);
11
+ if(req.method!=='POST'||req.url!=='/compile'||supplied.length!==expected.length||!timingSafeEqual(supplied,expected)){res.writeHead(403).end();return;}
12
+ if(pending>=maxPending){res.writeHead(503).end();return;}
13
+ pending++;
14
+ try{
15
+ const chunks=[];let bytes=0;
16
+ for await(const chunk of req){bytes+=chunk.length;if(bytes>65536)throw Error('Compiler request too large');chunks.push(chunk);}
17
+ // Decode once: an HTTP chunk boundary may split a UTF-8 route name.
18
+ const input=JSON.parse(Buffer.concat(chunks,bytes).toString('utf8'));if(typeof input.pattern!=='string'||!input.pattern.startsWith('/')||input.pattern.length>4096)throw Error('Invalid route pattern');
19
+ if(input.sources!==undefined&&(!Array.isArray(input.sources)||input.sources.length>32||!input.sources.every(p=>typeof p==='string'&&p.startsWith('/')&&p.length<=4096)))throw Error('Invalid retained route patterns');
20
+ const directory=await compile(input.pattern,input.sources||[]);
21
+ res.writeHead(200,{'content-type':'application/json','cache-control':'no-store'}).end(JSON.stringify({directory}));
22
+ }catch(error){res.writeHead(500,{'content-type':'application/json'}).end(JSON.stringify({error:String(error.message).slice(0,2048)}));}
23
+ finally{pending--;}
24
+ });
25
+ server.requestTimeout=125000;server.headersTimeout=5000;
26
+ await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(0,'127.0.0.1',resolve);});
27
+ return {descriptor:JSON.stringify({port:server.address().port,token}),close:()=>new Promise(resolve=>{server.close(resolve);server.closeIdleConnections();})};
28
+ }
29
+
30
+ export function deferredCompiler(descriptor,{root,home}) {
31
+ const value=JSON.parse(descriptor);
32
+ if(!Number.isInteger(value.port)||value.port<1||value.port>65535||!/^[\w-]{43}$/.test(value.token))throw Error('Invalid private compiler endpoint');
33
+ return async(pattern,signal,sources=[])=>{
34
+ const response=await fetch(`http://127.0.0.1:${value.port}/compile`,{method:'POST',headers:{authorization:'Bearer '+value.token,'content-type':'application/json'},body:JSON.stringify({pattern,sources}),signal:AbortSignal.any([...(signal?[signal]:[]),AbortSignal.timeout(120000)])});
35
+ const result=await response.json();if(!response.ok)throw Error(result.error||'Deferred route compilation failed');
36
+ const directory=await realpath(path.resolve(root,result.directory)),parent=await realpath(home),relative=path.relative(parent,directory);
37
+ if(!relative||relative.startsWith('..')||path.isAbsolute(relative))throw Error('Compiler generation escaped its development session');
38
+ const manifest=JSON.parse(await readFile(path.join(directory,'manifest.json'),'utf8'));
39
+ if(!manifest.dev||manifest.devSession!==path.basename(parent))throw Error('Invalid development generation');
40
+ return {distDir:directory,manifest};
41
+ };
42
+ }
@@ -0,0 +1,19 @@
1
+ export function consolePrompt(entries, {kind='logs',version,route='/',source='Page',dropped=0}={}) {
2
+ const groups = new Map();
3
+ const hasServer=entries.some(entry=>entry.source==='server');
4
+ for (const entry of entries) {
5
+ const key = (entry.source||'browser')+'\0'+entry.level + '\0' + entry.text;
6
+ const group = groups.get(key);
7
+ if (group) group.count++;
8
+ else groups.set(key,{entry,count:1});
9
+ }
10
+ const instruction = kind === 'errors' ? 'Fix all errors below.' : kind === 'message' ? `Investigate the ${hasServer?'server':'client'} message below and fix its root cause if it indicates a problem.` : `Review the ${hasServer?'development':'client'} logs below and fix the errors they reveal.`;
11
+ // The report deliberately omits URL query/fragment and browser/session data.
12
+ let path='/';try{path=new URL(route,'http://prnext.local').pathname;}catch{}
13
+ const header = [instruction,'','Context:',`- Framework: PRNext ${version || ''}`.trimEnd(),`- Route: ${path}`,`- Source: ${source} ${hasServer?'development console (browser and server)':'client console'}`,`- Captured messages: ${entries.length} (${groups.size} unique)`];
14
+ if(dropped)header.push('- Earlier messages were removed from the bounded console buffer.');
15
+ header.push('','Diagnostic messages:');
16
+ for (const {entry,count} of groups.values()) header.push('',`--- ${entry.level.toUpperCase()}${hasServer?' · '+(entry.source||'browser'):''}${count>1?` · ${count} occurrences`:''} ---`,entry.text);
17
+ header.push('','Find the root cause, make the smallest safe code changes, preserve intended behavior, and run the relevant checks.');
18
+ return header.join('\n');
19
+ }
@@ -0,0 +1,106 @@
1
+ // Browser-only diagnostics. Records stay in this window, never sent to PRNext.
2
+ // Bound both retained entries and formatting work; do not invoke object getters.
3
+ const LIMIT = 500, TEXT_LIMIT = 128_000, TEXT_BUDGET = 2 * 1024 * 1024;
4
+ const clip = text => text.length > TEXT_LIMIT ? text.slice(0,TEXT_LIMIT) + '\n[Message truncated at 128000 characters]' : text;
5
+ const nativeStackGetter = Object.getOwnPropertyDescriptor(new Error(), 'stack')?.get;
6
+ export function formatConsoleValue(value, depth = 0, seen = new WeakSet()) {
7
+ try {
8
+ if (typeof value === 'string') return clip(value);
9
+ if (typeof value === 'function') return '[Function]';
10
+ if (value === null || typeof value !== 'object') return clip(String(value));
11
+ if (seen.has(value)) return '[Circular]';
12
+ seen.add(value);
13
+ if (value instanceof Error) {
14
+ // Custom error properties can also be getters. Do not evaluate them.
15
+ const stack = Object.getOwnPropertyDescriptor(value, 'stack'), message = Object.getOwnPropertyDescriptor(value, 'message');
16
+ // V8 exposes its lazy stack as a shared native getter. Permit that getter,
17
+ // but never a custom accessor installed on a logged error object.
18
+ const nativeStack = nativeStackGetter && stack?.get === nativeStackGetter ? Reflect.apply(nativeStackGetter, value, []) : undefined;
19
+ const text = typeof nativeStack === 'string' ? nativeStack : stack && 'value' in stack && typeof stack.value === 'string' ? stack.value : message && 'value' in message && typeof message.value === 'string' ? message.value : '[Error]';
20
+ return clip(text);
21
+ }
22
+ if (depth >= 3) return Array.isArray(value) ? '[Array]' : '[Object]';
23
+ const keys = Object.keys(value), parts = [];
24
+ let remaining = TEXT_LIMIT;
25
+ for (const key of keys.slice(0, 20)) {
26
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
27
+ const text = descriptor && 'value' in descriptor ? formatConsoleValue(descriptor.value, depth + 1, seen) : '[Getter]';
28
+ const part = (Array.isArray(value) ? '' : key.slice(0, 100) + ': ') + text;
29
+ parts.push(part.slice(0, remaining)); remaining -= part.length;
30
+ if (remaining <= 0) break;
31
+ }
32
+ if (keys.length > parts.length) parts.push('…');
33
+ return clip(Array.isArray(value) ? '[' + parts.join(', ') + ']' : '{' + parts.join(', ') + '}');
34
+ } catch { return '[Unavailable]'; }
35
+ }
36
+
37
+ function formatArguments(args) {
38
+ const values = args.slice(0, 20);
39
+ if (typeof values[0] !== 'string') return clip(values.map(value => formatConsoleValue(value)).join(' '));
40
+ let index = 1;
41
+ const message = clip(values[0]).replace(/%[%sdifoOc]/g, token => {
42
+ if (token === '%%') return '%';
43
+ if (index >= values.length) return token;
44
+ const value = values[index++];
45
+ if (token === '%c') return '';
46
+ // Never coerce user objects through custom toString/valueOf methods.
47
+ if (/^%[dif]$/.test(token) && (typeof value === 'string' || typeof value === 'number')) return String(token === '%f' ? parseFloat(value) : parseInt(value, 10));
48
+ return formatConsoleValue(value);
49
+ });
50
+ return clip([message, ...values.slice(index).map(value => formatConsoleValue(value))].join(' '));
51
+ }
52
+
53
+ export function createConsoleCapture(target = globalThis) {
54
+ const entries = [], subscribers = new Set(), replacements = [];
55
+ let serial = 0, busy = false, scheduled = false, disposed = false, dropped = 0, retainedChars = 0;
56
+ function notify() {
57
+ if (scheduled || disposed) return;
58
+ scheduled = true;
59
+ queueMicrotask(() => {
60
+ scheduled = false;
61
+ if (!disposed) for (const subscriber of subscribers) { try { subscriber(); } catch {} }
62
+ });
63
+ }
64
+ function record(level, args) {
65
+ if (busy || disposed) return;
66
+ busy = true;
67
+ try {
68
+ const text=formatArguments(args);entries.push({id:++serial,time:Date.now(),level,text});retainedChars+=text.length;
69
+ while(entries.length>LIMIT||retainedChars>TEXT_BUDGET){retainedChars-=entries.shift().text.length;dropped++;}
70
+ notify();
71
+ } catch {} finally { busy = false; }
72
+ }
73
+ function clear() { entries.length = 0; dropped = 0;retainedChars=0;notify(); }
74
+ for (const method of ['log', 'info', 'warn', 'error', 'debug', 'table', 'trace', 'assert', 'clear']) {
75
+ const original = target.console?.[method];
76
+ if (typeof original !== 'function') continue;
77
+ const replacement = function (...args) {
78
+ // Keep the native console, return value and exception behavior intact.
79
+ try { return Reflect.apply(original, target.console, args); }
80
+ finally {
81
+ if (method === 'clear') clear();
82
+ else if (method === 'assert') { if (!args[0]) record('error', ['Assertion failed:', ...args.slice(1)]); }
83
+ else record(method === 'table' || method === 'trace' ? 'log' : method, args);
84
+ }
85
+ };
86
+ try { target.console[method] = replacement; replacements.push({method, original, replacement}); } catch {}
87
+ }
88
+ const onError = event => { if (event.error || event.message) record('error', [event.error || event.message]); };
89
+ const onRejection = event => record('error', ['Unhandled rejection:', event.reason]);
90
+ target.addEventListener('error', onError);
91
+ target.addEventListener('unhandledrejection', onRejection);
92
+ return {
93
+ snapshot: () => ({ entries: entries.slice(), dropped }), clear,
94
+ subscribe(callback) { subscribers.add(callback); return () => subscribers.delete(callback); },
95
+ dispose() {
96
+ disposed = true; subscribers.clear(); entries.length = 0;
97
+ for (const {method, original, replacement} of replacements) if (target.console[method] === replacement) target.console[method] = original;
98
+ target.removeEventListener('error', onError); target.removeEventListener('unhandledrejection', onRejection);
99
+ },
100
+ };
101
+ }
102
+
103
+ export function isDevicePreview() {
104
+ try { return globalThis.frameElement?.getAttribute('data-prnext-preview') === 'true'; }
105
+ catch { return false; }
106
+ }
@@ -0,0 +1,40 @@
1
+ // Keep inherited-stdio RPC frames below the native listener's 64 KiB limit.
2
+ const FRAME_BYTES=48*1024,MAX_FILES=100000,MAX_RECORDS=50000,MAX_EDGES=1000000,MAX_BYTES=16*1024*1024;
3
+ function batches(values){
4
+ const result=[];let batch=[],bytes=2;
5
+ for(const value of values){const size=Buffer.byteLength(JSON.stringify(value))+1;if(size>FRAME_BYTES)throw Error('Development graph entry exceeds the control frame budget');if(bytes+size>FRAME_BYTES){result.push(batch);batch=[];bytes=2;}batch.push(value);bytes+=size;}
6
+ if(batch.length)result.push(batch);return result;
7
+ }
8
+ export function compactDependencyGraph(manifest){
9
+ if(manifest.devDependencyStatus?.complete===false)throw Error(manifest.devDependencyStatus.reason||'Incomplete development graph');
10
+ const files=[],ids=new Map(),records=[];let edges=0,bytes=0;
11
+ const intern=file=>{if(typeof file!=='string'||!file||Buffer.byteLength(file)>8192)throw Error('Invalid development dependency');if(!ids.has(file)){bytes+=Buffer.byteLength(file);ids.set(file,files.length);files.push(file);}return ids.get(file);};
12
+ for(const record of [...manifest.devDependencies||[],...manifest.devModules||[]]){
13
+ const entries=[...new Set(record.files)].map(intern);edges+=entries.length;bytes+=Buffer.byteLength(record.route)+entries.length*4;
14
+ // Split a single very broad route too; the native builder merges its parts.
15
+ for(let offset=0;offset<Math.max(entries.length,1);offset+=4000)records.push({route:record.route,module:!!record.module,files:entries.slice(offset,offset+4000)});
16
+ if(files.length>MAX_FILES||records.length>MAX_RECORDS||edges>MAX_EDGES||bytes>MAX_BYTES)throw Error('Development graph exceeds its bounded file, edge or memory budget');
17
+ }
18
+ return {files,records,edges};
19
+ }
20
+ export async function publishDependencyGraph(gateway,manifest){
21
+ const graph=compactDependencyGraph(manifest),transaction=String(manifest.buildId);
22
+ await gateway.request('graph-begin',{transaction,files:graph.files.length,records:graph.records.length,edges:graph.edges});
23
+ try{
24
+ for(const files of batches(graph.files))await gateway.request('graph-files',{transaction,files});
25
+ for(const records of batches(graph.records))await gateway.request('graph-records',{transaction,records});
26
+ return await gateway.request('graph-commit',{transaction});
27
+ }catch(error){await gateway.request('graph-abort',{transaction}).catch(()=>{});throw error;}
28
+ }
29
+ export async function affectedDependencies(gateway,files){
30
+ const routes=new Set(),modules=new Set(),unknown=new Set();let complete=true;
31
+ for(const batch of batches(files).flatMap(batch=>{const chunks=[];for(let i=0;i<batch.length;i+=512)chunks.push(batch.slice(i,i+512));return chunks;})){
32
+ const result=await gateway.request('affected',{files:batch,transitive:true});
33
+ complete&&=result.complete!==false;
34
+ for(const value of result.routes||[])routes.add(value);for(const value of result.modules||[])modules.add(value);for(const value of result.unknown||[])unknown.add(value);
35
+ }
36
+ // HMR compiler messages and frontend notifications also have finite budgets.
37
+ if(Buffer.byteLength(JSON.stringify([...routes]))>32*1024||Buffer.byteLength(JSON.stringify([...modules]))>256*1024)complete=false;
38
+ return {routes:complete?[...routes]:[],modules:complete?[...modules]:[],unknown:[...unknown],complete};
39
+ }
40
+ export const canSkipUnopenedRoutes=(result,files,dormant,indexValid)=>indexValid&&result.complete!==false&&files.length>0&&result.unknown.length===files.length&&result.unknown.every(file=>dormant.has(file));
@@ -0,0 +1,27 @@
1
+ // This chrome lives in its own shadow root and never changes application styles.
2
+ export const elementPickerStyle = `
3
+ :host{all:initial;position:fixed;inset:0;overflow:hidden;pointer-events:none;z-index:2147483647;color-scheme:dark;color:#ededed;font:12px/1.4 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;text-align:left;direction:ltr}
4
+ *,*::before,*::after{box-sizing:border-box}[hidden]{display:none!important}svg{display:block;width:16px;height:16px;flex:none}
5
+ .target{position:fixed;left:0;top:0;outline:1px dashed #e8e5ee;outline-offset:0;box-shadow:0 0 0 1px #17141b80;background:transparent;pointer-events:none}
6
+ .corner{position:absolute;width:5px;height:5px;background:#f4f2f8;box-shadow:0 0 0 1px #17141b;pointer-events:none}
7
+ .corner[data-corner=tl]{left:-3px;top:-3px}.corner[data-corner=t]{left:calc(50% - 2.5px);top:-3px}.corner[data-corner=tr]{right:-3px;top:-3px}.corner[data-corner=r]{right:-3px;top:calc(50% - 2.5px)}
8
+ .corner[data-corner=bl]{left:-3px;bottom:-3px}.corner[data-corner=b]{left:calc(50% - 2.5px);bottom:-3px}.corner[data-corner=br]{right:-3px;bottom:-3px}.corner[data-corner=l]{left:-3px;top:calc(50% - 2.5px)}
9
+ .picker-tag{position:fixed;left:0;top:0;width:max-content;max-width:min(280px,calc(100vw - 16px));padding:4px 6px;border-radius:5px;background:#101014ed;pointer-events:none}
10
+ .picker-tag-top{display:flex;align-items:center;min-width:0}
11
+ .picker-dimensions{color:#b6b2bd;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;font-variant-numeric:tabular-nums;flex:none}
12
+ .picker-source{display:flex;align-items:center;gap:6px;margin-top:4px;color:#f0edf5;font:500 11px/16px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;min-width:0}
13
+ .picker-source-name{min-width:0;overflow:hidden;text-overflow:ellipsis}.picker-source-line{flex:none;margin-left:-6px;color:#b6b2bd;font-variant-numeric:tabular-nums}
14
+ .picker-source-parent{flex:none;color:#a6a1ae;font:10px/16px ui-sans-serif,system-ui}
15
+ .picker-file-icon{display:grid;place-items:center;flex:none;width:15px;height:15px;border-radius:2px;color:#dad6e2;font:800 9px/1 ui-sans-serif,system-ui;letter-spacing:-.5px}.picker-file-icon svg{width:15px;height:15px}
16
+ .picker-file-icon[data-language=javascript]{background:#f7df1e;color:#17150b}.picker-file-icon[data-language=typescript]{background:#3178c6;color:#fff}.picker-file-icon[data-language=html]{background:#e34f26;color:#fff}.picker-file-icon[data-language=css]{background:#7956b5;color:#fff;font-size:12px}.picker-file-icon[data-language=json]{color:#f7df1e}.picker-file-icon[data-language=vue]{color:#60c59a}.picker-file-icon[data-language=svelte]{color:#ff7c55}.picker-file-icon[data-language=python]{color:#80b9ef}.picker-file-icon[data-language=rust]{color:#ebaa8e}
17
+ .hint{position:fixed;left:50%;bottom:max(16px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;align-items:center;gap:10px;width:max-content;max-width:calc(100vw - 24px);padding:6px 6px 6px 14px;border:0;border-radius:22px;background:#080808;box-shadow:0 3px 8px #0006,0 12px 32px #0007;pointer-events:auto}
18
+ .picker-instruction{font-size:12px;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
19
+ .picker-enter{margin-left:4px}.hint kbd{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 4px;border:1px solid #343238;border-radius:5px;color:#939098;background:transparent;font:10px/1 ui-sans-serif,system-ui}
20
+ .picker-cancel{display:flex;align-items:center;justify-content:center;gap:9px;min-height:34px;padding:6px 10px;border:0;border-radius:15px;background:#191919;color:#ededed;font:500 11px/1.4 ui-sans-serif,system-ui;cursor:pointer;white-space:nowrap;flex:none;-webkit-tap-highlight-color:transparent}
21
+ .picker-cancel:hover{background:#252525}.picker-cancel:focus-visible{outline:1px solid #aaa;outline-offset:-3px;background:#252525}
22
+ .sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
23
+ @media(max-width:350px){.hint{gap:8px}.hint .picker-enter{display:none}.picker-tag{max-width:calc(100vw - 16px)}}
24
+ @media(pointer:coarse){.picker-cancel{min-width:64px;min-height:44px;padding-inline:12px}.picker-cancel kbd,.hint .picker-enter{display:none}.hint{border-radius:28px}.picker-cancel{border-radius:21px}}
25
+ @media(prefers-reduced-motion:no-preference){.picker-cancel{transition:background-color 120ms}.target,.picker-tag{animation:picker-mark-in 110ms ease-out}.target[data-motion=true]{transition:translate 110ms cubic-bezier(.22,.8,.2,1),width 110ms cubic-bezier(.22,.8,.2,1),height 110ms cubic-bezier(.22,.8,.2,1)}.picker-tag[data-motion=true]{transition:translate 110ms cubic-bezier(.22,.8,.2,1)}.hint{animation:picker-bar-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes picker-mark-in{from{opacity:0}to{opacity:1}}@keyframes picker-bar-in{from{opacity:0;translate:0 6px}to{opacity:1;translate:0 0}}}
26
+ @media(forced-colors:active){.target{outline-color:Highlight;box-shadow:none;background:transparent}.corner{background:Highlight;box-shadow:0 0 0 1px Canvas}.picker-tag,.hint{background:Canvas;color:CanvasText;border:1px solid ButtonText;box-shadow:none}.picker-dimensions,.picker-source,.picker-source-line,.picker-source-parent{color:CanvasText}.picker-file-icon[data-language]{color:CanvasText;background:Canvas}.picker-cancel{background:ButtonFace;color:ButtonText;border:1px solid ButtonText}.picker-cancel:focus-visible{outline:2px solid Highlight;outline-offset:-3px}.hint kbd{color:ButtonText;border-color:ButtonText}}
27
+ `;