@thomas.f/prnext 0.1.15-alpha → 0.1.17-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 (126) hide show
  1. package/AI.md +63 -0
  2. package/README.md +387 -12
  3. package/build/ADAPTERS.md +27 -2
  4. package/build/actions.mjs +5 -0
  5. package/build/analyze.mjs +184 -0
  6. package/build/app-config.mjs +7 -8
  7. package/build/app-static.mjs +12 -7
  8. package/build/app.mjs +23 -10
  9. package/build/build-lock.mjs +6 -6
  10. package/build/bundle-runtime.mjs +20 -5
  11. package/build/check.mjs +56 -11
  12. package/build/compiler.mjs +25 -1
  13. package/build/concurrent.mjs +12 -0
  14. package/build/config.mjs +21 -13
  15. package/build/css-modules.mjs +15 -4
  16. package/build/dev-compiler-health.mjs +11 -0
  17. package/build/dev-css-update.mjs +83 -0
  18. package/build/dev-dependencies.mjs +44 -0
  19. package/build/dev-discovery.mjs +28 -0
  20. package/build/dev-worker.mjs +14 -5
  21. package/build/dev.mjs +55 -4
  22. package/build/edge.mjs +3 -2
  23. package/build/framework-imports.mjs +40 -0
  24. package/build/incremental.mjs +8 -4
  25. package/build/index.mjs +96 -101
  26. package/build/inspect.mjs +3 -0
  27. package/build/inspection-summary.mjs +37 -4
  28. package/build/loader-cache.mjs +53 -26
  29. package/build/metadata-routes.mjs +2 -3
  30. package/build/migrate.mjs +60 -57
  31. package/build/npm-packages.mjs +3 -18
  32. package/build/package-manager.mjs +308 -0
  33. package/build/persistent-cache.mjs +97 -0
  34. package/build/precompress.mjs +62 -30
  35. package/build/prerender-pages.mjs +90 -0
  36. package/build/prerender-worker.mjs +10 -0
  37. package/build/production-builder.mjs +80 -0
  38. package/build/scan.mjs +4 -4
  39. package/build/source-analysis.mjs +42 -0
  40. package/build/static-adapter.mjs +13 -5
  41. package/build/static-input.mjs +48 -0
  42. package/build/styles.mjs +15 -3
  43. package/build/transform-cache.mjs +37 -4
  44. package/build/type-command.mjs +41 -0
  45. package/build/typed-routes.mjs +91 -0
  46. package/build/typescript-dev-worker.mjs +18 -0
  47. package/build/typescript-dev.mjs +22 -0
  48. package/build/typescript-watch.mjs +22 -0
  49. package/build/typescript.mjs +5 -4
  50. package/build/webpack-graph.mjs +164 -27
  51. package/build/worker-preload.mjs +9 -0
  52. package/cli.mjs +105 -121
  53. package/compat/index.d.ts +21 -7
  54. package/compat/link.d.cts +4 -3
  55. package/compat/navigation.d.cts +4 -3
  56. package/compat/router.d.cts +4 -3
  57. package/compat/typed-route.d.cts +17 -0
  58. package/docs/ai/compatibility.md +106 -0
  59. package/docs/ai/data.md +128 -0
  60. package/docs/ai/deployment.md +111 -0
  61. package/docs/ai/manifest.json +11 -0
  62. package/docs/ai/quickstart.md +146 -0
  63. package/docs/ai/routing.md +132 -0
  64. package/docs/ai/troubleshooting.md +120 -0
  65. package/index.mjs +2 -0
  66. package/native/diagnostics.mjs +34 -0
  67. package/native/resolve.mjs +7 -2
  68. package/package.json +28 -10
  69. package/release/ai-docs.mjs +48 -0
  70. package/release/verify.mjs +6 -0
  71. package/runtime/ai-docs.mjs +202 -0
  72. package/runtime/app-client.mjs +17 -6
  73. package/runtime/app-render.mjs +12 -0
  74. package/runtime/cloud/archive.mjs +5 -6
  75. package/runtime/compile-cache.mjs +8 -0
  76. package/runtime/dev-ai-brands.mjs +26 -0
  77. package/runtime/dev-ai-style.mjs +99 -0
  78. package/runtime/dev-build-reasons.mjs +25 -0
  79. package/runtime/dev-cache-control.mjs +53 -0
  80. package/runtime/dev-client.mjs +239 -45
  81. package/runtime/dev-console-copy.mjs +19 -0
  82. package/runtime/dev-console.mjs +106 -0
  83. package/runtime/dev-dependency-graph.mjs +40 -0
  84. package/runtime/dev-element-picker-style.mjs +27 -0
  85. package/runtime/dev-element-picker.mjs +213 -0
  86. package/runtime/dev-error-overlay-style.mjs +41 -0
  87. package/runtime/dev-error-overlay.mjs +167 -0
  88. package/runtime/dev-error-report.mjs +121 -0
  89. package/runtime/dev-file-changes.mjs +1 -1
  90. package/runtime/dev-gateway.mjs +44 -0
  91. package/runtime/dev-host-relay.mjs +66 -0
  92. package/runtime/dev-indicator.mjs +186 -0
  93. package/runtime/dev-issue-motion.mjs +214 -0
  94. package/runtime/dev-lazy-tool.mjs +18 -0
  95. package/runtime/dev-logo.mjs +2 -0
  96. package/runtime/dev-notification.mjs +81 -0
  97. package/runtime/dev-popout.mjs +34 -0
  98. package/runtime/dev-position.mjs +107 -0
  99. package/runtime/dev-preview-touch.mjs +122 -0
  100. package/runtime/dev-runtime-pool.mjs +45 -0
  101. package/runtime/dev-runtime-spare.mjs +57 -0
  102. package/runtime/dev-tools-client.mjs +155 -0
  103. package/runtime/dev-tools-mcp.mjs +126 -0
  104. package/runtime/dev-tools-metrics.mjs +31 -0
  105. package/runtime/dev-tools-model.mjs +42 -0
  106. package/runtime/dev-tools-panel.mjs +341 -0
  107. package/runtime/dev-tools-result.mjs +37 -0
  108. package/runtime/dev-tools-server.mjs +256 -0
  109. package/runtime/dev-tools-source.mjs +104 -0
  110. package/runtime/dev-tooltip.mjs +94 -0
  111. package/runtime/dev-ui-icons.mjs +8 -0
  112. package/runtime/dev-workbench-style.mjs +106 -0
  113. package/runtime/dev-workbench.mjs +428 -0
  114. package/runtime/dev-worker-logs.mjs +31 -0
  115. package/runtime/development.mjs +307 -0
  116. package/runtime/env.mjs +2 -1
  117. package/runtime/pages-client.mjs +10 -1
  118. package/runtime/pages-navigation.mjs +6 -1
  119. package/runtime/persistent/client.mjs +6 -5
  120. package/runtime/persistent/daemon.mjs +66 -56
  121. package/runtime/persistent/native-proxy.mjs +63 -0
  122. package/runtime/preload.mjs +12 -0
  123. package/runtime/rsc-worker.mjs +7 -0
  124. package/runtime/typed-route.mjs +19 -0
  125. package/runtime/watch-project.mjs +12 -5
  126. package/runtime/worker.mjs +36 -2
@@ -0,0 +1,99 @@
1
+ // Development-only inspector surfaces. No styles escape the tools' shadow root.
2
+ export const devAiStyle = `
3
+ dialog.wb-tools[data-panel=ai]{width:min(420px,calc(100vw - 32px));height:fit-content;max-height:calc(100dvh - 48px)}
4
+ dialog.wb-tools[data-panel=ai][data-selection=true]{width:min(560px,calc(100vw - 32px))}
5
+ [data-panel=ai] .wb-tools-content{padding:48px 20px 20px;flex:0 1 auto;max-height:calc(100dvh - 48px);overscroll-behavior:contain}
6
+ [data-panel=ai] .wb-head{display:contents}
7
+ [data-panel=ai] .wb-head>:not(.wb-close),[data-panel=ai] .wb-tools-nav,[data-panel=ai] .wb-tools-search,[data-panel=ai] .wb-ai-hero{display:none}
8
+ [data-panel=ai] .wb-close{position:absolute;top:8px;right:8px;z-index:2;width:34px;min-height:34px;background:#080808;color:#999}
9
+ [data-panel=ai] .wb-close:hover{color:#fff;background:#191919}
10
+ [data-panel=ai] .wb-tools-feedback{padding:0 20px 16px;font-size:11px;overflow-wrap:anywhere}
11
+ [data-panel=ai] [hidden]{display:none!important}
12
+ .wb-ai-layout{display:block;width:100%;margin:0 auto;min-width:0}
13
+ .wb-ai-layout>*{min-width:0}.wb-ai-has-selection{max-width:none}
14
+ .wb-ai-connect{min-width:0}
15
+ .wb-ai-connect>.wb-host-error{margin:8px 0 0;font-size:11px;line-height:1.5;overflow-wrap:anywhere}
16
+ .wb-ai-hero{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:5px 10px;margin-bottom:16px}
17
+ .wb-ai-heading{margin:0;font-size:14px;font-weight:550;letter-spacing:-.02em;line-height:1.4}
18
+ .wb-ai-description{grid-column:1/-1;color:#999;font-size:11px;line-height:1.6;margin:0}
19
+ .wb-ai-status{display:inline-flex;align-items:center;gap:6px;grid-column:2;grid-row:1;font-size:10px;white-space:nowrap;color:#999}
20
+ .wb-ai-status i{width:5px;height:5px;border-radius:50%;background:#777;flex:none}
21
+ .wb-ai-status[data-connected=true]{color:#a8dabd}.wb-ai-status[data-connected=true] i{background:#87d5a8}
22
+ .wb-ai-layout[data-connected=true] .wb-ai-hero{margin-bottom:0}
23
+ .wb-ai-setup{margin:0;min-width:0}
24
+ .wb-ai-layout[data-connected=true] .wb-ai-setup{margin-top:0}
25
+ .wb-ai-setup>summary{display:flex;align-items:center;gap:8px;min-height:32px;list-style:none;color:#8e8e8e;font-size:10px;cursor:pointer;user-select:none}
26
+ .wb-ai-setup>summary::-webkit-details-marker{display:none}
27
+ .wb-ai-setup>summary::after{content:'';width:5px;height:5px;margin-left:auto;border-right:1px solid currentColor;border-bottom:1px solid currentColor;rotate:45deg;translate:0 -2px}
28
+ .wb-ai-setup[open]>summary::after{rotate:225deg;translate:0 1px}
29
+ .wb-ai-setup>summary:hover{color:#ddd}
30
+ .wb-ai-setup-content{min-width:0}
31
+ .wb-ai-layout[data-connected=true] .wb-ai-setup-content{padding-top:0}
32
+ .wb-ai-workspace{min-width:0;margin:0;padding:0;border:0}
33
+ .wb-ai-ready{display:flex;flex-direction:column;align-items:center;gap:10px;padding:12px 0 8px;text-align:center}
34
+ .wb-ai-ready p{max-width:340px;margin:0;color:#888;font-size:11px;line-height:1.6;text-wrap:pretty}
35
+ .wb .wb-ai-select{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:8px 13px;border-radius:8px;background:#ededed;color:#0c0c0c;font-size:11px;font-weight:550}
36
+ .wb .wb-ai-select:hover:not(:disabled){background:#d6d6d6;color:#080808}.wb-ai-select svg{width:15px;height:15px}
37
+ .wb-host-connect{padding:0;background:transparent}
38
+ .wb-host-heading{display:flex;align-items:center;gap:7px;min-width:0;font-size:11px}.wb-host-heading svg{width:14px;height:14px;color:#999}
39
+ .wb-host-heading strong{font-weight:500}.wb-host-state{display:none}
40
+ .wb-host-command{display:flex;align-items:center;gap:8px;margin-top:8px;min-width:0;background:#111;border-radius:8px;padding:3px 3px 3px 10px}
41
+ .wb-host-command code{min-width:0;flex:1;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace;color:#ddd}
42
+ .wb .wb-host-copy{display:grid;place-items:center;flex:none;width:34px;padding:6px;color:#999}.wb-host-copy svg{width:14px;height:14px}
43
+ .wb-host-note{color:#888;font-size:10px;line-height:1.6;margin:6px 0 0}
44
+ .wb-ai-divider{margin:16px 0 8px;color:#888;font-size:10px}
45
+ .wb-ai-clients{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:2px;padding:3px;border-radius:10px;background:#111}
46
+ .wb .wb-ai-client{display:flex;align-items:center;justify-content:center;gap:7px;min-width:0;min-height:40px;padding:8px 6px;border:0;border-radius:7px;background:transparent;color:#aaa}
47
+ .wb .wb-ai-client:hover:not(:disabled){background:#1b1b1b;color:#fff}
48
+ .wb .wb-ai-client[aria-pressed=true]{background:#262626;color:#fff}
49
+ .wb-ai-client-logo{display:grid;place-items:center;width:20px;height:20px}.wb-ai-client-logo svg{width:20px;height:20px}
50
+ .wb-ai-client-label{font-size:11px;font-weight:500}.wb-ai-client-check{display:none}
51
+ .wb-ai-client:disabled{opacity:.5;cursor:default}
52
+ .wb .wb-ai-other{display:flex;align-items:center;justify-content:center;gap:6px;margin:4px 0 0 auto;min-height:28px;padding:4px 7px;color:#888;font-size:10px}
53
+ .wb-ai-other svg{width:13px;height:13px}.wb-ai-other[aria-pressed=true]{color:#eee;background:#191919}
54
+ .wb-local-setup{margin-top:8px;padding:0}
55
+ .wb-local-setup summary{display:flex;align-items:center;gap:8px;list-style:none;min-height:28px;color:#aaa;font-size:11px;cursor:pointer}
56
+ .wb-local-setup summary::-webkit-details-marker{display:none}.wb-local-setup summary::after{content:'';width:5px;height:5px;margin-left:auto;border-right:1px solid currentColor;border-bottom:1px solid currentColor;rotate:45deg;translate:0 -2px}
57
+ .wb-local-setup[open] summary::after{rotate:225deg;translate:0 1px}
58
+ .wb-mcp-reveal,.wb-mcp-help-reveal{height:0;min-width:0;overflow:hidden;opacity:0}
59
+ .wb-mcp-reveal[data-expanded=true],.wb-mcp-help-reveal[data-expanded=true]{opacity:1}
60
+ .wb-mcp-content{min-width:0;padding-top:8px;opacity:1}.wb-mcp-content[data-swapping=true]{opacity:.35}
61
+ .wb-local-setup .wb-mcp-controls{justify-content:space-between;align-items:center;flex-wrap:nowrap;gap:8px;margin:0;min-width:0}.wb-mcp-format{font-size:10px;color:#999}
62
+ .wb .wb-mcp-help-toggle{display:grid;place-items:center;flex:none;width:28px;min-height:28px;padding:0;border-radius:7px;color:#999;background:transparent;font-size:11px}
63
+ .wb .wb-mcp-help-toggle:hover,.wb .wb-mcp-help-toggle[aria-expanded=true]{background:#191919;color:#ddd}.wb-mcp-help-toggle svg{width:14px;height:14px}
64
+ .wb-mcp-help{padding:7px 0 1px;color:#999;font-size:10px;line-height:1.6;overflow-wrap:anywhere}.wb-mcp-help p{margin:0}.wb-mcp-help a{display:inline-flex;align-items:center;gap:4px;min-height:28px;color:#c3b9f1;text-decoration:none;text-underline-offset:3px}.wb-mcp-help a:hover{text-decoration:underline}
65
+ .wb-mcp-snippet{display:grid;grid-template-columns:minmax(0,1fr) 34px;align-items:center;gap:6px;min-width:0;margin-top:8px}
66
+ .wb .wb-config-copy{display:grid;place-items:center;align-self:center;width:34px;height:34px;min-height:34px;padding:0;border-radius:8px;color:#999;background:transparent}.wb .wb-config-copy:hover{color:#eee;background:#191919}.wb-config-copy svg{width:14px;height:14px}
67
+ .wb-local-setup pre.wb-mcp-code{width:100%;height:83px;min-height:83px;max-height:83px;margin:0;padding:10px;overflow:auto;overscroll-behavior:contain;white-space:pre-wrap;overflow-wrap:anywhere;tab-size:2;font:10px/18px ui-monospace,SFMono-Regular,Consolas,monospace;color:#bcbcbc;background:#101010;border-radius:8px;scrollbar-width:thin;scrollbar-color:#333 #101010}
68
+ .wb-component-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:15px}.wb-component-identity{display:flex;align-items:center;gap:10px;min-width:0}.wb-component-identity>div{min-width:0}
69
+ .wb-component-mark{display:grid;place-items:center;width:36px;height:36px;border-radius:11px;background:#b7c3ff12;color:#c4ccff;flex:none}.wb-component-mark svg{width:20px;height:20px}
70
+ .wb-component h3{margin:0 0 3px;font-size:14px;letter-spacing:-.02em;font-weight:550;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
71
+ .wb-component-kind{display:block;font:10px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;color:#929292;font-variant-numeric:tabular-nums}
72
+ .wb .wb-component-reselect{display:flex;align-items:center;gap:6px;flex:none;padding:6px 8px;color:#999;font-size:10px}.wb-component-reselect svg{width:14px;height:14px}
73
+ .wb-component-visual{display:grid;place-items:center;position:relative;min-height:135px;max-height:190px;overflow:auto;isolation:isolate;border-radius:12px;background:#101010;box-shadow:inset 0 0 0 1px #ffffff08;padding:20px}
74
+ .wb-component-visual .wb-selection-image{display:block;max-width:100%;max-height:150px;object-fit:contain;object-position:center;margin:0;border-radius:4px}
75
+ .wb-component-visual [hidden]{display:none!important}.wb-component-placeholder{font-size:11px;color:#888;text-align:center;overflow-wrap:anywhere}
76
+ .wb-component-visual[data-state=error]{place-items:start;background:#e14a580b;color:#ffa2ac;min-height:90px}.wb-component-visual[data-state=error] .wb-component-placeholder{color:#ffa2ac;text-align:left;font:11px/1.65 ui-monospace,monospace}
77
+ .wb-component-source{display:flex;align-items:center;gap:10px;min-width:0;margin:11px 0 18px}.wb-component-location{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#909090;font:10px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace}
78
+ .wb-component-editor{display:inline-flex;align-items:center;min-height:28px;text-decoration:none;color:#aaa;font-size:10px;white-space:nowrap}.wb-component-editor:hover{color:#eee}.wb-component-editor[hidden]{display:none}
79
+ .wb-component-composer{border:1px solid #303030;border-radius:12px;overflow:hidden;background:#0c0c0c}
80
+ .wb-component-label{display:flex;flex-direction:column;gap:9px;padding:13px 13px 0;font-size:12px;color:#ccc;font-weight:500}
81
+ .wb-component-label textarea{display:block;width:100%;min-height:78px;max-height:200px;resize:vertical;margin:0;padding:0 1px;border:0;border-radius:0;background:transparent;color:#eee;font:12px/1.65 ui-sans-serif,system-ui;outline-offset:3px;scrollbar-color:#333 #0c0c0c}
82
+ .wb-component-label textarea::placeholder{color:#666}.wb-component-label textarea:focus-visible{outline:0}
83
+ .wb-component-composer:focus-within{border-color:#777;box-shadow:0 0 0 1px #777}
84
+ .wb-component-compose-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;padding:10px}.wb-component-parts{font-size:9px;color:#888;overflow-wrap:anywhere}
85
+ .wb .wb-ai-primary{display:inline-flex;align-items:center;justify-content:center;gap:10px;min-height:34px;padding:7px 11px;border-radius:8px;background:#ededed;color:#0c0c0c;font-size:11px;font-weight:600}
86
+ .wb .wb-ai-primary:hover:not(:disabled){background:#d6d6d6;color:#080808}.wb-ai-primary svg{width:14px;height:14px}.wb .wb-ai-primary:disabled{opacity:.45;cursor:wait}
87
+ .wb-component-details{margin-top:14px}.wb-component-details summary{color:#888;font-size:10px;cursor:pointer;min-height:28px;padding:4px 0}.wb-component-details pre{max-height:180px;overflow:auto;color:#aaa;background:#111;padding:12px;border-radius:9px;margin:8px 0;scrollbar-color:#333 #111}
88
+ .wb-component-styles{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:5px 14px;font:10px/1.5 ui-monospace,monospace;color:#aaa;margin:12px 0}
89
+ .wb-component-styles dt{color:#777;min-width:0;overflow-wrap:anywhere}.wb-component-styles dd{margin:0;min-width:0;overflow-wrap:anywhere;text-align:right}
90
+ .wb-ai-verify{display:flex;align-items:center;justify-content:space-between;gap:14px;border-top:1px solid #202020;margin-top:16px;padding-top:12px}.wb-ai-verify strong{font-size:11px;font-weight:500;color:#bbb}.wb-ai-verify p{color:#777;font-size:10px;margin:3px 0 0}
91
+ .wb .wb-ai-verify-button{display:inline-flex;align-items:center;gap:7px;background:transparent;flex:none;font-size:11px;color:#bbb}.wb-ai-verify-button svg{width:13px;height:13px}
92
+ .wb-ai-layout button:focus-visible,.wb-ai-layout summary:focus-visible,.wb-ai-layout a:focus-visible{outline:2px solid #888;outline-offset:-3px}
93
+ @media(max-width:740px){.wb-component-visual{min-height:110px}}
94
+ @media(max-width:500px){dialog.wb-tools[data-panel=ai],dialog.wb-tools[data-panel=ai][data-selection=true]{width:calc(100vw - 16px);height:fit-content;max-height:calc(100dvh - 32px)}[data-panel=ai] .wb-tools-content{padding:48px 14px 18px;max-height:calc(100dvh - 32px)}[data-panel=ai] .wb-tools-feedback{padding:0 14px 14px}.wb-component-compose-foot{gap:10px}.wb-component-parts{width:100%}.wb .wb-ai-primary{width:100%}.wb-ai-verify{gap:10px}.wb-ai-verify p{max-width:150px}.wb-mcp-controls{align-items:flex-start}.wb-component-reselect{font-size:0!important;gap:0!important}.wb-component-reselect svg{width:17px;height:17px}.wb-component-identity{gap:8px}.wb-component-location{font-size:9px}}
95
+ @media(pointer:coarse){[data-panel=ai] .wb-tools-content{padding-top:60px}[data-panel=ai] .wb-close,.wb .wb-host-copy,.wb .wb-config-copy,.wb .wb-mcp-help-toggle{width:44px;min-height:44px}.wb-mcp-snippet{grid-template-columns:minmax(0,1fr) 44px}.wb-mcp-help a{min-height:44px}.wb .wb-ai-client,.wb .wb-config-copy,.wb .wb-ai-other,.wb-local-setup summary,.wb-ai-setup>summary,.wb-component-details summary,.wb-component-editor,.wb .wb-component-reselect,.wb .wb-ai-primary,.wb .wb-ai-select,.wb .wb-ai-verify-button{min-height:44px}.wb .wb-component-reselect{min-width:44px;justify-content:center}.wb-component-label textarea{font-size:16px}}
96
+ @media(prefers-reduced-motion:no-preference){.wb .wb-ai-client,.wb .wb-ai-select,.wb-ai-setup>summary{transition:background-color 120ms,color 120ms}.wb-component-composer{transition:border-color 120ms,box-shadow 120ms}.wb-mcp-reveal,.wb-mcp-help-reveal{transition:height 220ms ease-out,opacity 160ms ease-out}.wb-mcp-content{transition:opacity 120ms ease-out}}
97
+ @media(prefers-reduced-motion:reduce){.wb-mcp-reveal,.wb-mcp-help-reveal,.wb-mcp-content{transition:none!important}}
98
+ @media(forced-colors:active){.wb-component-visual{border:1px solid CanvasText}.wb-ai-clients{background:Canvas}.wb .wb-ai-client{border:1px solid ButtonText}.wb .wb-ai-client[aria-pressed=true]{outline:2px solid Highlight;outline-offset:-2px}.wb-component-mark{color:ButtonText}.wb .wb-ai-primary,.wb .wb-ai-select{background:ButtonFace;color:ButtonText;border:1px solid ButtonText}.wb .wb-mcp-help-toggle[aria-expanded=true]{outline:1px solid Highlight;outline-offset:-2px}.wb-mcp-help a{color:LinkText}.wb-ai-workspace,.wb-ai-verify{border-color:CanvasText}.wb-component-composer:focus-within{border-color:Highlight;outline:2px solid Highlight}.wb-ai-layout button:focus-visible,.wb-ai-layout summary:focus-visible,.wb-ai-layout a:focus-visible{outline-color:Highlight}}
99
+ `;
@@ -0,0 +1,25 @@
1
+ const messages={
2
+ 'initial-build':'Compile the requested routes.',
3
+ 'route-opened':'Compile a newly opened route.',
4
+ 'source-changed':'Source files changed.',
5
+ 'environment-changed':'Environment variables changed; restart the compiler.',
6
+ 'configuration-changed':'Build configuration changed; rebuild the active routes.',
7
+ 'safe-mode':'Safe mode uses a fresh compiler and the complete route graph.',
8
+ 'unknown-dependency':'A changed file is outside the dependency index; refresh all active routes.',
9
+ 'dependency-capacity':'The affected graph exceeds the selective-update budget; rebuild all active routes.',
10
+ 'missing-style-dependencies':'This build has no per-bundle style graph; rebuild styles and JavaScript together.',
11
+ 'css-only':'Update styles without replacing the application worker.',
12
+ 'css-module-exports':'CSS Module exports changed; rebuild JavaScript and styles together.',
13
+ 'css-processor':'Custom CSS processing requires a complete build.',
14
+ 'stylesheet-language':'This stylesheet requires the complete compiler.',
15
+ 'unknown-style-dependency':'A stylesheet is outside the recorded style graph.',
16
+ 'external-asset-base':'External asset URLs require a complete build.',
17
+ 'outside-active-graph':'Keep this unopened route uncompiled until it is requested.',
18
+ };
19
+ export function devBuildReason({initial=false,safe=false,changed=[],cause}={}){
20
+ const code=safe?'safe-mode':initial?'initial-build':
21
+ changed.some(file=>/(?:^|\/)\.env(?:\.|$)/.test(file))?'environment-changed':
22
+ changed.some(file=>/(?:^|\/)(?:(?:prnext|next|postcss|tsconfig|jsconfig)\.config\.|(?:tsconfig|jsconfig)\.json$|package(?:-lock)?\.json$|pnpm-lock\.yaml$|yarn\.lock$)/.test(file))?'configuration-changed':
23
+ cause&&messages[cause]?cause:changed.length?'source-changed':'route-opened';
24
+ return {code,summary:messages[code]};
25
+ }
@@ -0,0 +1,53 @@
1
+ import {open,lstat,realpath,rename,rm,readFile} from 'node:fs/promises';
2
+ import {constants} from 'node:fs';
3
+ import {randomUUID} from 'node:crypto';
4
+ import path from 'node:path';
5
+
6
+ const name='.dev-cache-rpc.json';
7
+ function endpoint(value){
8
+ let url;try{url=new URL(value);}catch{throw Error('Development cache connection is unavailable');}
9
+ if(url.protocol!=='http:'||url.hostname!=='127.0.0.1'||!url.port||url.pathname!=='/cache'||url.search||url.hash||url.username||url.password)throw Error('Invalid development cache endpoint');return url.href;
10
+ }
11
+ async function scopedDirectory(root,directory){
12
+ const requestedRoot=path.resolve(root),requestedDirectory=path.resolve(requestedRoot,directory);
13
+ root=await realpath(requestedRoot);
14
+ // macOS /var and Windows short-name paths can alias the project root. Map
15
+ // only that trusted root to its real path; links inside a generation must
16
+ // still be rejected rather than silently following another directory.
17
+ const relative=[requestedRoot,root].map(base=>path.relative(path.join(base,'.prnext-dev'),requestedDirectory)).find(value=>
18
+ value&&value!=='..'&&!value.startsWith('..'+path.sep)&&!path.isAbsolute(value)&&value.split(path.sep).length===2);
19
+ const expected=relative&&path.join(root,'.prnext-dev',relative);
20
+ if(!expected||await realpath(requestedDirectory)!==expected)throw Error('Development cache must belong to a live project generation');
21
+ return expected;
22
+ }
23
+ export function normalizeDevCachePath(value){
24
+ if(typeof value!=='string'||!value.startsWith('/')||value.startsWith('//')||value.length>1024||/[\\\u0000-\u001f]/.test(value))throw Error('A local cache pathname is required');
25
+ return new URL(value,'http://local').pathname.replace(/%[\da-f]{2}/gi,escape=>{const character=String.fromCharCode(parseInt(escape.slice(1),16));return /[a-z\d_.~-]/i.test(character)?character:escape.toUpperCase();}).replace(/\/+$/,'')||'/';
26
+ }
27
+ /** Only a development worker may publish its native process's private RPC. */
28
+ export async function publishDevCacheCapability({root,distDir,manifest,env=process.env}){
29
+ if(manifest.dev!==true||typeof manifest.devSession!=='string'||typeof manifest.buildId!=='string'||!env.PRNEXT_CACHE_URL||!env.PRNEXT_CACHE_TOKEN)return false;
30
+ const directory=await scopedDirectory(root,distDir);
31
+ if(!/^[a-f\d]{64}$/i.test(env.PRNEXT_CACHE_TOKEN))throw Error('Invalid development cache capability');
32
+ const value={version:1,session:manifest.devSession,buildId:manifest.buildId,endpoint:endpoint(env.PRNEXT_CACHE_URL),token:env.PRNEXT_CACHE_TOKEN};
33
+ const filename=path.join(directory,name),temporary=filename+'.'+randomUUID()+'.tmp';
34
+ try{const file=await open(temporary,'wx',0o600);try{await file.writeFile(JSON.stringify(value));}finally{await file.close();}await rename(temporary,filename);return true;}finally{await rm(temporary,{force:true});}
35
+ }
36
+ export async function invalidateDevRouteCache({root,directory,session,buildId,paths,signal,fetcher=fetch}){
37
+ directory=await scopedDirectory(root,directory);
38
+ const manifest=JSON.parse(await readFile(path.join(directory,'manifest.json'),'utf8'));
39
+ if(manifest.dev!==true||manifest.devSession!==session||manifest.buildId!==buildId)throw Error('Development cache generation changed; retry');
40
+ if(manifest.config?.cacheHandler||Object.keys(manifest.config?.cacheHandlers||{}).length)return {invalidated:false,scope:'custom-handler',reason:'Custom cache handlers must be invalidated through their own API.',persistentTransformsPreserved:true,generationPreserved:true};
41
+ const filename=path.join(directory,name),info=await lstat(filename).catch(()=>null);
42
+ if(!info)return {invalidated:false,scope:'native-route-cache',reason:'No active runtime cache yet. Visit the route first.',persistentTransformsPreserved:true,generationPreserved:true};
43
+ if(!info.isFile()||info.isSymbolicLink()||info.size>4096||process.platform!=='win32'&&(info.mode&0o077))throw Error('Invalid private development cache connection');
44
+ const file=await open(filename,constants.O_RDONLY|(constants.O_NOFOLLOW||0));let capability;
45
+ try{const stat=await file.stat();if(stat.ino!==info.ino||!stat.isFile()||stat.size>4096)throw Error('Development cache connection changed');capability=JSON.parse(await file.readFile('utf8'));}finally{await file.close();}
46
+ if(capability.version!==1||capability.session!==session||capability.buildId!==buildId||!/^[a-f\d]{64}$/i.test(capability.token||''))throw Error('Development cache connection changed; retry');
47
+ const associations=[...new Set(paths.map(value=>'page:'+normalizeDevCachePath(value)))];if(!associations.length||associations.length>2)throw Error('A targeted development route is required');
48
+ const timeout=AbortSignal.timeout(5000),combined=signal?AbortSignal.any([signal,timeout]):timeout;
49
+ const response=await fetcher(endpoint(capability.endpoint),{method:'POST',redirect:'error',headers:{'content-type':'application/json',authorization:'Bearer '+capability.token},body:JSON.stringify({op:'invalidate',paths:associations,mode:'expire'}),signal:combined});
50
+ if(!response.ok){await response.body?.cancel();throw Error('Development cache invalidation failed ('+response.status+')');}
51
+ const body=await response.text();if(Buffer.byteLength(body)>16384)throw Error('Invalid development cache reply');JSON.parse(body);
52
+ return {invalidated:true,scope:'native-route-cache',paths:associations.map(value=>value.slice(5)),persistentTransformsPreserved:true,generationPreserved:true,customHandlersInvalidated:false};
53
+ }
@@ -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,130 @@ 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; }
201
+ if (state.unsafe) { reload('An edited module exports values outside React components. Page reloaded.'); return; }
87
202
  await updateStyles(kind === 'pages' ? route.css : manifest.app.css);
88
- clearError();
89
- Refresh.performReactRefresh();
90
- if (pending) await application.devCommit?.(pending.options);
203
+ // An unrelated type-only edit may publish a new generation without changing
204
+ // any browser module. Keep its existing runtime/hydration issues and current
205
+ // selection until application code changes, rather than racing diagnostics.
206
+ if(state.refreshSourceChanged||navigation){
207
+ state.recoverableIssue=undefined;state.recoverableKey=undefined;
208
+ clearError();
209
+ }else if(hasCriticalIssue()&&(state.issueKind==='build'||errorKey(state.issue))){
210
+ clearError();
211
+ }
212
+ if(!message.cssOnly)Refresh.performReactRefresh();
213
+ if (pending) await (navigation ? application.devSync?.(pending.options) : application.devCommit?.(pending.options));
91
214
  state.buildId = message.buildId;
215
+ if(message.sequence)state.sequence=message.sequence;
92
216
  state.manifest = manifest;
93
217
  document.documentElement.dataset.prnextDevBuild = message.buildId;
94
- globalThis.dispatchEvent(new CustomEvent('prnext:refresh', { detail: { buildId: message.buildId } }));
218
+ state.lastRefresh={buildId:message.buildId,durationMs:Math.round(performance.now()-refreshStarted),changed};
219
+ await painted();state.confirmed=true;
220
+ globalThis.dispatchEvent(new CustomEvent('prnext:refresh', { detail: state.lastRefresh }));
221
+ globalThis.dispatchEvent(new CustomEvent('prnext:devtools:ready'));
222
+ if(state.typeErrors)typeDiagnostics(state.typeErrors);
95
223
  } catch (error) { showError(error); }
96
224
  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); }
225
+ state.applying = false; state.pending = null; state.indicator?.update();
226
+ applied();
227
+ if (state.queued) { const queued = state.queued; state.queued = null; if (queued.buildId !== state.buildId) void applyUpdate(queued,{navigation:queued.navigation}); }
99
228
  }
100
229
  }
101
230
 
102
231
  function connect(options) {
232
+ state.basePath=options.basePath||'';
103
233
  if (state.source) return;
104
234
  state.buildId = options.dev.buildId;
105
- const source = new EventSource(`${options.basePath || ''}/_prnext/dev`);
235
+ state.sequence=options.dev.sequence||0;state.session=options.dev.session;
236
+ state.clientId ||= crypto.randomUUID();
237
+ state.onDemand=options.dev.onDemand===true;
238
+ state.devConnection = 'connecting'; state.devBuildState = 'ready';
239
+ if (options.dev.indicator === false) { state.consoleCapture?.dispose(); state.consoleCapture = null; state.consoleDisabled = true; }
240
+ 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});
241
+ state.tools ||= createDevToolsClient({capture:state.consoleCapture,snapshot,config:options.dev.tools||options.devTools});
242
+ showReloadReason();
243
+ if (options.dev.indicator !== false && !isDevicePreview() && !state.indicator) state.indicator = createDevIndicator({
244
+ tools:state.tools,
245
+ position: options.dev.indicator?.position,
246
+ capture: state.consoleCapture,
247
+ snapshot: () => ({
248
+ connection: state.devConnection, building: state.devBuildState === 'building' || state.applying,
249
+ router: state.currentKind, buildId: state.buildId, sequence: state.sequence,
250
+ typeErrors: state.typeErrors?.length || 0,
251
+ issues: (state.typeErrors?.length || 0) + (hasCriticalIssue() ? 1 : 0) + (state.recoverableIssue ? 1 : 0),
252
+ criticalIssues: hasCriticalIssue() ? 1 : 0,
253
+ }),
254
+ showIssues() {
255
+ const critical=hasCriticalIssue();
256
+ const error = critical?state.issue:state.recoverableIssue||state.issue||state.lastTypeDiagnostic;
257
+ if (error===undefined||error===null) return;
258
+ if (typeof error === 'object') state.dismissedErrors?.delete(error);
259
+ const key = errorKey(error); if (key) state.dismissedDigests?.delete(key);
260
+ state.dismissedTypeDiagnostic = undefined; showError(error,{retain:error!==state.recoverableIssue});
261
+ state.errorOverlay?.focusDismiss();
262
+ },
263
+ });
264
+ if (isDevicePreview()) { try { parent.postMessage({type:'prnext:preview-ready'}, location.origin); } catch {} }
265
+ state.ensureRoute=async raw=>{
266
+ if(!state.onDemand)return;
267
+ const url=new URL(raw,location.href);if(url.origin!==location.origin)return;
268
+ state.ensuring=(state.ensuring||0)+1;
269
+ try{
270
+ const response=await fetch(`${options.basePath||''}/_prnext/ensure?client=${state.clientId}&path=${encodeURIComponent(url.pathname+url.search)}`,{cache:'no-store',credentials:'same-origin'});
271
+ if(!response.ok)throw Error(await response.text());
272
+ const update=await response.json();
273
+ if(update.buildId!==state.buildId)await applyUpdate(update,{navigation:true});
274
+ }finally{state.ensuring--;}
275
+ };
276
+ const source = new EventSource(`${options.basePath || ''}/_prnext/dev?client=${state.clientId}&path=${encodeURIComponent(location.pathname+location.search)}`);
106
277
  state.source = source;
278
+ source.onopen = () => { state.devConnection = 'connected'; state.indicator?.update(); };
279
+ source.onerror = () => { state.devConnection = 'reconnecting'; state.indicator?.update(); };
107
280
  source.onmessage = event => {
108
281
  let message; try { message = JSON.parse(event.data); } catch { return; }
282
+ if(message.session&&state.session&&message.session!==state.session){reload('Development server restarted. Page reloaded.');return;}
283
+ state.devBuildState = message.state; state.indicator?.update();
284
+ if(Array.isArray(message.typeErrors))typeDiagnostics(message.typeErrors);
109
285
  if (message.state === 'error') { showError(message.error || 'Build failed'); return; }
110
286
  if (message.state !== 'ready') return;
111
287
  if (message.buildId === state.buildId) return;
112
- void applyUpdate(message);
288
+ // A cold navigation may receive SSE readiness before its ensure response.
289
+ // Updating modules is safe; refreshing the old route would cancel the click.
290
+ void applyUpdate(message,{navigation:state.ensuring>0});
113
291
  };
114
292
  globalThis.addEventListener('error', event => { if (event.error) showError(event.error); });
115
293
  globalThis.addEventListener('unhandledrejection', event => showError(event.reason));
116
- globalThis.addEventListener('pagehide', () => source.close(), { once: true });
294
+ globalThis.addEventListener('pagehide', () => {source.close();state.tools?.dispose();state.stopHydrationDiagnostics?.();}, { once: true });
117
295
  }
118
296
 
119
297
  state.reportError = showError;
298
+ state.reportRecoverableError = reportRecoverableError;
299
+ if(state.consoleCapture&&!state.stopHydrationDiagnostics){
300
+ let serial=0;
301
+ // Attribute-only hydration mismatches are reported by React through console,
302
+ // without invoking onRecoverableError. Match its diagnostic, not arbitrary logs.
303
+ state.stopHydrationDiagnostics=state.consoleCapture.subscribe(()=>{
304
+ for(const entry of state.consoleCapture?.snapshot().entries||[]){
305
+ if(entry.id<=serial)continue;serial=entry.id;
306
+ 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);
307
+ }
308
+ });
309
+ }
310
+ function painted(){return new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));}
120
311
  state.bootstrap = (kind, options, start) => {
121
312
  state.currentKind = kind;
122
313
  if (!state.applications.has(kind)) {
314
+ state.confirmed=false;
123
315
  const application = Promise.resolve().then(start);
124
316
  state.applications.set(kind, application);
125
317
  connect(options);
126
- application.catch(showError);
318
+ application.then(async current=>{await current.devReady;await painted();if(!state.applying){state.confirmed=true;globalThis.dispatchEvent(new CustomEvent('prnext:devtools:ready'));}},error=>{
319
+ state.bootstrapFailed={kind,buildId:options.dev.buildId};state.confirmed=false;showError(error);
320
+ }).catch(showError);
127
321
  } else if (state.applying) state.pending = { kind, options };
128
322
  return state.applications.get(kind);
129
323
  };
@@ -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
+ }