@vertz/ui-server 0.2.3 → 0.2.4

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.
@@ -23,6 +23,12 @@ interface BunDevServerOptions {
23
23
  projectRoot?: string;
24
24
  /** Log requests. @default true */
25
25
  logRequests?: boolean;
26
+ /**
27
+ * Editor for error overlay links. Auto-detected from VERTZ_EDITOR or EDITOR env vars.
28
+ * Supported: 'vscode' | 'cursor' | 'webstorm' | 'zed'
29
+ * @default 'vscode'
30
+ */
31
+ editor?: string;
26
32
  }
27
33
  interface ErrorDetail {
28
34
  message: string;
@@ -73,11 +79,12 @@ interface SSRPageHtmlOptions {
73
79
  bodyHtml: string;
74
80
  ssrData: unknown[];
75
81
  scriptTag: string;
82
+ editor?: string;
76
83
  }
77
84
  /**
78
85
  * Generate a full SSR HTML page with the given content, CSS, SSR data, and script tag.
79
86
  */
80
- declare function generateSSRPageHtml({ title, css, bodyHtml, ssrData, scriptTag }: SSRPageHtmlOptions): string;
87
+ declare function generateSSRPageHtml({ title, css, bodyHtml, ssrData, scriptTag, editor }: SSRPageHtmlOptions): string;
81
88
  interface FetchInterceptorOptions {
82
89
  apiHandler: (req: Request) => Promise<Response>;
83
90
  origin: string;
@@ -5,9 +5,143 @@ import {
5
5
 
6
6
  // src/bun-dev-server.ts
7
7
  import { execSync } from "child_process";
8
- import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, watch, writeFileSync } from "fs";
8
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, watch, writeFileSync as writeFileSync2 } from "fs";
9
9
  import { dirname, normalize, resolve } from "path";
10
10
 
11
+ // src/debug-logger.ts
12
+ import { appendFileSync, writeFileSync } from "fs";
13
+ import { join } from "path";
14
+ function createDebugLogger(logDir) {
15
+ const envValue = process.env.VERTZ_DEBUG;
16
+ if (!envValue) {
17
+ return {
18
+ log() {},
19
+ isEnabled() {
20
+ return false;
21
+ }
22
+ };
23
+ }
24
+ const enableAll = envValue === "1";
25
+ const enabledCategories = enableAll ? null : new Set(envValue.split(","));
26
+ const logFile = join(logDir, "debug.log");
27
+ writeFileSync(logFile, "");
28
+ function isEnabled(category) {
29
+ return enableAll || enabledCategories.has(category);
30
+ }
31
+ return {
32
+ log(category, message, data) {
33
+ if (!isEnabled(category))
34
+ return;
35
+ const entry = { cat: category, msg: message, ...data };
36
+ appendFileSync(logFile, JSON.stringify(entry) + `
37
+ `);
38
+ },
39
+ isEnabled
40
+ };
41
+ }
42
+
43
+ // src/diagnostics-collector.ts
44
+ class DiagnosticsCollector {
45
+ startTime = Date.now();
46
+ pluginFilter = "";
47
+ pluginHmr = false;
48
+ pluginFastRefresh = false;
49
+ processedFilesSet = new Set;
50
+ processedCount = 0;
51
+ ssrModuleStatus = "pending";
52
+ ssrLastReloadTime = null;
53
+ ssrLastReloadDurationMs = null;
54
+ ssrLastReloadError = null;
55
+ ssrReloadCount = 0;
56
+ ssrFailedReloadCount = 0;
57
+ hmrBundledScriptUrl = null;
58
+ hmrBootstrapDiscovered = false;
59
+ errorCurrent = null;
60
+ errorLastCategory = null;
61
+ errorLastMessage = null;
62
+ wsConnectedClients = 0;
63
+ watcherLastChangedFile = null;
64
+ watcherLastChangeTime = null;
65
+ recordPluginConfig(filter, hmr, fastRefresh) {
66
+ this.pluginFilter = filter;
67
+ this.pluginHmr = hmr;
68
+ this.pluginFastRefresh = fastRefresh;
69
+ }
70
+ recordPluginProcess(file) {
71
+ this.processedFilesSet.add(file);
72
+ this.processedCount++;
73
+ }
74
+ recordSSRReload(success, durationMs, error) {
75
+ this.ssrReloadCount++;
76
+ this.ssrLastReloadTime = new Date().toISOString();
77
+ this.ssrLastReloadDurationMs = durationMs;
78
+ if (success) {
79
+ this.ssrModuleStatus = "loaded";
80
+ this.ssrLastReloadError = null;
81
+ } else {
82
+ this.ssrModuleStatus = "error";
83
+ this.ssrLastReloadError = error ?? null;
84
+ this.ssrFailedReloadCount++;
85
+ }
86
+ }
87
+ recordHMRAssets(bundledScriptUrl, bootstrapDiscovered) {
88
+ this.hmrBundledScriptUrl = bundledScriptUrl;
89
+ this.hmrBootstrapDiscovered = bootstrapDiscovered;
90
+ }
91
+ recordError(category, message) {
92
+ this.errorCurrent = category;
93
+ this.errorLastCategory = category;
94
+ this.errorLastMessage = message;
95
+ }
96
+ recordErrorClear() {
97
+ this.errorCurrent = null;
98
+ }
99
+ recordWebSocketChange(count) {
100
+ this.wsConnectedClients = count;
101
+ }
102
+ recordFileChange(file) {
103
+ this.watcherLastChangedFile = file;
104
+ this.watcherLastChangeTime = new Date().toISOString();
105
+ }
106
+ getSnapshot() {
107
+ return {
108
+ status: "ok",
109
+ uptime: (Date.now() - this.startTime) / 1000,
110
+ plugin: {
111
+ filter: this.pluginFilter,
112
+ hmr: this.pluginHmr,
113
+ fastRefresh: this.pluginFastRefresh,
114
+ processedFiles: Array.from(this.processedFilesSet),
115
+ processedCount: this.processedCount
116
+ },
117
+ ssr: {
118
+ moduleStatus: this.ssrModuleStatus,
119
+ lastReloadTime: this.ssrLastReloadTime,
120
+ lastReloadDurationMs: this.ssrLastReloadDurationMs,
121
+ lastReloadError: this.ssrLastReloadError,
122
+ reloadCount: this.ssrReloadCount,
123
+ failedReloadCount: this.ssrFailedReloadCount
124
+ },
125
+ hmr: {
126
+ bundledScriptUrl: this.hmrBundledScriptUrl,
127
+ bootstrapDiscovered: this.hmrBootstrapDiscovered
128
+ },
129
+ errors: {
130
+ current: this.errorCurrent,
131
+ lastCategory: this.errorLastCategory,
132
+ lastMessage: this.errorLastMessage
133
+ },
134
+ websocket: {
135
+ connectedClients: this.wsConnectedClients
136
+ },
137
+ watcher: {
138
+ lastChangedFile: this.watcherLastChangedFile,
139
+ lastChangeTime: this.watcherLastChangeTime
140
+ }
141
+ };
142
+ }
143
+ }
144
+
11
145
  // src/source-map-resolver.ts
12
146
  import { readFileSync } from "fs";
13
147
  import { resolve as resolvePath } from "path";
@@ -1114,144 +1248,168 @@ function parseHMRAssets(html) {
1114
1248
  bootstrapScript: bootstrapMatch?.[1] ? `<script>${bootstrapMatch[1]}</script>` : null
1115
1249
  };
1116
1250
  }
1117
- var ERROR_CHANNEL_SCRIPT = [
1118
- "<script>(function(){",
1119
- "var V=window.__vertz_overlay={};",
1120
- "V._ws=null;",
1121
- "V._src=null;",
1122
- "V._hadClientError=false;",
1123
- "V._needsReload=false;",
1124
- 'var rts=sessionStorage.getItem("__vertz_recovering");',
1125
- "V._recovering=rts&&(Date.now()-Number(rts)<10000);",
1126
- 'if(V._recovering)sessionStorage.removeItem("__vertz_recovering");',
1127
- "var _reload=location.reload.bind(location);",
1128
- "try{location.reload=function(){if(V._src){V._needsReload=true;return}_reload()}}catch(e){}",
1129
- "V.esc=function(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')};",
1130
- "V.formatErrors=function(errs){",
1131
- `if(!errs||!errs.length)return'<p style="margin:0;color:var(--ve-muted);font-size:12px">Check your terminal for details.</p>';`,
1132
- "var groups=[],seen={};",
1133
- "errs.forEach(function(e){",
1134
- "var k=(e.file||'')+'|'+(e.line||0);",
1135
- "if(!seen[k]){seen[k]={file:e.file,absFile:e.absFile,line:e.line,lineText:e.lineText,msgs:[]};groups.push(seen[k])}",
1136
- "seen[k].msgs.push({message:e.message,column:e.column})});",
1137
- "return groups.map(function(g){var h='';",
1138
- "if(g.file){",
1139
- "var loc=V.esc(g.file)+(g.line?':'+g.line:'');",
1140
- "var href=g.absFile?'vscode://file/'+encodeURI(g.absFile)+(g.line?':'+g.line:''):'';",
1141
- `h+=href?'<a href="'+href+'" style="color:var(--ve-link);font-size:12px;text-decoration:underline;text-underline-offset:2px">'+loc+'</a>'` + `:'<span style="color:var(--ve-link);font-size:12px">'+loc+'</span>';`,
1142
- "h+='<br>'}",
1143
- `g.msgs.forEach(function(m){h+='<div style="color:var(--ve-error);font-size:12px;margin:2px 0">'+V.esc(m.message)+'</div>'});`,
1144
- `if(g.lineText){h+='<pre style="margin:4px 0 0;color:var(--ve-code);font-size:11px;background:var(--ve-code-bg);border-radius:4px;padding:6px 8px;overflow-x:auto;border:1px solid var(--ve-border)">'+V.esc(g.lineText)+'</pre>'}`,
1145
- `return'<div style="margin-bottom:10px">'+h+'</div>'}).join('')};`,
1146
- "V.formatStack=function(frames){",
1147
- "if(!frames||!frames.length)return'';",
1148
- `var h='<div style="margin-top:8px;border-top:1px solid var(--ve-border);padding-top:8px">';`,
1149
- "var visible=frames.slice(0,3);var hidden=frames.slice(3);",
1150
- "visible.forEach(function(f){h+=V._renderFrame(f)});",
1151
- `if(hidden.length){h+='<details style="margin-top:2px"><summary style="color:var(--ve-muted);font-size:11px;cursor:pointer;list-style:none">'`,
1152
- "+hidden.length+' more frame'+(hidden.length>1?'s':'')+'</summary>';",
1153
- "hidden.forEach(function(f){h+=V._renderFrame(f)});h+='</details>'}",
1154
- "return h+'</div>'};",
1155
- "V._renderFrame=function(f){",
1156
- "var name=f.functionName||'(anonymous)';",
1157
- "var loc=V.esc(f.file)+(f.line?':'+f.line:'');",
1158
- "var isSrc=f.file&&f.file.indexOf('src/')!==-1&&f.file.indexOf('node_modules')===-1;",
1159
- "var color=isSrc?'var(--ve-fg)':'var(--ve-muted)';",
1160
- "var href=f.absFile?'vscode://file/'+encodeURI(f.absFile)+(f.line?':'+f.line:''):'';",
1161
- `var link=href?'<a href="'+href+'" style="color:var(--ve-link);text-decoration:underline;text-underline-offset:2px">'+loc+'</a>':'<span>'+loc+'</span>';`,
1162
- `return'<div style="font-size:11px;color:'+color+';margin:1px 0;font-family:ui-monospace,monospace">'+V.esc(name)+' '+link+'</div>'};`,
1163
- "V.removeOverlay=function(){V._src=null;var e=document.getElementById('__vertz_error');if(e)e.remove();" + "var d=document.getElementById('__vertz_error_data');if(d)d.remove()};",
1164
- "V.showOverlay=function(t,body,payload,src){",
1165
- "V.removeOverlay();",
1166
- "V._src=src||'ws';",
1167
- "var d=document,c=d.createElement('div');",
1168
- "c.id='__vertz_error';",
1169
- "c.style.cssText='",
1170
- "--ve-bg:hsl(0 0% 100%);--ve-fg:hsl(0 0% 9%);--ve-muted:hsl(0 0% 45%);",
1171
- "--ve-error:hsl(0 72% 51%);--ve-link:hsl(221 83% 53%);--ve-border:hsl(0 0% 90%);",
1172
- "--ve-code:hsl(24 70% 45%);--ve-code-bg:hsl(0 0% 97%);--ve-btn:hsl(0 0% 9%);--ve-btn-fg:hsl(0 0% 100%);",
1173
- "position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;",
1174
- "background:var(--ve-bg);color:var(--ve-fg);border-radius:8px;padding:14px 16px;",
1175
- "max-width:480px;width:calc(100% - 32px);font-family:ui-sans-serif,system-ui,sans-serif;",
1176
- "box-shadow:0 4px 24px rgba(0,0,0,0.12),0 1px 3px rgba(0,0,0,0.08);border:1px solid var(--ve-border)';",
1177
- "var st=d.createElement('style');",
1178
- "st.textContent='@media(prefers-color-scheme:dark){#__vertz_error{",
1179
- "--ve-bg:hsl(0 0% 7%);--ve-fg:hsl(0 0% 93%);--ve-muted:hsl(0 0% 55%);",
1180
- "--ve-error:hsl(0 72% 65%);--ve-link:hsl(217 91% 70%);--ve-border:hsl(0 0% 18%);",
1181
- "--ve-code:hsl(36 80% 65%);--ve-code-bg:hsl(0 0% 11%);--ve-btn:hsl(0 0% 93%);--ve-btn-fg:hsl(0 0% 7%)}}';",
1182
- "d.head.appendChild(st);",
1183
- `c.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'`,
1184
- `+'<span style="font-size:13px;font-weight:600;color:var(--ve-error)">'+V.esc(t)+'</span>'`,
1185
- `+'<button id="__vertz_retry" style="background:var(--ve-btn);color:var(--ve-btn-fg);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;font-weight:500">Retry</button>'`,
1186
- "+'</div>'+body;",
1187
- "(d.body||d.documentElement).appendChild(c);",
1188
- "d.getElementById('__vertz_retry').onclick=function(){location.reload()};",
1189
- "if(payload){var s=d.createElement('script');s.type='application/json';s.id='__vertz_error_data';s.textContent=JSON.stringify(payload);(d.body||d.documentElement).appendChild(s)}};",
1190
- "var delay=1000,maxDelay=30000;",
1191
- "function connect(){",
1192
- "var p=location.protocol==='https:'?'wss:':'ws:';",
1193
- "var ws=new WebSocket(p+'//'+location.host+'/__vertz_errors');",
1194
- "V._ws=ws;",
1195
- "ws.onmessage=function(e){",
1196
- "try{var m=JSON.parse(e.data);",
1197
- "if(m.type==='error'){",
1198
- "if(V._recovering)return;",
1199
- "V.showOverlay(m.category==='build'?'Build failed':m.category==='ssr'?'SSR error':m.category==='resolve'?'Module not found':'Runtime error',V.formatErrors(m.errors)+V.formatStack(m.parsedStack),m,'ws')}",
1200
- "else if(m.type==='clear'){",
1201
- "if(V._needsReload){V._needsReload=false;V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
1202
- "var a=document.getElementById('app');",
1203
- "if(!a||a.innerHTML.length<50){V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
1204
- "if(V._hadClientError)return;",
1205
- "V.removeOverlay()}",
1206
- "else if(m.type==='connected'){delay=1000}",
1207
- "}catch(ex){}};",
1208
- "ws.onclose=function(){V._ws=null;setTimeout(function(){delay=Math.min(delay*2,maxDelay);connect()},delay)};",
1209
- "ws.onerror=function(){ws.close()}}",
1210
- "connect();",
1211
- "V._sendResolveStack=function(stack,msg){",
1212
- 'if(V._ws&&V._ws.readyState===1){try{V._ws.send(JSON.stringify({type:"resolve-stack",stack:stack,message:msg}))}catch(e){}}};',
1213
- "function showRuntimeError(title,errors,payload){",
1214
- "var a=document.getElementById('app');",
1215
- "if(V._recovering&&a&&a.innerHTML.length>50)return;",
1216
- "if(V._recovering)V._recovering=false;",
1217
- "V._hadClientError=true;",
1218
- "V.showOverlay(title,V.formatErrors(errors),payload,'client')}",
1219
- "if(V._recovering){setTimeout(function(){V._recovering=false},5000)}",
1220
- "window.addEventListener('error',function(e){",
1221
- "var msg=e.message||String(e.error);",
1222
- "var stk=e.error&&e.error.stack;",
1223
- "if(stk){V._sendResolveStack(stk,msg)}",
1224
- "var f=e.filename,isBundled=f&&(f.indexOf('/_bun/')!==-1||f.indexOf('blob:')!==-1);",
1225
- "var errInfo=isBundled?{message:msg}:{message:msg,file:f,line:e.lineno,column:e.colno};",
1226
- "showRuntimeError('Runtime error',[errInfo],{type:'error',category:'runtime',errors:[errInfo]})});",
1227
- "window.addEventListener('unhandledrejection',function(e){",
1228
- "var m=e.reason instanceof Error?e.reason.message:String(e.reason);",
1229
- "var stk=e.reason&&e.reason.stack;",
1230
- "if(stk){V._sendResolveStack(stk,m)}",
1231
- "showRuntimeError('Runtime error',[{message:m}],{type:'error',category:'runtime',errors:[{message:m}]})});",
1232
- "var hmrErr=false,origCE=console.error,origCL=console.log;",
1233
- "console.error=function(){",
1234
- "var t=Array.prototype.join.call(arguments,' ');",
1235
- "var hmr=t.match(/\\[vertz-hmr\\] Error re-mounting (\\w+): ([\\s\\S]*?)(?:\\n\\s+at |$)/);",
1236
- "if(hmr){hmrErr=true;V._hadClientError=true;",
1237
- "V.showOverlay('Runtime error',V.formatErrors([{message:hmr[2].split('\\n')[0]}]),{type:'error',category:'runtime',errors:[{message:hmr[2].split('\\n')[0]}]},'client')}",
1238
- "origCE.apply(console,arguments)};",
1239
- "console.log=function(){",
1240
- "var t=Array.prototype.join.call(arguments,' ');",
1241
- "if(t.indexOf('[vertz-hmr] Hot updated:')!==-1){",
1242
- "if(!hmrErr&&V._src==='client'){",
1243
- "V._hadClientError=false;V.removeOverlay();setTimeout(function(){var a=document.getElementById('app');if(!a||a.innerHTML.length<50){V._needsReload=true}},500)}",
1244
- "hmrErr=false}",
1245
- "origCL.apply(console,arguments)};",
1246
- "})()</script>"
1247
- ].join("");
1251
+ function detectEditor(explicit) {
1252
+ if (explicit)
1253
+ return explicit;
1254
+ const env = process.env.VERTZ_EDITOR || process.env.EDITOR || "";
1255
+ const lower = env.toLowerCase();
1256
+ if (lower.includes("cursor"))
1257
+ return "cursor";
1258
+ if (lower.includes("zed"))
1259
+ return "zed";
1260
+ if (lower.includes("webstorm") || lower.includes("idea"))
1261
+ return "webstorm";
1262
+ return "vscode";
1263
+ }
1264
+ function editorHrefJs(editor) {
1265
+ if (editor === "webstorm" || editor === "idea") {
1266
+ return `V._editorHref=function(f,l){if(!f)return'';return'${editor}://open?file='+encodeURI(f)+(l?'&line='+l:'')};`;
1267
+ }
1268
+ return `V._editorHref=function(f,l){if(!f)return'';return'${editor}://file/'+encodeURI(f)+(l?':'+l:'')};`;
1269
+ }
1270
+ function buildErrorChannelScript(editor) {
1271
+ return [
1272
+ "<style>bun-hmr{display:none!important}</style>",
1273
+ "<script>(function(){",
1274
+ "var V=window.__vertz_overlay={};",
1275
+ editorHrefJs(editor),
1276
+ "V._ws=null;",
1277
+ "V._src=null;",
1278
+ "V._hadClientError=false;",
1279
+ "V._needsReload=false;",
1280
+ 'var rts=sessionStorage.getItem("__vertz_recovering");',
1281
+ "V._recovering=rts&&(Date.now()-Number(rts)<10000);",
1282
+ 'if(V._recovering)sessionStorage.removeItem("__vertz_recovering");',
1283
+ "var _reload=location.reload.bind(location);",
1284
+ "try{location.reload=function(){if(V._src){V._needsReload=true;return}_reload()}}catch(e){}",
1285
+ "V.esc=function(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')};",
1286
+ "V.formatErrors=function(errs){",
1287
+ `if(!errs||!errs.length)return'<p style="margin:0;color:var(--ve-muted);font-size:12px">Check your terminal for details.</p>';`,
1288
+ "var groups=[],seen={};",
1289
+ "errs.forEach(function(e){",
1290
+ "var k=(e.file||'')+'|'+(e.line||0);",
1291
+ "if(!seen[k]){seen[k]={file:e.file,absFile:e.absFile,line:e.line,lineText:e.lineText,msgs:[]};groups.push(seen[k])}",
1292
+ "seen[k].msgs.push({message:e.message,column:e.column})});",
1293
+ "return groups.map(function(g){var h='';",
1294
+ "if(g.file){",
1295
+ "var loc=V.esc(g.file)+(g.line?':'+g.line:'');",
1296
+ "var href=V._editorHref(g.absFile,g.line);",
1297
+ `h+=href?'<a href="'+href+'" style="color:var(--ve-link);font-size:12px;text-decoration:underline;text-underline-offset:2px">'+loc+'</a>'` + `:'<span style="color:var(--ve-link);font-size:12px">'+loc+'</span>';`,
1298
+ "h+='<br>'}",
1299
+ `g.msgs.forEach(function(m){h+='<div style="color:var(--ve-error);font-size:12px;margin:2px 0">'+V.esc(m.message)+'</div>'});`,
1300
+ `if(g.lineText){h+='<pre style="margin:4px 0 0;color:var(--ve-code);font-size:11px;background:var(--ve-code-bg);border-radius:4px;padding:6px 8px;overflow-x:auto;border:1px solid var(--ve-border)">'+V.esc(g.lineText)+'</pre>'}`,
1301
+ `return'<div style="margin-bottom:10px">'+h+'</div>'}).join('')};`,
1302
+ "V.formatStack=function(frames){",
1303
+ "if(!frames||!frames.length)return'';",
1304
+ `var h='<div style="margin-top:8px;border-top:1px solid var(--ve-border);padding-top:8px">';`,
1305
+ "var visible=frames.slice(0,3);var hidden=frames.slice(3);",
1306
+ "visible.forEach(function(f){h+=V._renderFrame(f)});",
1307
+ `if(hidden.length){h+='<details style="margin-top:2px"><summary style="color:var(--ve-muted);font-size:11px;cursor:pointer;list-style:none">'`,
1308
+ "+hidden.length+' more frame'+(hidden.length>1?'s':'')+'</summary>';",
1309
+ "hidden.forEach(function(f){h+=V._renderFrame(f)});h+='</details>'}",
1310
+ "return h+'</div>'};",
1311
+ "V._renderFrame=function(f){",
1312
+ "var name=f.functionName||'(anonymous)';",
1313
+ "var loc=V.esc(f.file)+(f.line?':'+f.line:'');",
1314
+ "var isSrc=f.file&&f.file.indexOf('src/')!==-1&&f.file.indexOf('node_modules')===-1;",
1315
+ "var color=isSrc?'var(--ve-fg)':'var(--ve-muted)';",
1316
+ "var href=V._editorHref(f.absFile,f.line);",
1317
+ `var link=href?'<a href="'+href+'" style="color:var(--ve-link);text-decoration:underline;text-underline-offset:2px">'+loc+'</a>':'<span>'+loc+'</span>';`,
1318
+ `return'<div style="font-size:11px;color:'+color+';margin:1px 0;font-family:ui-monospace,monospace">'+V.esc(name)+' '+link+'</div>'};`,
1319
+ "V.removeOverlay=function(){V._src=null;var e=document.getElementById('__vertz_error');if(e)e.remove();" + "var d=document.getElementById('__vertz_error_data');if(d)d.remove()};",
1320
+ "V.showOverlay=function(t,body,payload,src){",
1321
+ "V.removeOverlay();",
1322
+ "V._src=src||'ws';",
1323
+ "var d=document,c=d.createElement('div');",
1324
+ "c.id='__vertz_error';",
1325
+ "c.style.cssText='",
1326
+ "--ve-bg:hsl(0 0% 100%);--ve-fg:hsl(0 0% 9%);--ve-muted:hsl(0 0% 45%);",
1327
+ "--ve-error:hsl(0 72% 51%);--ve-link:hsl(221 83% 53%);--ve-border:hsl(0 0% 90%);",
1328
+ "--ve-code:hsl(24 70% 45%);--ve-code-bg:hsl(0 0% 97%);--ve-btn:hsl(0 0% 9%);--ve-btn-fg:hsl(0 0% 100%);",
1329
+ "position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;",
1330
+ "background:var(--ve-bg);color:var(--ve-fg);border-radius:8px;padding:14px 16px;",
1331
+ "max-width:480px;width:calc(100% - 32px);font-family:ui-sans-serif,system-ui,sans-serif;",
1332
+ "box-shadow:0 4px 24px rgba(0,0,0,0.12),0 1px 3px rgba(0,0,0,0.08);border:1px solid var(--ve-border)';",
1333
+ "var st=d.createElement('style');",
1334
+ "st.textContent='@media(prefers-color-scheme:dark){#__vertz_error{",
1335
+ "--ve-bg:hsl(0 0% 7%);--ve-fg:hsl(0 0% 93%);--ve-muted:hsl(0 0% 55%);",
1336
+ "--ve-error:hsl(0 72% 65%);--ve-link:hsl(217 91% 70%);--ve-border:hsl(0 0% 18%);",
1337
+ "--ve-code:hsl(36 80% 65%);--ve-code-bg:hsl(0 0% 11%);--ve-btn:hsl(0 0% 93%);--ve-btn-fg:hsl(0 0% 7%)}}';",
1338
+ "d.head.appendChild(st);",
1339
+ `c.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'`,
1340
+ `+'<span style="font-size:13px;font-weight:600;color:var(--ve-error)">'+V.esc(t)+'</span>'`,
1341
+ `+'<button id="__vertz_retry" style="background:var(--ve-btn);color:var(--ve-btn-fg);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;font-weight:500">Retry</button>'`,
1342
+ "+'</div>'+body;",
1343
+ "(d.body||d.documentElement).appendChild(c);",
1344
+ "d.getElementById('__vertz_retry').onclick=function(){location.reload()};",
1345
+ "if(payload){var s=d.createElement('script');s.type='application/json';s.id='__vertz_error_data';s.textContent=JSON.stringify(payload);(d.body||d.documentElement).appendChild(s)}};",
1346
+ "var delay=1000,maxDelay=30000;",
1347
+ "function connect(){",
1348
+ "var p=location.protocol==='https:'?'wss:':'ws:';",
1349
+ "var ws=new WebSocket(p+'//'+location.host+'/__vertz_errors');",
1350
+ "V._ws=ws;",
1351
+ "ws.onmessage=function(e){",
1352
+ "try{var m=JSON.parse(e.data);",
1353
+ "if(m.type==='error'){",
1354
+ "if(V._recovering)return;",
1355
+ "V.showOverlay(m.category==='build'?'Build failed':m.category==='ssr'?'SSR error':m.category==='resolve'?'Module not found':'Runtime error',V.formatErrors(m.errors)+V.formatStack(m.parsedStack),m,'ws')}",
1356
+ "else if(m.type==='clear'){",
1357
+ "if(V._needsReload){V._needsReload=false;V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
1358
+ "var a=document.getElementById('app');",
1359
+ "if(!a||a.innerHTML.length<50){V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
1360
+ "if(V._hadClientError)return;",
1361
+ "V.removeOverlay()}",
1362
+ "else if(m.type==='connected'){delay=1000}",
1363
+ "}catch(ex){}};",
1364
+ "ws.onclose=function(){V._ws=null;setTimeout(function(){delay=Math.min(delay*2,maxDelay);connect()},delay)};",
1365
+ "ws.onerror=function(){ws.close()}}",
1366
+ "connect();",
1367
+ "V._sendResolveStack=function(stack,msg){",
1368
+ 'if(V._ws&&V._ws.readyState===1){try{V._ws.send(JSON.stringify({type:"resolve-stack",stack:stack,message:msg}))}catch(e){}}};',
1369
+ "function showRuntimeError(title,errors,payload){",
1370
+ "var a=document.getElementById('app');",
1371
+ "if(V._recovering&&a&&a.innerHTML.length>50)return;",
1372
+ "if(V._recovering)V._recovering=false;",
1373
+ "V._hadClientError=true;",
1374
+ "V.showOverlay(title,V.formatErrors(errors),payload,'client')}",
1375
+ "if(V._recovering){setTimeout(function(){V._recovering=false},5000)}",
1376
+ "window.addEventListener('error',function(e){",
1377
+ "var msg=e.message||String(e.error);",
1378
+ "var stk=e.error&&e.error.stack;",
1379
+ "if(stk){V._sendResolveStack(stk,msg)}",
1380
+ "var f=e.filename,isBundled=f&&(f.indexOf('/_bun/')!==-1||f.indexOf('blob:')!==-1);",
1381
+ "var errInfo=isBundled?{message:msg}:{message:msg,file:f,line:e.lineno,column:e.colno};",
1382
+ "showRuntimeError('Runtime error',[errInfo],{type:'error',category:'runtime',errors:[errInfo]})});",
1383
+ "window.addEventListener('unhandledrejection',function(e){",
1384
+ "var m=e.reason instanceof Error?e.reason.message:String(e.reason);",
1385
+ "var stk=e.reason&&e.reason.stack;",
1386
+ "if(stk){V._sendResolveStack(stk,m)}",
1387
+ "showRuntimeError('Runtime error',[{message:m}],{type:'error',category:'runtime',errors:[{message:m}]})});",
1388
+ "var hmrErr=false,origCE=console.error,origCL=console.log;",
1389
+ "console.error=function(){",
1390
+ "var t=Array.prototype.join.call(arguments,' ');",
1391
+ "var hmr=t.match(/\\[vertz-hmr\\] Error re-mounting (\\w+): ([\\s\\S]*?)(?:\\n\\s+at |$)/);",
1392
+ "if(hmr){hmrErr=true;V._hadClientError=true;",
1393
+ "V.showOverlay('Runtime error',V.formatErrors([{message:hmr[2].split('\\n')[0]}]),{type:'error',category:'runtime',errors:[{message:hmr[2].split('\\n')[0]}]},'client')}",
1394
+ "origCE.apply(console,arguments)};",
1395
+ "console.log=function(){",
1396
+ "var t=Array.prototype.join.call(arguments,' ');",
1397
+ "if(t.indexOf('[vertz-hmr] Hot updated:')!==-1){",
1398
+ "if(!hmrErr&&V._src==='client'){",
1399
+ "V._hadClientError=false;V.removeOverlay();setTimeout(function(){var a=document.getElementById('app');if(!a||a.innerHTML.length<50){V._needsReload=true}},500)}",
1400
+ "hmrErr=false}",
1401
+ "origCL.apply(console,arguments)};",
1402
+ "})()</script>"
1403
+ ].join("");
1404
+ }
1248
1405
  var RELOAD_GUARD_SCRIPT = `<script>(function(){var K="__vertz_reload_count",T="__vertz_reload_ts",s=sessionStorage,n=parseInt(s.getItem(K)||"0",10),t=parseInt(s.getItem(T)||"0",10),now=Date.now();if(now-t<100){n++}else{n=1}s.setItem(K,String(n));s.setItem(T,String(now));if(n>10){window.stop();s.removeItem(K);s.removeItem(T);var d=document,o=d.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.6)";var c=d.createElement("div");c.style.cssText="background:#fff;color:#1a1a1a;border-radius:12px;padding:32px;max-width:480px;width:90%;font-family:system-ui,sans-serif;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3)";c.innerHTML='<div style="font-size:40px;margin-bottom:16px">&#9888;&#65039;</div><h2 style="margin:0 0 8px;font-size:20px">Dev server connection lost</h2><p style="margin:0 0 20px;color:#666;font-size:14px;line-height:1.5">The page reloaded 10+ times in rapid succession. This usually means the dev server stopped or a build failed.</p><button id="__vertz_retry" style="background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 24px;font-size:14px;cursor:pointer">Retry</button>';o.appendChild(c);(d.body||d.documentElement).appendChild(o);d.getElementById("__vertz_retry").onclick=function(){location.href=location.href}}else{setTimeout(function(){s.removeItem(K);s.removeItem(T)},5e3)}})()</script>`;
1249
1406
  function generateSSRPageHtml({
1250
1407
  title,
1251
1408
  css,
1252
1409
  bodyHtml,
1253
1410
  ssrData,
1254
- scriptTag
1411
+ scriptTag,
1412
+ editor = "vscode"
1255
1413
  }) {
1256
1414
  const ssrDataScript = ssrData.length > 0 ? `<script>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>` : "";
1257
1415
  return `<!doctype html>
@@ -1261,7 +1419,7 @@ function generateSSRPageHtml({
1261
1419
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1262
1420
  <title>${title}</title>
1263
1421
  ${css}
1264
- ${ERROR_CHANNEL_SCRIPT}
1422
+ ${buildErrorChannelScript(editor)}
1265
1423
  ${RELOAD_GUARD_SCRIPT}
1266
1424
  </head>
1267
1425
  <body>
@@ -1339,8 +1497,14 @@ function createBunDevServer(options) {
1339
1497
  clientEntry: clientEntryOption,
1340
1498
  title = "Vertz App",
1341
1499
  projectRoot = process.cwd(),
1342
- logRequests = true
1500
+ logRequests = true,
1501
+ editor: editorOption
1343
1502
  } = options;
1503
+ const editor = detectEditor(editorOption);
1504
+ const devDir = resolve(projectRoot, ".vertz", "dev");
1505
+ mkdirSync(devDir, { recursive: true });
1506
+ const logger = createDebugLogger(devDir);
1507
+ const diagnostics = new DiagnosticsCollector;
1344
1508
  let server = null;
1345
1509
  let srcWatcherRef = null;
1346
1510
  let refreshTimeout = null;
@@ -1379,6 +1543,8 @@ function createBunDevServer(options) {
1379
1543
  return;
1380
1544
  }
1381
1545
  currentError = { category, errors };
1546
+ diagnostics.recordError(category, errors[0]?.message ?? "");
1547
+ logger.log("ws", "broadcast-error", { category, errorCount: errors.length });
1382
1548
  const msg = JSON.stringify({ type: "error", category, errors });
1383
1549
  for (const ws of wsClients) {
1384
1550
  ws.sendText(msg);
@@ -1388,6 +1554,7 @@ function createBunDevServer(options) {
1388
1554
  if (currentError === null && !pendingRuntimeError)
1389
1555
  return;
1390
1556
  currentError = null;
1557
+ diagnostics.recordErrorClear();
1391
1558
  if (runtimeDebounceTimer) {
1392
1559
  clearTimeout(runtimeDebounceTimer);
1393
1560
  runtimeDebounceTimer = null;
@@ -1403,6 +1570,7 @@ function createBunDevServer(options) {
1403
1570
  if (currentError === null && !pendingRuntimeError)
1404
1571
  return;
1405
1572
  currentError = null;
1573
+ diagnostics.recordErrorClear();
1406
1574
  if (runtimeDebounceTimer) {
1407
1575
  clearTimeout(runtimeDebounceTimer);
1408
1576
  runtimeDebounceTimer = null;
@@ -1571,7 +1739,9 @@ function createBunDevServer(options) {
1571
1739
  });
1572
1740
  const { plugin: serverPlugin } = createVertzBunPlugin({
1573
1741
  hmr: false,
1574
- fastRefresh: false
1742
+ fastRefresh: false,
1743
+ logger,
1744
+ diagnostics
1575
1745
  });
1576
1746
  plugin(serverPlugin);
1577
1747
  let ssrMod;
@@ -1584,19 +1754,21 @@ function createBunDevServer(options) {
1584
1754
  console.error("[Server] Failed to load SSR module:", e);
1585
1755
  process.exit(1);
1586
1756
  }
1587
- const devDir = resolve(projectRoot, ".vertz", "dev");
1588
1757
  mkdirSync(devDir, { recursive: true });
1589
- const frRuntimePath = "../../node_modules/@vertz/ui-server/dist/bun-plugin/fast-refresh-runtime.js";
1758
+ const frInitPath = resolve(devDir, "fast-refresh-init.ts");
1759
+ writeFileSync2(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
1760
+ if (import.meta.hot) import.meta.hot.accept();
1761
+ `);
1590
1762
  const hmrShellHtml = `<!doctype html>
1591
1763
  <html lang="en"><head>
1592
1764
  <meta charset="UTF-8" />
1593
1765
  <title>HMR Shell</title>
1594
1766
  </head><body>
1595
- <script type="module" src="${frRuntimePath}"></script>
1767
+ <script type="module" src="./fast-refresh-init.ts"></script>
1596
1768
  <script type="module" src="${clientSrc}"></script>
1597
1769
  </body></html>`;
1598
1770
  const hmrShellPath = resolve(devDir, "hmr-shell.html");
1599
- writeFileSync(hmrShellPath, hmrShellHtml);
1771
+ writeFileSync2(hmrShellPath, hmrShellHtml);
1600
1772
  const hmrShellModule = __require(hmrShellPath);
1601
1773
  setupOpenAPIWatcher();
1602
1774
  let bundledScriptUrl = null;
@@ -1663,6 +1835,9 @@ function createBunDevServer(options) {
1663
1835
  return Response.json({ errors: [{ message: msg }] });
1664
1836
  }
1665
1837
  }
1838
+ if (pathname === "/__vertz_diagnostics") {
1839
+ return Response.json(diagnostics.getSnapshot());
1840
+ }
1666
1841
  if (openapi && request.method === "GET" && pathname === "/api/openapi.json") {
1667
1842
  return serveOpenAPISpec();
1668
1843
  }
@@ -1727,14 +1902,22 @@ data: {}
1727
1902
  });
1728
1903
  }
1729
1904
  try {
1905
+ logger.log("ssr", "render-start", { url: pathname });
1906
+ const ssrStart = performance.now();
1730
1907
  const result = await ssrRenderToString(ssrMod, pathname, { ssrTimeout: 300 });
1908
+ logger.log("ssr", "render-done", {
1909
+ url: pathname,
1910
+ durationMs: Math.round(performance.now() - ssrStart),
1911
+ htmlBytes: result.html.length
1912
+ });
1731
1913
  const scriptTag = buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc);
1732
1914
  const html = generateSSRPageHtml({
1733
1915
  title,
1734
1916
  css: result.css,
1735
1917
  bodyHtml: result.html,
1736
1918
  ssrData: result.ssrData,
1737
- scriptTag
1919
+ scriptTag,
1920
+ editor
1738
1921
  });
1739
1922
  return new Response(html, {
1740
1923
  status: 200,
@@ -1752,14 +1935,16 @@ data: {}
1752
1935
  console.error("[Server] SSR error:", err);
1753
1936
  const errMsg = err instanceof Error ? err.message : String(err);
1754
1937
  const errStack = err instanceof Error ? err.stack : undefined;
1755
- broadcastError("ssr", [{ message: errMsg, stack: errStack }]);
1938
+ const { message: _, ...loc } = errStack ? parseSourceFromStack(errStack) : { message: "" };
1939
+ broadcastError("ssr", [{ message: errMsg, ...loc, stack: errStack }]);
1756
1940
  const scriptTag = buildScriptTag(bundledScriptUrl, hmrBootstrapScript, clientSrc);
1757
1941
  const fallbackHtml = generateSSRPageHtml({
1758
1942
  title,
1759
1943
  css: "",
1760
1944
  bodyHtml: "",
1761
1945
  ssrData: [],
1762
- scriptTag
1946
+ scriptTag,
1947
+ editor
1763
1948
  });
1764
1949
  return new Response(fallbackHtml, {
1765
1950
  status: 200,
@@ -1773,6 +1958,8 @@ data: {}
1773
1958
  websocket: {
1774
1959
  open(ws) {
1775
1960
  wsClients.add(ws);
1961
+ diagnostics.recordWebSocketChange(wsClients.size);
1962
+ logger.log("ws", "client-connected", { total: wsClients.size });
1776
1963
  ws.sendText(JSON.stringify({ type: "connected" }));
1777
1964
  if (currentError) {
1778
1965
  ws.sendText(JSON.stringify({
@@ -1862,6 +2049,7 @@ data: {}
1862
2049
  },
1863
2050
  close(ws) {
1864
2051
  wsClients.delete(ws);
2052
+ diagnostics.recordWebSocketChange(wsClients.size);
1865
2053
  }
1866
2054
  },
1867
2055
  development: {
@@ -1890,6 +2078,7 @@ data: {}
1890
2078
  console.log("[Server] Extracted HMR bootstrap script");
1891
2079
  }
1892
2080
  }
2081
+ diagnostics.recordHMRAssets(bundledScriptUrl, hmrBootstrapScript !== null);
1893
2082
  } catch (e) {
1894
2083
  console.warn("[Server] Could not discover HMR bundled URL:", e);
1895
2084
  }
@@ -1904,6 +2093,8 @@ data: {}
1904
2093
  clearTimeout(refreshTimeout);
1905
2094
  refreshTimeout = setTimeout(async () => {
1906
2095
  lastChangedFile = `src/${filename}`;
2096
+ diagnostics.recordFileChange(lastChangedFile);
2097
+ logger.log("watcher", "file-changed", { file: lastChangedFile });
1907
2098
  lastBroadcastedError = "";
1908
2099
  sourceMapResolver.invalidate();
1909
2100
  if (logRequests) {
@@ -1953,31 +2144,51 @@ data: {}
1953
2144
  } catch {}
1954
2145
  if (stopped)
1955
2146
  return;
1956
- for (const key of Object.keys(__require.cache)) {
2147
+ const cacheKeys = Object.keys(__require.cache);
2148
+ let cacheCleared = 0;
2149
+ for (const key of cacheKeys) {
1957
2150
  if (key.startsWith(srcDir) || key.startsWith(entryPath)) {
1958
2151
  delete __require.cache[key];
2152
+ cacheCleared++;
1959
2153
  }
1960
2154
  }
2155
+ logger.log("watcher", "cache-cleared", { entries: cacheCleared });
2156
+ const ssrWrapperPath = resolve(devDir, "ssr-reload-entry.ts");
2157
+ mkdirSync(devDir, { recursive: true });
2158
+ writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
2159
+ `);
2160
+ const ssrReloadStart = performance.now();
1961
2161
  try {
1962
- const freshMod = await import(`${entryPath}?t=${Date.now()}`);
2162
+ const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
1963
2163
  ssrMod = freshMod;
2164
+ const durationMs = Math.round(performance.now() - ssrReloadStart);
2165
+ diagnostics.recordSSRReload(true, durationMs);
2166
+ logger.log("watcher", "ssr-reload", { status: "ok", durationMs });
1964
2167
  if (logRequests) {
1965
2168
  console.log("[Server] SSR module refreshed");
1966
2169
  }
1967
- } catch (e) {
2170
+ } catch {
2171
+ logger.log("watcher", "ssr-reload", { status: "retry" });
1968
2172
  if (stopped)
1969
2173
  return;
1970
2174
  await new Promise((r) => setTimeout(r, 500));
1971
2175
  if (stopped)
1972
2176
  return;
1973
- for (const key of Object.keys(__require.cache)) {
2177
+ const retryKeys = Object.keys(__require.cache);
2178
+ for (const key of retryKeys) {
1974
2179
  if (key.startsWith(srcDir) || key.startsWith(entryPath)) {
1975
2180
  delete __require.cache[key];
1976
2181
  }
1977
2182
  }
2183
+ mkdirSync(devDir, { recursive: true });
2184
+ writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
2185
+ `);
1978
2186
  try {
1979
- const freshMod = await import(`${entryPath}?t=${Date.now()}`);
2187
+ const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
1980
2188
  ssrMod = freshMod;
2189
+ const durationMs = Math.round(performance.now() - ssrReloadStart);
2190
+ diagnostics.recordSSRReload(true, durationMs);
2191
+ logger.log("watcher", "ssr-reload", { status: "ok", durationMs, retry: true });
1981
2192
  if (logRequests) {
1982
2193
  console.log("[Server] SSR module refreshed (retry)");
1983
2194
  }
@@ -1985,7 +2196,11 @@ data: {}
1985
2196
  console.error("[Server] Failed to refresh SSR module:", e2);
1986
2197
  const errMsg = e2 instanceof Error ? e2.message : String(e2);
1987
2198
  const errStack = e2 instanceof Error ? e2.stack : undefined;
1988
- broadcastError("ssr", [{ message: errMsg, stack: errStack }]);
2199
+ const durationMs = Math.round(performance.now() - ssrReloadStart);
2200
+ diagnostics.recordSSRReload(false, durationMs, errMsg);
2201
+ logger.log("watcher", "ssr-reload", { status: "failed", error: errMsg });
2202
+ const { message: _m, ...loc2 } = errStack ? parseSourceFromStack(errStack) : { message: "" };
2203
+ broadcastError("ssr", [{ message: errMsg, ...loc2, stack: errStack }]);
1989
2204
  }
1990
2205
  }
1991
2206
  }, 100);
@@ -1,4 +1,76 @@
1
+ type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
1
2
  import { CSSExtractionResult } from "@vertz/ui-compiler";
3
+ type DebugCategory = "plugin" | "ssr" | "watcher" | "ws";
4
+ interface DebugLogger {
5
+ log(category: DebugCategory, message: string, data?: Record<string, unknown>): void;
6
+ isEnabled(category: DebugCategory): boolean;
7
+ }
8
+ interface DiagnosticsSnapshot {
9
+ status: "ok";
10
+ uptime: number;
11
+ plugin: {
12
+ filter: string;
13
+ hmr: boolean;
14
+ fastRefresh: boolean;
15
+ processedFiles: string[];
16
+ processedCount: number;
17
+ };
18
+ ssr: {
19
+ moduleStatus: "pending" | "loaded" | "error";
20
+ lastReloadTime: string | null;
21
+ lastReloadDurationMs: number | null;
22
+ lastReloadError: string | null;
23
+ reloadCount: number;
24
+ failedReloadCount: number;
25
+ };
26
+ hmr: {
27
+ bundledScriptUrl: string | null;
28
+ bootstrapDiscovered: boolean;
29
+ };
30
+ errors: {
31
+ current: ErrorCategory | null;
32
+ lastCategory: ErrorCategory | null;
33
+ lastMessage: string | null;
34
+ };
35
+ websocket: {
36
+ connectedClients: number;
37
+ };
38
+ watcher: {
39
+ lastChangedFile: string | null;
40
+ lastChangeTime: string | null;
41
+ };
42
+ }
43
+ declare class DiagnosticsCollector {
44
+ private startTime;
45
+ private pluginFilter;
46
+ private pluginHmr;
47
+ private pluginFastRefresh;
48
+ private processedFilesSet;
49
+ private processedCount;
50
+ private ssrModuleStatus;
51
+ private ssrLastReloadTime;
52
+ private ssrLastReloadDurationMs;
53
+ private ssrLastReloadError;
54
+ private ssrReloadCount;
55
+ private ssrFailedReloadCount;
56
+ private hmrBundledScriptUrl;
57
+ private hmrBootstrapDiscovered;
58
+ private errorCurrent;
59
+ private errorLastCategory;
60
+ private errorLastMessage;
61
+ private wsConnectedClients;
62
+ private watcherLastChangedFile;
63
+ private watcherLastChangeTime;
64
+ recordPluginConfig(filter: string, hmr: boolean, fastRefresh: boolean): void;
65
+ recordPluginProcess(file: string): void;
66
+ recordSSRReload(success: boolean, durationMs: number, error?: string): void;
67
+ recordHMRAssets(bundledScriptUrl: string | null, bootstrapDiscovered: boolean): void;
68
+ recordError(category: ErrorCategory, message: string): void;
69
+ recordErrorClear(): void;
70
+ recordWebSocketChange(count: number): void;
71
+ recordFileChange(file: string): void;
72
+ getSnapshot(): DiagnosticsSnapshot;
73
+ }
2
74
  import { BunPlugin as BunPlugin_seob6 } from "bun";
3
75
  interface VertzBunPluginOptions {
4
76
  /** Regex filter for files to transform. Defaults to .tsx files. */
@@ -21,6 +93,10 @@ interface VertzBunPluginOptions {
21
93
  fastRefresh?: boolean;
22
94
  /** Project root for computing relative paths. */
23
95
  projectRoot?: string;
96
+ /** Debug logger for opt-in diagnostic logging. */
97
+ logger?: DebugLogger;
98
+ /** Diagnostics collector for the health check endpoint. */
99
+ diagnostics?: DiagnosticsCollector;
24
100
  }
25
101
  /** CSS extractions tracked across all transformed files (for dead CSS elimination). */
26
102
  type FileExtractionsMap = Map<string, CSSExtractionResult>;
@@ -100,6 +100,8 @@ function createVertzBunPlugin(options) {
100
100
  const cssOutDir = options?.cssOutDir ?? resolve(projectRoot, ".vertz", "css");
101
101
  const cssExtractor = new CSSExtractor;
102
102
  const componentAnalyzer = new ComponentAnalyzer;
103
+ const logger = options?.logger;
104
+ const diagnostics = options?.diagnostics;
103
105
  const fileExtractions = new Map;
104
106
  const cssSidecarMap = new Map;
105
107
  mkdirSync(cssOutDir, { recursive: true });
@@ -108,7 +110,10 @@ function createVertzBunPlugin(options) {
108
110
  setup(build) {
109
111
  build.onLoad({ filter }, async (args) => {
110
112
  try {
113
+ const startMs = logger?.isEnabled("plugin") ? performance.now() : 0;
111
114
  const source = await Bun.file(args.path).text();
115
+ const relPath = relative(projectRoot, args.path);
116
+ logger?.log("plugin", "onLoad", { file: relPath, bytes: source.length });
112
117
  const hydrationS = new MagicString(source);
113
118
  const hydrationProject = new Project({
114
119
  useInMemoryFileSystem: true,
@@ -144,8 +149,8 @@ function createVertzBunPlugin(options) {
144
149
  const cssFilePath = resolve(cssOutDir, cssFileName);
145
150
  writeFileSync(cssFilePath, extraction.css);
146
151
  cssSidecarMap.set(args.path, cssFilePath);
147
- const relPath = relative(dirname(args.path), cssFilePath);
148
- const importPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
152
+ const relPath2 = relative(dirname(args.path), cssFilePath);
153
+ const importPath = relPath2.startsWith(".") ? relPath2 : `./${relPath2}`;
149
154
  cssImportLine = `import '${importPath}';
150
155
  `;
151
156
  }
@@ -180,6 +185,20 @@ import.meta.hot.accept();
180
185
  `;
181
186
  }
182
187
  contents += sourceMapComment;
188
+ if (logger?.isEnabled("plugin")) {
189
+ const durationMs = Math.round(performance.now() - startMs);
190
+ const stages = [
191
+ "hydration",
192
+ fastRefresh ? "stableIds" : null,
193
+ "compile",
194
+ "sourceMap",
195
+ extraction.css.length > 0 ? "css" : null,
196
+ fastRefresh && refreshPreamble ? "fastRefresh" : null,
197
+ hmr ? "hmr" : null
198
+ ].filter(Boolean).join(",");
199
+ logger.log("plugin", "done", { file: relPath, durationMs, stages });
200
+ }
201
+ diagnostics?.recordPluginProcess(relPath);
183
202
  return { contents, loader: "tsx" };
184
203
  } catch (err) {
185
204
  const message = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-server",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI server-side rendering runtime",