@mandujs/core 0.41.2 → 0.43.0
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.
- package/package.json +21 -4
- package/src/auth/__tests__/login.test.ts +420 -419
- package/src/auth/__tests__/reset.test.ts +296 -296
- package/src/brain/adapters/anthropic-oauth.ts +421 -420
- package/src/brain/adapters/index.ts +2 -1
- package/src/brain/adapters/ollama.ts +1 -1
- package/src/brain/adapters/openai-oauth.ts +534 -533
- package/src/brain/brain.ts +2 -1
- package/src/brain/redactor.ts +196 -196
- package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
- package/src/bundler/__tests__/cold-start.test.ts +504 -504
- package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
- package/src/bundler/__tests__/hdr.test.ts +1 -1
- package/src/bundler/analyzer.ts +958 -958
- package/src/bundler/build.ts +104 -14
- package/src/bundler/dev.ts +125 -0
- package/src/bundler/hmr-types.ts +1 -0
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
- package/src/bundler/plugins/index.ts +14 -0
- package/src/bundler/plugins/react-compiler-lint.ts +253 -0
- package/src/bundler/plugins/react-compiler.ts +162 -0
- package/src/bundler/types.ts +12 -0
- package/src/change/integrity.ts +2 -1
- package/src/client/index.ts +10 -0
- package/src/client/island.ts +38 -11
- package/src/client/router.ts +6 -1
- package/src/config/mandu.ts +57 -0
- package/src/config/validate.ts +42 -0
- package/src/content/collection.ts +844 -809
- package/src/content/content-layer.ts +316 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/digest.ts +133 -133
- package/src/content/generate-types.ts +168 -168
- package/src/content/index.ts +6 -1
- package/src/content/llms-txt.ts +277 -277
- package/src/contract/define.ts +474 -474
- package/src/contract/route-helpers.ts +2 -1
- package/src/contract/zod-utils.ts +158 -155
- package/src/db/index.ts +513 -513
- package/src/desktop/__tests__/smoke.test.ts +100 -100
- package/src/desktop/webview-fallback.ts +583 -583
- package/src/desktop/window.ts +3 -1
- package/src/dev-error-overlay/overlay-client.ts +300 -300
- package/src/devtools/ai/mcp-connector.ts +499 -498
- package/src/devtools/client/components/kitchen-root.tsx +7 -2
- package/src/email/resend.ts +163 -163
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/graph.ts +898 -898
- package/src/guard/index.ts +16 -0
- package/src/guard/statistics.ts +578 -578
- package/src/guard/tsgolint-bridge.ts +512 -0
- package/src/i18n/locale-resolver.ts +214 -214
- package/src/id/__tests__/id.test.ts +120 -120
- package/src/intent/index.ts +321 -321
- package/src/island/index.ts +39 -23
- package/src/kitchen/api/contract-api.ts +15 -8
- package/src/kitchen/kitchen-ui.ts +2137 -2137
- package/src/lockfile/index.ts +3 -2
- package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
- package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
- package/src/middleware/secure/index.ts +417 -417
- package/src/observability/event-bus.ts +2 -2
- package/src/observability/metrics.ts +334 -334
- package/src/observability/tracing.ts +694 -694
- package/src/openapi/generator.ts +1 -1
- package/src/perf/user-marks.ts +553 -553
- package/src/plugins/registry.ts +387 -387
- package/src/resource/ddl/diff.ts +392 -392
- package/src/resource/ddl/snapshot.ts +448 -447
- package/src/resource/generator-schema.ts +477 -476
- package/src/resource/parser.ts +4 -2
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.ts +422 -422
- package/src/runtime/fast-refresh-types.ts +126 -128
- package/src/runtime/image-handler.ts +206 -195
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +36 -19
- package/src/runtime/session-key.ts +328 -328
- package/src/scheduler/__tests__/scheduler.test.ts +514 -514
- package/src/seo/resolve/index.ts +353 -353
- package/src/spec/load.ts +1 -1
- package/src/testing/reporter.ts +676 -676
- package/src/testing/server.ts +196 -196
- package/src/testing/snapshot.ts +444 -444
- package/src/utils/__tests__/lru-cache.test.ts +186 -186
- package/src/utils/bun.ts +8 -8
package/src/desktop/window.ts
CHANGED
|
@@ -99,7 +99,7 @@ export async function _loadWebviewBun(): Promise<WebviewBunModule> {
|
|
|
99
99
|
"Then pin the version in package.json. Tested: ^2.4.0 (MIT).",
|
|
100
100
|
"Docs: https://github.com/tr1ckydev/webview-bun",
|
|
101
101
|
].join("\n"),
|
|
102
|
-
{ cause
|
|
102
|
+
{ cause },
|
|
103
103
|
);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -353,6 +353,7 @@ export async function createWindow(
|
|
|
353
353
|
`[@mandujs/core/desktop] Failed to bind handler "${name}": ${
|
|
354
354
|
error instanceof Error ? error.message : String(error)
|
|
355
355
|
}`,
|
|
356
|
+
{ cause: error },
|
|
356
357
|
);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
@@ -419,6 +420,7 @@ export async function createWindow(
|
|
|
419
420
|
`[@mandujs/core/desktop] navigate() failed: ${
|
|
420
421
|
error instanceof Error ? error.message : String(error)
|
|
421
422
|
}`,
|
|
423
|
+
{ cause: error },
|
|
422
424
|
);
|
|
423
425
|
}
|
|
424
426
|
|
|
@@ -1,300 +1,300 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 18.α — Dev Error Overlay client IIFE.
|
|
3
|
-
*
|
|
4
|
-
* This file EXPORTS a string (`OVERLAY_CLIENT_SCRIPT`) that is inlined
|
|
5
|
-
* into the SSR `<head>` by the injector. It is NOT compiled as a
|
|
6
|
-
* module; the whole body runs in parse order on every dev page load.
|
|
7
|
-
*
|
|
8
|
-
* Size target: < 10 KB gzipped (current ≈ 2 KB gz). No dependencies.
|
|
9
|
-
* Only touches `window`, `document`, `navigator.clipboard`, and a tiny
|
|
10
|
-
* WeakSet.
|
|
11
|
-
*
|
|
12
|
-
* Listeners installed:
|
|
13
|
-
* - `window.onerror` → uncaught script errors
|
|
14
|
-
* - `unhandledrejection` → Promise rejections
|
|
15
|
-
* - custom event `__MANDU_ERROR__` → the server embeds a 500 payload
|
|
16
|
-
* in the HTML, fires this event on DOMContentLoaded, and the
|
|
17
|
-
* overlay picks it up. User code can also `dispatchEvent` it to
|
|
18
|
-
* surface synthetic errors.
|
|
19
|
-
*
|
|
20
|
-
* Everything scoped in an IIFE — NO globals leak except the single
|
|
21
|
-
* `__MANDU_OVERLAY_MOUNTED__` sentinel that prevents double-mount on
|
|
22
|
-
* HMR.
|
|
23
|
-
*/
|
|
24
|
-
import { OVERLAY_STYLES } from "./overlay-styles";
|
|
25
|
-
import {
|
|
26
|
-
OVERLAY_CUSTOM_EVENT,
|
|
27
|
-
OVERLAY_MOUNTED_FLAG,
|
|
28
|
-
OVERLAY_PAYLOAD_ELEMENT_ID,
|
|
29
|
-
} from "./types";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Build the client-side IIFE body as a string. Parameterised by the
|
|
33
|
-
* style block and the two sentinel names so tests can verify the exact
|
|
34
|
-
* shape without re-declaring the constants.
|
|
35
|
-
*/
|
|
36
|
-
function buildOverlayClientScript(): string {
|
|
37
|
-
// The IIFE below references the constants through string interpolation,
|
|
38
|
-
// NOT through imports — because it runs in the browser and has no
|
|
39
|
-
// module loader.
|
|
40
|
-
return `(function(){
|
|
41
|
-
var MOUNTED=${JSON.stringify(OVERLAY_MOUNTED_FLAG)};
|
|
42
|
-
var EVT=${JSON.stringify(OVERLAY_CUSTOM_EVENT)};
|
|
43
|
-
var PAYLOAD_ID=${JSON.stringify(OVERLAY_PAYLOAD_ELEMENT_ID)};
|
|
44
|
-
if(typeof window==="undefined"||typeof document==="undefined")return;
|
|
45
|
-
if(window[MOUNTED]===true)return;
|
|
46
|
-
window[MOUNTED]=true;
|
|
47
|
-
|
|
48
|
-
var STYLE=${JSON.stringify(OVERLAY_STYLES)};
|
|
49
|
-
var seen=new WeakSet();
|
|
50
|
-
|
|
51
|
-
function parseFrames(stack){
|
|
52
|
-
if(typeof stack!=="string"||stack.length===0)return [];
|
|
53
|
-
var lines=stack.split("\\n");
|
|
54
|
-
var out=[];
|
|
55
|
-
for(var i=0;i<lines.length;i++){
|
|
56
|
-
var raw=lines[i];
|
|
57
|
-
var t=raw.trim();
|
|
58
|
-
if(!t||t===stack.split("\\n")[0]&&/^[A-Z][a-zA-Z]*(?:Error)?:/.test(t))continue;
|
|
59
|
-
// Chrome: " at fn (file:line:col)"
|
|
60
|
-
// Firefox: "fn@file:line:col"
|
|
61
|
-
var m=t.match(/^at\\s+(.+?)\\s+\\((.+):(\\d+):(\\d+)\\)$/);
|
|
62
|
-
if(!m)m=t.match(/^at\\s+(.+):(\\d+):(\\d+)$/);
|
|
63
|
-
if(m&&m.length===5){
|
|
64
|
-
out.push({fn:m[1],file:m[2],line:parseInt(m[3],10),column:parseInt(m[4],10),raw:raw});
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if(m&&m.length===4){
|
|
68
|
-
out.push({fn:"<anonymous>",file:m[1],line:parseInt(m[2],10),column:parseInt(m[3],10),raw:raw});
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
var ff=t.match(/^(.*?)@(.+):(\\d+):(\\d+)$/);
|
|
72
|
-
if(ff){
|
|
73
|
-
out.push({fn:ff[1]||"<anonymous>",file:ff[2],line:parseInt(ff[3],10),column:parseInt(ff[4],10),raw:raw});
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
out.push({fn:"<anonymous>",file:t,line:null,column:null,raw:raw});
|
|
77
|
-
}
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function toPayload(err,kind){
|
|
82
|
-
var name="Error",message="",stack="";
|
|
83
|
-
if(err&&typeof err==="object"){
|
|
84
|
-
name=err.name||"Error";
|
|
85
|
-
message=typeof err.message==="string"?err.message:String(err.message||"");
|
|
86
|
-
stack=typeof err.stack==="string"?err.stack:"";
|
|
87
|
-
}else if(typeof err==="string"){
|
|
88
|
-
message=err;
|
|
89
|
-
}else if(err!=null){
|
|
90
|
-
try{message=String(err);}catch(_){message="<unserializable error>";}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
name:name,
|
|
94
|
-
message:message,
|
|
95
|
-
frames:parseFrames(stack),
|
|
96
|
-
stack:stack,
|
|
97
|
-
kind:kind,
|
|
98
|
-
timestamp:Date.now(),
|
|
99
|
-
url:typeof location!=="undefined"?location.pathname+location.search:undefined,
|
|
100
|
-
userAgent:typeof navigator!=="undefined"?navigator.userAgent:undefined
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function esc(s){
|
|
105
|
-
if(s==null)return "";
|
|
106
|
-
return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function fileHref(file,line,col){
|
|
110
|
-
// vscode://file/<abs>:<line>:<col> — falls back gracefully when the
|
|
111
|
-
// host OS has no handler. Relative paths get "//" prefix so the URL
|
|
112
|
-
// still parses, even though the editor may refuse to jump.
|
|
113
|
-
var p=file;
|
|
114
|
-
if(!/^[a-zA-Z]:|^\\//.test(p))p="/"+p;
|
|
115
|
-
var u="vscode://file"+p;
|
|
116
|
-
if(line)u+=":"+line;
|
|
117
|
-
if(col)u+=":"+col;
|
|
118
|
-
return u;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function renderFrames(frames){
|
|
122
|
-
if(!frames||frames.length===0)return '<li class="mandu-dev-overlay__frame"><span class="mandu-dev-overlay__frame-fn"><no stack></span></li>';
|
|
123
|
-
var items=[];
|
|
124
|
-
for(var i=0;i<frames.length&&i<40;i++){
|
|
125
|
-
var f=frames[i];
|
|
126
|
-
var loc=esc(f.file)+(f.line?":"+f.line:"")+(f.column?":"+f.column:"");
|
|
127
|
-
var href=f.file?fileHref(f.file,f.line,f.column):"";
|
|
128
|
-
var locHtml=href?'<a class="mandu-dev-overlay__frame-link" href="'+esc(href)+'">'+loc+'</a>':loc;
|
|
129
|
-
items.push('<li class="mandu-dev-overlay__frame"><span class="mandu-dev-overlay__frame-fn">'+esc(f.fn||"<anonymous>")+'</span><span class="mandu-dev-overlay__frame-loc">'+locHtml+'</span></li>');
|
|
130
|
-
}
|
|
131
|
-
return items.join("");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function ensureStyleTag(){
|
|
135
|
-
if(document.getElementById("__mandu-dev-overlay-style"))return;
|
|
136
|
-
var s=document.createElement("style");
|
|
137
|
-
s.id="__mandu-dev-overlay-style";
|
|
138
|
-
s.textContent=STYLE;
|
|
139
|
-
(document.head||document.documentElement).appendChild(s);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function copyForAI(payload,btn){
|
|
143
|
-
var text="# Mandu dev error snapshot\\n\\n"
|
|
144
|
-
+"- kind: "+payload.kind+"\\n"
|
|
145
|
-
+"- name: "+payload.name+"\\n"
|
|
146
|
-
+"- message: "+payload.message+"\\n"
|
|
147
|
-
+"- url: "+(payload.url||"")+"\\n"
|
|
148
|
-
+"- routeId: "+(payload.routeId||"")+"\\n"
|
|
149
|
-
+"- timestamp: "+new Date(payload.timestamp).toISOString()+"\\n\\n"
|
|
150
|
-
+"## Stack\\n\\n\`\`\`\\n"+payload.stack+"\\n\`\`\`\\n";
|
|
151
|
-
var done=function(){
|
|
152
|
-
var prev=btn.textContent;
|
|
153
|
-
btn.textContent="Copied!";
|
|
154
|
-
setTimeout(function(){btn.textContent=prev;},1500);
|
|
155
|
-
};
|
|
156
|
-
if(navigator.clipboard&&navigator.clipboard.writeText){
|
|
157
|
-
navigator.clipboard.writeText(text).then(done,function(){
|
|
158
|
-
fallbackCopy(text);done();
|
|
159
|
-
});
|
|
160
|
-
}else{
|
|
161
|
-
fallbackCopy(text);done();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function fallbackCopy(text){
|
|
166
|
-
try{
|
|
167
|
-
var ta=document.createElement("textarea");
|
|
168
|
-
ta.value=text;ta.style.position="fixed";ta.style.left="-9999px";
|
|
169
|
-
document.body.appendChild(ta);ta.select();
|
|
170
|
-
try{document.execCommand("copy");}catch(_){}
|
|
171
|
-
document.body.removeChild(ta);
|
|
172
|
-
}catch(_){}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function mount(payload){
|
|
176
|
-
if(seen.has(payload))return;
|
|
177
|
-
seen.add(payload);
|
|
178
|
-
ensureStyleTag();
|
|
179
|
-
var root=document.getElementById("__mandu-dev-overlay-root");
|
|
180
|
-
if(!root){
|
|
181
|
-
root=document.createElement("div");
|
|
182
|
-
root.id="__mandu-dev-overlay-root";
|
|
183
|
-
root.className="mandu-dev-overlay";
|
|
184
|
-
root.setAttribute("role","dialog");
|
|
185
|
-
root.setAttribute("aria-modal","true");
|
|
186
|
-
root.setAttribute("aria-label","Mandu dev error overlay");
|
|
187
|
-
(document.body||document.documentElement).appendChild(root);
|
|
188
|
-
}
|
|
189
|
-
var kindLabel=({ssr:"SSR render failed",window:"Uncaught error",unhandledrejection:"Unhandled promise rejection",manual:"Error"})[payload.kind]||"Error";
|
|
190
|
-
var meta="";
|
|
191
|
-
if(payload.routeId||payload.url){
|
|
192
|
-
meta='<div class="mandu-dev-overlay__meta">'
|
|
193
|
-
+(payload.routeId?'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">Route</span><span class="mandu-dev-overlay__meta-value">'+esc(payload.routeId)+'</span></div>':'')
|
|
194
|
-
+(payload.url?'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">URL</span><span class="mandu-dev-overlay__meta-value">'+esc(payload.url)+'</span></div>':'')
|
|
195
|
-
+'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">Time</span><span class="mandu-dev-overlay__meta-value">'+esc(new Date(payload.timestamp).toLocaleTimeString())+'</span></div>'
|
|
196
|
-
+'</div>';
|
|
197
|
-
}
|
|
198
|
-
root.innerHTML='<div class="mandu-dev-overlay__panel">'
|
|
199
|
-
+'<div class="mandu-dev-overlay__header">'
|
|
200
|
-
+'<div class="mandu-dev-overlay__title">'
|
|
201
|
-
+'<span class="mandu-dev-overlay__kind">'+esc(kindLabel)+'</span>'
|
|
202
|
-
+'<span class="mandu-dev-overlay__name">'+esc(payload.name)+'</span>'
|
|
203
|
-
+'</div>'
|
|
204
|
-
+'<div class="mandu-dev-overlay__actions">'
|
|
205
|
-
+'<button type="button" class="mandu-dev-overlay__btn mandu-dev-overlay__btn--primary" data-action="copy">Copy for AI</button>'
|
|
206
|
-
+'<button type="button" class="mandu-dev-overlay__btn" data-action="close" aria-label="Close overlay">Close</button>'
|
|
207
|
-
+'</div>'
|
|
208
|
-
+'</div>'
|
|
209
|
-
+'<div class="mandu-dev-overlay__body">'
|
|
210
|
-
+'<p class="mandu-dev-overlay__message">'+esc(payload.message||"(no message)")+'</p>'
|
|
211
|
-
+meta
|
|
212
|
-
+'<h3 class="mandu-dev-overlay__section-title">Stack frames</h3>'
|
|
213
|
-
+'<ol class="mandu-dev-overlay__frames">'+renderFrames(payload.frames)+'</ol>'
|
|
214
|
-
+(payload.stack?'<h3 class="mandu-dev-overlay__section-title" style="margin-top:16px">Raw stack</h3><pre class="mandu-dev-overlay__stack">'+esc(payload.stack)+'</pre>':'')
|
|
215
|
-
+'</div>'
|
|
216
|
-
+'<div class="mandu-dev-overlay__footer">Dev-only overlay. Press <code>Esc</code> to dismiss. Never shipped in production builds.</div>'
|
|
217
|
-
+'</div>';
|
|
218
|
-
root.hidden=false;
|
|
219
|
-
|
|
220
|
-
var onClick=function(e){
|
|
221
|
-
var t=e.target;
|
|
222
|
-
if(!t||!t.getAttribute)return;
|
|
223
|
-
var a=t.getAttribute("data-action");
|
|
224
|
-
if(a==="close")hide();
|
|
225
|
-
else if(a==="copy")copyForAI(payload,t);
|
|
226
|
-
};
|
|
227
|
-
root.onclick=onClick;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function hide(){
|
|
231
|
-
var root=document.getElementById("__mandu-dev-overlay-root");
|
|
232
|
-
if(root)root.hidden=true;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function handle(err,kind,extra){
|
|
236
|
-
try{
|
|
237
|
-
var p=toPayload(err,kind);
|
|
238
|
-
if(extra&&typeof extra==="object"){
|
|
239
|
-
if(extra.routeId)p.routeId=extra.routeId;
|
|
240
|
-
if(extra.url)p.url=extra.url;
|
|
241
|
-
}
|
|
242
|
-
mount(p);
|
|
243
|
-
}catch(_){
|
|
244
|
-
// overlay must never throw — silent drop.
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
window.addEventListener("error",function(ev){
|
|
249
|
-
if(ev&&ev.error)handle(ev.error,"window");
|
|
250
|
-
else if(ev)handle(ev.message||"Script error",(\"window\"));
|
|
251
|
-
});
|
|
252
|
-
window.addEventListener("unhandledrejection",function(ev){
|
|
253
|
-
handle(ev&&ev.reason,"unhandled-rejection");
|
|
254
|
-
});
|
|
255
|
-
window.addEventListener(EVT,function(ev){
|
|
256
|
-
var d=ev&&ev.detail;
|
|
257
|
-
if(!d)return;
|
|
258
|
-
if(d.name||d.message||d.stack){
|
|
259
|
-
var p={
|
|
260
|
-
name:d.name||"Error",
|
|
261
|
-
message:d.message||"",
|
|
262
|
-
frames:d.frames||parseFrames(d.stack||""),
|
|
263
|
-
stack:d.stack||"",
|
|
264
|
-
kind:d.kind||"manual",
|
|
265
|
-
timestamp:d.timestamp||Date.now(),
|
|
266
|
-
routeId:d.routeId,
|
|
267
|
-
url:d.url||(typeof location!=="undefined"?location.pathname+location.search:undefined),
|
|
268
|
-
userAgent:typeof navigator!=="undefined"?navigator.userAgent:undefined
|
|
269
|
-
};
|
|
270
|
-
mount(p);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
document.addEventListener("keydown",function(e){
|
|
274
|
-
if(e.key==="Escape")hide();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
function readEmbedded(){
|
|
278
|
-
var el=document.getElementById(PAYLOAD_ID);
|
|
279
|
-
if(!el)return;
|
|
280
|
-
try{
|
|
281
|
-
var data=JSON.parse(el.textContent||"null");
|
|
282
|
-
if(data&&typeof data==="object")mount(data);
|
|
283
|
-
}catch(_){}
|
|
284
|
-
}
|
|
285
|
-
if(document.readyState==="loading"){
|
|
286
|
-
document.addEventListener("DOMContentLoaded",readEmbedded);
|
|
287
|
-
}else{
|
|
288
|
-
readEmbedded();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Public surface (tests + user code)
|
|
292
|
-
window.__MANDU_DEV_OVERLAY__={show:function(e){handle(e,"manual");},hide:hide};
|
|
293
|
-
})();`;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** The serialized IIFE. Computed once at module-load. */
|
|
297
|
-
export const OVERLAY_CLIENT_SCRIPT = buildOverlayClientScript();
|
|
298
|
-
|
|
299
|
-
/** @internal test-only accessor so tests can recompute / inspect the script. */
|
|
300
|
-
export const _testOnly_buildOverlayClientScript = buildOverlayClientScript;
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.α — Dev Error Overlay client IIFE.
|
|
3
|
+
*
|
|
4
|
+
* This file EXPORTS a string (`OVERLAY_CLIENT_SCRIPT`) that is inlined
|
|
5
|
+
* into the SSR `<head>` by the injector. It is NOT compiled as a
|
|
6
|
+
* module; the whole body runs in parse order on every dev page load.
|
|
7
|
+
*
|
|
8
|
+
* Size target: < 10 KB gzipped (current ≈ 2 KB gz). No dependencies.
|
|
9
|
+
* Only touches `window`, `document`, `navigator.clipboard`, and a tiny
|
|
10
|
+
* WeakSet.
|
|
11
|
+
*
|
|
12
|
+
* Listeners installed:
|
|
13
|
+
* - `window.onerror` → uncaught script errors
|
|
14
|
+
* - `unhandledrejection` → Promise rejections
|
|
15
|
+
* - custom event `__MANDU_ERROR__` → the server embeds a 500 payload
|
|
16
|
+
* in the HTML, fires this event on DOMContentLoaded, and the
|
|
17
|
+
* overlay picks it up. User code can also `dispatchEvent` it to
|
|
18
|
+
* surface synthetic errors.
|
|
19
|
+
*
|
|
20
|
+
* Everything scoped in an IIFE — NO globals leak except the single
|
|
21
|
+
* `__MANDU_OVERLAY_MOUNTED__` sentinel that prevents double-mount on
|
|
22
|
+
* HMR.
|
|
23
|
+
*/
|
|
24
|
+
import { OVERLAY_STYLES } from "./overlay-styles";
|
|
25
|
+
import {
|
|
26
|
+
OVERLAY_CUSTOM_EVENT,
|
|
27
|
+
OVERLAY_MOUNTED_FLAG,
|
|
28
|
+
OVERLAY_PAYLOAD_ELEMENT_ID,
|
|
29
|
+
} from "./types";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the client-side IIFE body as a string. Parameterised by the
|
|
33
|
+
* style block and the two sentinel names so tests can verify the exact
|
|
34
|
+
* shape without re-declaring the constants.
|
|
35
|
+
*/
|
|
36
|
+
function buildOverlayClientScript(): string {
|
|
37
|
+
// The IIFE below references the constants through string interpolation,
|
|
38
|
+
// NOT through imports — because it runs in the browser and has no
|
|
39
|
+
// module loader.
|
|
40
|
+
return `(function(){
|
|
41
|
+
var MOUNTED=${JSON.stringify(OVERLAY_MOUNTED_FLAG)};
|
|
42
|
+
var EVT=${JSON.stringify(OVERLAY_CUSTOM_EVENT)};
|
|
43
|
+
var PAYLOAD_ID=${JSON.stringify(OVERLAY_PAYLOAD_ELEMENT_ID)};
|
|
44
|
+
if(typeof window==="undefined"||typeof document==="undefined")return;
|
|
45
|
+
if(window[MOUNTED]===true)return;
|
|
46
|
+
window[MOUNTED]=true;
|
|
47
|
+
|
|
48
|
+
var STYLE=${JSON.stringify(OVERLAY_STYLES)};
|
|
49
|
+
var seen=new WeakSet();
|
|
50
|
+
|
|
51
|
+
function parseFrames(stack){
|
|
52
|
+
if(typeof stack!=="string"||stack.length===0)return [];
|
|
53
|
+
var lines=stack.split("\\n");
|
|
54
|
+
var out=[];
|
|
55
|
+
for(var i=0;i<lines.length;i++){
|
|
56
|
+
var raw=lines[i];
|
|
57
|
+
var t=raw.trim();
|
|
58
|
+
if(!t||t===stack.split("\\n")[0]&&/^[A-Z][a-zA-Z]*(?:Error)?:/.test(t))continue;
|
|
59
|
+
// Chrome: " at fn (file:line:col)"
|
|
60
|
+
// Firefox: "fn@file:line:col"
|
|
61
|
+
var m=t.match(/^at\\s+(.+?)\\s+\\((.+):(\\d+):(\\d+)\\)$/);
|
|
62
|
+
if(!m)m=t.match(/^at\\s+(.+):(\\d+):(\\d+)$/);
|
|
63
|
+
if(m&&m.length===5){
|
|
64
|
+
out.push({fn:m[1],file:m[2],line:parseInt(m[3],10),column:parseInt(m[4],10),raw:raw});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if(m&&m.length===4){
|
|
68
|
+
out.push({fn:"<anonymous>",file:m[1],line:parseInt(m[2],10),column:parseInt(m[3],10),raw:raw});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
var ff=t.match(/^(.*?)@(.+):(\\d+):(\\d+)$/);
|
|
72
|
+
if(ff){
|
|
73
|
+
out.push({fn:ff[1]||"<anonymous>",file:ff[2],line:parseInt(ff[3],10),column:parseInt(ff[4],10),raw:raw});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
out.push({fn:"<anonymous>",file:t,line:null,column:null,raw:raw});
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toPayload(err,kind){
|
|
82
|
+
var name="Error",message="",stack="";
|
|
83
|
+
if(err&&typeof err==="object"){
|
|
84
|
+
name=err.name||"Error";
|
|
85
|
+
message=typeof err.message==="string"?err.message:String(err.message||"");
|
|
86
|
+
stack=typeof err.stack==="string"?err.stack:"";
|
|
87
|
+
}else if(typeof err==="string"){
|
|
88
|
+
message=err;
|
|
89
|
+
}else if(err!=null){
|
|
90
|
+
try{message=String(err);}catch(_){message="<unserializable error>";}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
name:name,
|
|
94
|
+
message:message,
|
|
95
|
+
frames:parseFrames(stack),
|
|
96
|
+
stack:stack,
|
|
97
|
+
kind:kind,
|
|
98
|
+
timestamp:Date.now(),
|
|
99
|
+
url:typeof location!=="undefined"?location.pathname+location.search:undefined,
|
|
100
|
+
userAgent:typeof navigator!=="undefined"?navigator.userAgent:undefined
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function esc(s){
|
|
105
|
+
if(s==null)return "";
|
|
106
|
+
return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function fileHref(file,line,col){
|
|
110
|
+
// vscode://file/<abs>:<line>:<col> — falls back gracefully when the
|
|
111
|
+
// host OS has no handler. Relative paths get "//" prefix so the URL
|
|
112
|
+
// still parses, even though the editor may refuse to jump.
|
|
113
|
+
var p=file;
|
|
114
|
+
if(!/^[a-zA-Z]:|^\\//.test(p))p="/"+p;
|
|
115
|
+
var u="vscode://file"+p;
|
|
116
|
+
if(line)u+=":"+line;
|
|
117
|
+
if(col)u+=":"+col;
|
|
118
|
+
return u;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function renderFrames(frames){
|
|
122
|
+
if(!frames||frames.length===0)return '<li class="mandu-dev-overlay__frame"><span class="mandu-dev-overlay__frame-fn"><no stack></span></li>';
|
|
123
|
+
var items=[];
|
|
124
|
+
for(var i=0;i<frames.length&&i<40;i++){
|
|
125
|
+
var f=frames[i];
|
|
126
|
+
var loc=esc(f.file)+(f.line?":"+f.line:"")+(f.column?":"+f.column:"");
|
|
127
|
+
var href=f.file?fileHref(f.file,f.line,f.column):"";
|
|
128
|
+
var locHtml=href?'<a class="mandu-dev-overlay__frame-link" href="'+esc(href)+'">'+loc+'</a>':loc;
|
|
129
|
+
items.push('<li class="mandu-dev-overlay__frame"><span class="mandu-dev-overlay__frame-fn">'+esc(f.fn||"<anonymous>")+'</span><span class="mandu-dev-overlay__frame-loc">'+locHtml+'</span></li>');
|
|
130
|
+
}
|
|
131
|
+
return items.join("");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function ensureStyleTag(){
|
|
135
|
+
if(document.getElementById("__mandu-dev-overlay-style"))return;
|
|
136
|
+
var s=document.createElement("style");
|
|
137
|
+
s.id="__mandu-dev-overlay-style";
|
|
138
|
+
s.textContent=STYLE;
|
|
139
|
+
(document.head||document.documentElement).appendChild(s);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function copyForAI(payload,btn){
|
|
143
|
+
var text="# Mandu dev error snapshot\\n\\n"
|
|
144
|
+
+"- kind: "+payload.kind+"\\n"
|
|
145
|
+
+"- name: "+payload.name+"\\n"
|
|
146
|
+
+"- message: "+payload.message+"\\n"
|
|
147
|
+
+"- url: "+(payload.url||"")+"\\n"
|
|
148
|
+
+"- routeId: "+(payload.routeId||"")+"\\n"
|
|
149
|
+
+"- timestamp: "+new Date(payload.timestamp).toISOString()+"\\n\\n"
|
|
150
|
+
+"## Stack\\n\\n\`\`\`\\n"+payload.stack+"\\n\`\`\`\\n";
|
|
151
|
+
var done=function(){
|
|
152
|
+
var prev=btn.textContent;
|
|
153
|
+
btn.textContent="Copied!";
|
|
154
|
+
setTimeout(function(){btn.textContent=prev;},1500);
|
|
155
|
+
};
|
|
156
|
+
if(navigator.clipboard&&navigator.clipboard.writeText){
|
|
157
|
+
navigator.clipboard.writeText(text).then(done,function(){
|
|
158
|
+
fallbackCopy(text);done();
|
|
159
|
+
});
|
|
160
|
+
}else{
|
|
161
|
+
fallbackCopy(text);done();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function fallbackCopy(text){
|
|
166
|
+
try{
|
|
167
|
+
var ta=document.createElement("textarea");
|
|
168
|
+
ta.value=text;ta.style.position="fixed";ta.style.left="-9999px";
|
|
169
|
+
document.body.appendChild(ta);ta.select();
|
|
170
|
+
try{document.execCommand("copy");}catch(_){}
|
|
171
|
+
document.body.removeChild(ta);
|
|
172
|
+
}catch(_){}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function mount(payload){
|
|
176
|
+
if(seen.has(payload))return;
|
|
177
|
+
seen.add(payload);
|
|
178
|
+
ensureStyleTag();
|
|
179
|
+
var root=document.getElementById("__mandu-dev-overlay-root");
|
|
180
|
+
if(!root){
|
|
181
|
+
root=document.createElement("div");
|
|
182
|
+
root.id="__mandu-dev-overlay-root";
|
|
183
|
+
root.className="mandu-dev-overlay";
|
|
184
|
+
root.setAttribute("role","dialog");
|
|
185
|
+
root.setAttribute("aria-modal","true");
|
|
186
|
+
root.setAttribute("aria-label","Mandu dev error overlay");
|
|
187
|
+
(document.body||document.documentElement).appendChild(root);
|
|
188
|
+
}
|
|
189
|
+
var kindLabel=({ssr:"SSR render failed",window:"Uncaught error",unhandledrejection:"Unhandled promise rejection",manual:"Error"})[payload.kind]||"Error";
|
|
190
|
+
var meta="";
|
|
191
|
+
if(payload.routeId||payload.url){
|
|
192
|
+
meta='<div class="mandu-dev-overlay__meta">'
|
|
193
|
+
+(payload.routeId?'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">Route</span><span class="mandu-dev-overlay__meta-value">'+esc(payload.routeId)+'</span></div>':'')
|
|
194
|
+
+(payload.url?'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">URL</span><span class="mandu-dev-overlay__meta-value">'+esc(payload.url)+'</span></div>':'')
|
|
195
|
+
+'<div class="mandu-dev-overlay__meta-item"><span class="mandu-dev-overlay__meta-label">Time</span><span class="mandu-dev-overlay__meta-value">'+esc(new Date(payload.timestamp).toLocaleTimeString())+'</span></div>'
|
|
196
|
+
+'</div>';
|
|
197
|
+
}
|
|
198
|
+
root.innerHTML='<div class="mandu-dev-overlay__panel">'
|
|
199
|
+
+'<div class="mandu-dev-overlay__header">'
|
|
200
|
+
+'<div class="mandu-dev-overlay__title">'
|
|
201
|
+
+'<span class="mandu-dev-overlay__kind">'+esc(kindLabel)+'</span>'
|
|
202
|
+
+'<span class="mandu-dev-overlay__name">'+esc(payload.name)+'</span>'
|
|
203
|
+
+'</div>'
|
|
204
|
+
+'<div class="mandu-dev-overlay__actions">'
|
|
205
|
+
+'<button type="button" class="mandu-dev-overlay__btn mandu-dev-overlay__btn--primary" data-action="copy">Copy for AI</button>'
|
|
206
|
+
+'<button type="button" class="mandu-dev-overlay__btn" data-action="close" aria-label="Close overlay">Close</button>'
|
|
207
|
+
+'</div>'
|
|
208
|
+
+'</div>'
|
|
209
|
+
+'<div class="mandu-dev-overlay__body">'
|
|
210
|
+
+'<p class="mandu-dev-overlay__message">'+esc(payload.message||"(no message)")+'</p>'
|
|
211
|
+
+meta
|
|
212
|
+
+'<h3 class="mandu-dev-overlay__section-title">Stack frames</h3>'
|
|
213
|
+
+'<ol class="mandu-dev-overlay__frames">'+renderFrames(payload.frames)+'</ol>'
|
|
214
|
+
+(payload.stack?'<h3 class="mandu-dev-overlay__section-title" style="margin-top:16px">Raw stack</h3><pre class="mandu-dev-overlay__stack">'+esc(payload.stack)+'</pre>':'')
|
|
215
|
+
+'</div>'
|
|
216
|
+
+'<div class="mandu-dev-overlay__footer">Dev-only overlay. Press <code>Esc</code> to dismiss. Never shipped in production builds.</div>'
|
|
217
|
+
+'</div>';
|
|
218
|
+
root.hidden=false;
|
|
219
|
+
|
|
220
|
+
var onClick=function(e){
|
|
221
|
+
var t=e.target;
|
|
222
|
+
if(!t||!t.getAttribute)return;
|
|
223
|
+
var a=t.getAttribute("data-action");
|
|
224
|
+
if(a==="close")hide();
|
|
225
|
+
else if(a==="copy")copyForAI(payload,t);
|
|
226
|
+
};
|
|
227
|
+
root.onclick=onClick;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function hide(){
|
|
231
|
+
var root=document.getElementById("__mandu-dev-overlay-root");
|
|
232
|
+
if(root)root.hidden=true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handle(err,kind,extra){
|
|
236
|
+
try{
|
|
237
|
+
var p=toPayload(err,kind);
|
|
238
|
+
if(extra&&typeof extra==="object"){
|
|
239
|
+
if(extra.routeId)p.routeId=extra.routeId;
|
|
240
|
+
if(extra.url)p.url=extra.url;
|
|
241
|
+
}
|
|
242
|
+
mount(p);
|
|
243
|
+
}catch(_){
|
|
244
|
+
// overlay must never throw — silent drop.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
window.addEventListener("error",function(ev){
|
|
249
|
+
if(ev&&ev.error)handle(ev.error,"window");
|
|
250
|
+
else if(ev)handle(ev.message||"Script error",(\"window\"));
|
|
251
|
+
});
|
|
252
|
+
window.addEventListener("unhandledrejection",function(ev){
|
|
253
|
+
handle(ev&&ev.reason,"unhandled-rejection");
|
|
254
|
+
});
|
|
255
|
+
window.addEventListener(EVT,function(ev){
|
|
256
|
+
var d=ev&&ev.detail;
|
|
257
|
+
if(!d)return;
|
|
258
|
+
if(d.name||d.message||d.stack){
|
|
259
|
+
var p={
|
|
260
|
+
name:d.name||"Error",
|
|
261
|
+
message:d.message||"",
|
|
262
|
+
frames:d.frames||parseFrames(d.stack||""),
|
|
263
|
+
stack:d.stack||"",
|
|
264
|
+
kind:d.kind||"manual",
|
|
265
|
+
timestamp:d.timestamp||Date.now(),
|
|
266
|
+
routeId:d.routeId,
|
|
267
|
+
url:d.url||(typeof location!=="undefined"?location.pathname+location.search:undefined),
|
|
268
|
+
userAgent:typeof navigator!=="undefined"?navigator.userAgent:undefined
|
|
269
|
+
};
|
|
270
|
+
mount(p);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
document.addEventListener("keydown",function(e){
|
|
274
|
+
if(e.key==="Escape")hide();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
function readEmbedded(){
|
|
278
|
+
var el=document.getElementById(PAYLOAD_ID);
|
|
279
|
+
if(!el)return;
|
|
280
|
+
try{
|
|
281
|
+
var data=JSON.parse(el.textContent||"null");
|
|
282
|
+
if(data&&typeof data==="object")mount(data);
|
|
283
|
+
}catch(_){}
|
|
284
|
+
}
|
|
285
|
+
if(document.readyState==="loading"){
|
|
286
|
+
document.addEventListener("DOMContentLoaded",readEmbedded);
|
|
287
|
+
}else{
|
|
288
|
+
readEmbedded();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Public surface (tests + user code)
|
|
292
|
+
window.__MANDU_DEV_OVERLAY__={show:function(e){handle(e,"manual");},hide:hide};
|
|
293
|
+
})();`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The serialized IIFE. Computed once at module-load. */
|
|
297
|
+
export const OVERLAY_CLIENT_SCRIPT = buildOverlayClientScript();
|
|
298
|
+
|
|
299
|
+
/** @internal test-only accessor so tests can recompute / inspect the script. */
|
|
300
|
+
export const _testOnly_buildOverlayClientScript = buildOverlayClientScript;
|