bertui 0.3.9 → 0.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/src/build.js CHANGED
@@ -1,9 +1,8 @@
1
- import { join, relative, basename } from 'path';
1
+ import { join, relative, basename, extname, dirname } from 'path';
2
2
  import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
3
- import { extname, dirname } from 'path';
4
3
  import logger from './logger/logger.js';
5
4
  import { buildCSS } from './build/css-builder.js';
6
- import { loadEnvVariables, replaceEnvInCode } from './utils/env.js'; // ✅ IMPORT THIS!
5
+ import { loadEnvVariables, replaceEnvInCode } from './utils/env.js';
7
6
 
8
7
  export async function buildProduction(options = {}) {
9
8
  const root = options.root || process.cwd();
@@ -26,7 +25,6 @@ export async function buildProduction(options = {}) {
26
25
  const startTime = Date.now();
27
26
 
28
27
  try {
29
- // ✅ LOAD ENV VARS BEFORE COMPILATION!
30
28
  logger.info('Step 0: Loading environment variables...');
31
29
  const envVars = loadEnvVariables(root);
32
30
  if (Object.keys(envVars).length > 0) {
@@ -34,27 +32,15 @@ export async function buildProduction(options = {}) {
34
32
  }
35
33
 
36
34
  logger.info('Step 1: Compiling for production...');
37
- const { routes } = await compileForBuild(root, buildDir, envVars); // ✅ PASS ENV VARS!
35
+ const { routes } = await compileForBuild(root, buildDir, envVars);
38
36
  logger.success('Production compilation complete');
39
37
 
40
38
  logger.info('Step 2: Building CSS with Lightning CSS...');
41
39
  await buildAllCSS(root, outDir);
42
40
 
43
- const publicDir = join(root, 'public');
44
- if (existsSync(publicDir)) {
45
- logger.info('Step 3: Copying public assets...');
46
- const publicFiles = readdirSync(publicDir);
47
- for (const file of publicFiles) {
48
- const srcFile = join(publicDir, file);
49
- const destFile = join(outDir, file);
50
- if (statSync(srcFile).isFile()) {
51
- cpSync(srcFile, destFile);
52
- }
53
- }
54
- logger.success('Public assets copied');
55
- } else {
56
- logger.info('Step 3: No public directory found, skipping...');
57
- }
41
+ // FIX: Copy all static assets from src/ and public/
42
+ logger.info('Step 3: Copying static assets...');
43
+ await copyAllStaticAssets(root, outDir);
58
44
 
59
45
  logger.info('Step 4: Bundling JavaScript with Bun...');
60
46
  const buildEntry = join(buildDir, 'main.js');
@@ -77,13 +63,11 @@ export async function buildProduction(options = {}) {
77
63
  asset: '[name]-[hash].[ext]'
78
64
  },
79
65
  external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
80
- // ✅ CRITICAL: Add define to replace process.env at bundle time!
81
66
  define: {
82
67
  'process.env.NODE_ENV': '"production"',
83
68
  'process.env.PUBLIC_APP_NAME': JSON.stringify(envVars.PUBLIC_APP_NAME || 'BertUI App'),
84
69
  'process.env.PUBLIC_API_URL': JSON.stringify(envVars.PUBLIC_API_URL || ''),
85
70
  'process.env.PUBLIC_USERNAME': JSON.stringify(envVars.PUBLIC_USERNAME || ''),
86
- // Add all other env vars dynamically
87
71
  ...Object.fromEntries(
88
72
  Object.entries(envVars).map(([key, value]) => [
89
73
  `process.env.${key}`,
@@ -137,6 +121,78 @@ export async function buildProduction(options = {}) {
137
121
  }
138
122
  }
139
123
 
124
+ // ✅ NEW FUNCTION: Copy all static assets
125
+ async function copyAllStaticAssets(root, outDir) {
126
+ const publicDir = join(root, 'public');
127
+ const srcDir = join(root, 'src');
128
+
129
+ let assetsCopied = 0;
130
+
131
+ // Copy from public/
132
+ if (existsSync(publicDir)) {
133
+ assetsCopied += await copyStaticAssetsFromDir(publicDir, outDir, 'public');
134
+ }
135
+
136
+ // Copy static assets from src/ (images, fonts, etc.)
137
+ if (existsSync(srcDir)) {
138
+ const assetsOutDir = join(outDir, 'assets');
139
+ mkdirSync(assetsOutDir, { recursive: true });
140
+ assetsCopied += await copyStaticAssetsFromDir(srcDir, assetsOutDir, 'src', true);
141
+ }
142
+
143
+ logger.success(`Copied ${assetsCopied} static assets`);
144
+ }
145
+
146
+ // ✅ NEW FUNCTION: Recursively copy static assets
147
+ async function copyStaticAssetsFromDir(sourceDir, targetDir, label, skipStyles = false) {
148
+ const staticExtensions = [
149
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif', // Images
150
+ '.woff', '.woff2', '.ttf', '.otf', '.eot', // Fonts
151
+ '.mp4', '.webm', '.ogg', '.mp3', '.wav', // Media
152
+ '.pdf', '.zip', '.json', '.xml', '.txt' // Documents
153
+ ];
154
+
155
+ let copiedCount = 0;
156
+
157
+ function copyRecursive(dir, targetBase) {
158
+ const entries = readdirSync(dir, { withFileTypes: true });
159
+
160
+ for (const entry of entries) {
161
+ const srcPath = join(dir, entry.name);
162
+ const relativePath = relative(sourceDir, srcPath);
163
+ const destPath = join(targetBase, relativePath);
164
+
165
+ if (entry.isDirectory()) {
166
+ // Skip node_modules, .bertui, etc.
167
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
168
+ continue;
169
+ }
170
+
171
+ // Skip styles directory if requested
172
+ if (skipStyles && entry.name === 'styles') {
173
+ continue;
174
+ }
175
+
176
+ mkdirSync(destPath, { recursive: true });
177
+ copyRecursive(srcPath, targetBase);
178
+ } else if (entry.isFile()) {
179
+ const ext = extname(entry.name);
180
+
181
+ // Copy static assets only
182
+ if (staticExtensions.includes(ext.toLowerCase())) {
183
+ mkdirSync(dirname(destPath), { recursive: true });
184
+ cpSync(srcPath, destPath);
185
+ logger.debug(`Copied ${label}/${relativePath}`);
186
+ copiedCount++;
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ copyRecursive(sourceDir, targetDir);
193
+ return copiedCount;
194
+ }
195
+
140
196
  async function buildAllCSS(root, outDir) {
141
197
  const srcStylesDir = join(root, 'src', 'styles');
142
198
  const stylesOutDir = join(outDir, 'styles');
@@ -153,7 +209,6 @@ async function buildAllCSS(root, outDir) {
153
209
  }
154
210
  }
155
211
 
156
- // ✅ ACCEPT ENV VARS PARAMETER
157
212
  async function compileForBuild(root, buildDir, envVars) {
158
213
  const srcDir = join(root, 'src');
159
214
  const pagesDir = join(srcDir, 'pages');
@@ -168,7 +223,6 @@ async function compileForBuild(root, buildDir, envVars) {
168
223
  logger.info(`Found ${routes.length} routes`);
169
224
  }
170
225
 
171
- // ✅ PASS ENV VARS TO COMPILATION
172
226
  await compileBuildDirectory(srcDir, buildDir, root, envVars);
173
227
 
174
228
  if (routes.length > 0) {
@@ -367,7 +421,6 @@ ${routeConfigs}
367
421
  await Bun.write(join(buildDir, 'router.js'), routerCode);
368
422
  }
369
423
 
370
- // ✅ ACCEPT ENV VARS PARAMETER
371
424
  async function compileBuildDirectory(srcDir, buildDir, root, envVars) {
372
425
  const files = readdirSync(srcDir);
373
426
 
@@ -378,29 +431,33 @@ async function compileBuildDirectory(srcDir, buildDir, root, envVars) {
378
431
  if (stat.isDirectory()) {
379
432
  const subBuildDir = join(buildDir, file);
380
433
  mkdirSync(subBuildDir, { recursive: true });
381
- await compileBuildDirectory(srcPath, subBuildDir, root, envVars); // ✅ PASS IT DOWN
434
+ await compileBuildDirectory(srcPath, subBuildDir, root, envVars);
382
435
  } else {
383
436
  const ext = extname(file);
384
437
 
385
438
  if (ext === '.css') continue;
386
439
 
387
440
  if (['.jsx', '.tsx', '.ts'].includes(ext)) {
388
- await compileBuildFile(srcPath, buildDir, file, root, envVars); // ✅ PASS IT HERE
441
+ await compileBuildFile(srcPath, buildDir, file, root, envVars);
389
442
  } else if (ext === '.js') {
390
443
  const outPath = join(buildDir, file);
391
444
  let code = await Bun.file(srcPath).text();
392
445
 
393
446
  code = removeCSSImports(code);
394
- code = replaceEnvInCode(code, envVars); // ✅ REPLACE ENV VARS!
447
+ code = replaceEnvInCode(code, envVars);
395
448
  code = fixBuildImports(code, srcPath, outPath, root);
396
449
 
450
+ // ✅ FIX: Add React import if needed
451
+ if (usesJSX(code) && !code.includes('import React')) {
452
+ code = `import React from 'react';\n${code}`;
453
+ }
454
+
397
455
  await Bun.write(outPath, code);
398
456
  }
399
457
  }
400
458
  }
401
459
  }
402
460
 
403
- // ✅ ACCEPT ENV VARS PARAMETER
404
461
  async function compileBuildFile(srcPath, buildDir, filename, root, envVars) {
405
462
  const ext = extname(filename);
406
463
  const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
@@ -409,7 +466,7 @@ async function compileBuildFile(srcPath, buildDir, filename, root, envVars) {
409
466
  let code = await Bun.file(srcPath).text();
410
467
 
411
468
  code = removeCSSImports(code);
412
- code = replaceEnvInCode(code, envVars); // ✅ REPLACE ENV VARS BEFORE TRANSPILATION!
469
+ code = replaceEnvInCode(code, envVars);
413
470
 
414
471
  const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
415
472
  const outPath = join(buildDir, outFilename);
@@ -429,7 +486,8 @@ async function compileBuildFile(srcPath, buildDir, filename, root, envVars) {
429
486
 
430
487
  let compiled = await transpiler.transform(code);
431
488
 
432
- if (!compiled.includes('import React') && (compiled.includes('React.createElement') || compiled.includes('React.Fragment'))) {
489
+ // FIX: Add React import if needed
490
+ if (usesJSX(compiled) && !compiled.includes('import React')) {
433
491
  compiled = `import React from 'react';\n${compiled}`;
434
492
  }
435
493
 
@@ -442,6 +500,14 @@ async function compileBuildFile(srcPath, buildDir, filename, root, envVars) {
442
500
  }
443
501
  }
444
502
 
503
+ function usesJSX(code) {
504
+ return code.includes('React.createElement') ||
505
+ code.includes('React.Fragment') ||
506
+ /<[A-Z]/.test(code) ||
507
+ code.includes('jsx(') ||
508
+ code.includes('jsxs(');
509
+ }
510
+
445
511
  function removeCSSImports(code) {
446
512
  code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
447
513
  code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
@@ -20,13 +20,11 @@ export async function compileProject(root) {
20
20
  logger.info('Created .bertui/compiled/');
21
21
  }
22
22
 
23
- // Load environment variables
24
23
  const envVars = loadEnvVariables(root);
25
24
  if (Object.keys(envVars).length > 0) {
26
25
  logger.info(`Loaded ${Object.keys(envVars).length} environment variables`);
27
26
  }
28
27
 
29
- // Generate env.js file
30
28
  const envCode = generateEnvCode(envVars);
31
29
  await Bun.write(join(outDir, 'env.js'), envCode);
32
30
 
@@ -285,13 +283,15 @@ async function compileDirectory(srcDir, outDir, root, envVars) {
285
283
  const outPath = join(outDir, file);
286
284
  let code = await Bun.file(srcPath).text();
287
285
 
288
- // Remove ALL CSS imports
289
286
  code = removeCSSImports(code);
290
- // Inject environment variables
291
287
  code = replaceEnvInCode(code, envVars);
292
- // Fix router imports
293
288
  code = fixRouterImports(code, outPath, root);
294
289
 
290
+ // ✅ CRITICAL FIX: Ensure React import for .js files with JSX
291
+ if (usesJSX(code) && !code.includes('import React')) {
292
+ code = `import React from 'react';\n${code}`;
293
+ }
294
+
295
295
  await Bun.write(outPath, code);
296
296
  logger.debug(`Copied: ${relativePath}`);
297
297
  stats.files++;
@@ -312,13 +312,8 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
312
312
  try {
313
313
  let code = await Bun.file(srcPath).text();
314
314
 
315
- // CRITICAL FIX: Remove ALL CSS imports before transpilation
316
315
  code = removeCSSImports(code);
317
-
318
- // Remove dotenv imports (not needed in browser)
319
316
  code = removeDotenvImports(code);
320
-
321
- // Inject environment variables
322
317
  code = replaceEnvInCode(code, envVars);
323
318
 
324
319
  const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
@@ -336,7 +331,8 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
336
331
  });
337
332
  let compiled = await transpiler.transform(code);
338
333
 
339
- if (!compiled.includes('import React') && (compiled.includes('React.createElement') || compiled.includes('React.Fragment'))) {
334
+ // CRITICAL FIX: Always add React import if JSX is present
335
+ if (usesJSX(compiled) && !compiled.includes('import React')) {
340
336
  compiled = `import React from 'react';\n${compiled}`;
341
337
  }
342
338
 
@@ -350,29 +346,25 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
350
346
  }
351
347
  }
352
348
 
353
- // NEW FUNCTION: Remove all CSS imports
349
+ // NEW: Detect if code uses JSX
350
+ function usesJSX(code) {
351
+ return code.includes('React.createElement') ||
352
+ code.includes('React.Fragment') ||
353
+ /<[A-Z]/.test(code) || // Detects JSX tags like <Component>
354
+ code.includes('jsx(') || // Runtime JSX
355
+ code.includes('jsxs('); // Runtime JSX
356
+ }
357
+
354
358
  function removeCSSImports(code) {
355
- // Remove CSS imports (with or without quotes, single or double)
356
- // Matches: import './styles.css', import "./styles.css", import "styles.css", import 'styles.css'
357
359
  code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
358
-
359
- // Also remove bertui/styles imports
360
360
  code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
361
-
362
361
  return code;
363
362
  }
364
363
 
365
- // NEW FUNCTION: Remove dotenv imports and dotenv.config() calls
366
364
  function removeDotenvImports(code) {
367
- // Remove: import dotenv from 'dotenv'
368
365
  code = code.replace(/import\s+\w+\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
369
-
370
- // Remove: import { config } from 'dotenv'
371
366
  code = code.replace(/import\s+\{[^}]+\}\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
372
-
373
- // Remove: dotenv.config()
374
367
  code = code.replace(/\w+\.config\(\s*\)\s*;?\s*/g, '');
375
-
376
368
  return code;
377
369
  }
378
370
 
@@ -1,7 +1,7 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { watch } from 'fs';
3
3
  import { join, extname } from 'path';
4
- import { existsSync, readdirSync } from 'fs'; // ✅ FIXED: Import properly
4
+ import { existsSync, readdirSync } from 'fs';
5
5
  import logger from '../logger/logger.js';
6
6
  import { compileProject } from '../client/compiler.js';
7
7
  import { loadConfig } from '../config/loadConfig.js';
@@ -32,7 +32,6 @@ export async function startDevServer(options = {}) {
32
32
  const path = params['*'];
33
33
 
34
34
  if (path.includes('.')) {
35
- // Handle compiled directory files
36
35
  if (path.startsWith('compiled/')) {
37
36
  const filePath = join(compiledDir, path.replace('compiled/', ''));
38
37
  const file = Bun.file(filePath);
@@ -50,7 +49,6 @@ export async function startDevServer(options = {}) {
50
49
  }
51
50
  }
52
51
 
53
- // Handle CSS files from .bertui/styles
54
52
  if (path.startsWith('styles/') && path.endsWith('.css')) {
55
53
  const cssPath = join(stylesDir, path.replace('styles/', ''));
56
54
  const file = Bun.file(cssPath);
@@ -101,6 +99,14 @@ ws.onmessage = (event) => {
101
99
  if (data.type === 'recompiling') {
102
100
  console.log('%c⚙️ Recompiling...', 'color: #3b82f6');
103
101
  }
102
+
103
+ if (data.type === 'compilation-error') {
104
+ console.error('%c❌ Compilation Error', 'color: #ef4444; font-weight: bold');
105
+ console.error(data.message);
106
+ if (window.__BERTUI_SHOW_ERROR__) {
107
+ window.__BERTUI_SHOW_ERROR__(data);
108
+ }
109
+ }
104
110
  };
105
111
 
106
112
  ws.onerror = (error) => {
@@ -116,6 +122,226 @@ ws.onclose = () => {
116
122
  headers: { 'Content-Type': 'application/javascript' }
117
123
  });
118
124
  })
125
+
126
+ .get('/error-overlay.js', () => {
127
+ const errorOverlayScript = `
128
+ (function() {
129
+ 'use strict';
130
+
131
+ let overlayElement = null;
132
+
133
+ function createOverlay() {
134
+ if (overlayElement) return overlayElement;
135
+
136
+ const overlay = document.createElement('div');
137
+ overlay.id = 'bertui-error-overlay';
138
+ overlay.style.cssText = \`
139
+ position: fixed;
140
+ top: 0;
141
+ left: 0;
142
+ width: 100%;
143
+ height: 100%;
144
+ background: rgba(0, 0, 0, 0.95);
145
+ color: #fff;
146
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
147
+ font-size: 14px;
148
+ line-height: 1.5;
149
+ z-index: 9999999;
150
+ overflow: auto;
151
+ padding: 20px;
152
+ box-sizing: border-box;
153
+ display: none;
154
+ \`;
155
+ document.body.appendChild(overlay);
156
+ overlayElement = overlay;
157
+ return overlay;
158
+ }
159
+
160
+ function showError(error) {
161
+ const overlay = createOverlay();
162
+
163
+ const errorType = error.type || 'Runtime Error';
164
+ const errorMessage = error.message || 'Unknown error';
165
+ const errorStack = error.stack || '';
166
+ const errorFile = error.file || 'Unknown file';
167
+ const errorLine = error.line || '';
168
+ const errorColumn = error.column || '';
169
+
170
+ overlay.innerHTML = \`
171
+ <div style="max-width: 1200px; margin: 0 auto;">
172
+ <div style="display: flex; align-items: center; margin-bottom: 30px;">
173
+ <div style="
174
+ background: #ef4444;
175
+ width: 50px;
176
+ height: 50px;
177
+ border-radius: 50%;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ font-size: 24px;
182
+ margin-right: 15px;
183
+ ">❌</div>
184
+ <div>
185
+ <h1 style="margin: 0; font-size: 28px; font-weight: bold; color: #ef4444;">
186
+ \${errorType}
187
+ </h1>
188
+ <p style="margin: 5px 0 0 0; color: #a0a0a0; font-size: 14px;">
189
+ BertUI detected an error in your application
190
+ </p>
191
+ </div>
192
+ </div>
193
+
194
+ <div style="
195
+ background: #1a1a1a;
196
+ border: 1px solid #333;
197
+ border-radius: 8px;
198
+ padding: 20px;
199
+ margin-bottom: 20px;
200
+ ">
201
+ <div style="color: #fbbf24; font-weight: bold; margin-bottom: 10px;">
202
+ \${errorFile}\${errorLine ? ':' + errorLine : ''}\${errorColumn ? ':' + errorColumn : ''}
203
+ </div>
204
+ <div style="color: #fff; white-space: pre-wrap; word-break: break-word;">
205
+ \${escapeHtml(errorMessage)}
206
+ </div>
207
+ </div>
208
+
209
+ \${errorStack ? \`
210
+ <div style="
211
+ background: #0a0a0a;
212
+ border: 1px solid #222;
213
+ border-radius: 8px;
214
+ padding: 20px;
215
+ margin-bottom: 20px;
216
+ ">
217
+ <div style="color: #a0a0a0; font-weight: bold; margin-bottom: 10px;">
218
+ Stack Trace:
219
+ </div>
220
+ <pre style="
221
+ margin: 0;
222
+ color: #d0d0d0;
223
+ white-space: pre-wrap;
224
+ word-break: break-word;
225
+ font-size: 12px;
226
+ ">\${escapeHtml(errorStack)}</pre>
227
+ </div>
228
+ \` : ''}
229
+
230
+ <div style="display: flex; gap: 15px; flex-wrap: wrap;">
231
+ <button onclick="window.__BERTUI_HIDE_ERROR__()" style="
232
+ background: #3b82f6;
233
+ color: white;
234
+ border: none;
235
+ padding: 12px 24px;
236
+ border-radius: 6px;
237
+ font-size: 14px;
238
+ font-weight: 600;
239
+ cursor: pointer;
240
+ ">Dismiss (Esc)</button>
241
+ <button onclick="window.location.reload()" style="
242
+ background: #10b981;
243
+ color: white;
244
+ border: none;
245
+ padding: 12px 24px;
246
+ border-radius: 6px;
247
+ font-size: 14px;
248
+ font-weight: 600;
249
+ cursor: pointer;
250
+ ">Reload Page</button>
251
+ </div>
252
+
253
+ <div style="
254
+ margin-top: 30px;
255
+ padding-top: 20px;
256
+ border-top: 1px solid #333;
257
+ color: #666;
258
+ font-size: 12px;
259
+ ">
260
+ 💡 <strong>Tip:</strong> Fix the error in your code, and the page will automatically reload with HMR.
261
+ </div>
262
+ </div>
263
+ \`;
264
+
265
+ overlay.style.display = 'block';
266
+ }
267
+
268
+ function hideError() {
269
+ if (overlayElement) {
270
+ overlayElement.style.display = 'none';
271
+ }
272
+ }
273
+
274
+ function escapeHtml(text) {
275
+ const div = document.createElement('div');
276
+ div.textContent = text;
277
+ return div.innerHTML;
278
+ }
279
+
280
+ function parseErrorStack(error) {
281
+ const stack = error.stack || '';
282
+ const lines = stack.split('\\n');
283
+
284
+ for (const line of lines) {
285
+ const match = line.match(/\\((.+):(\\d+):(\\d+)\\)/) ||
286
+ line.match(/at (.+):(\\d+):(\\d+)/) ||
287
+ line.match(/(.+):(\\d+):(\\d+)/);
288
+
289
+ if (match) {
290
+ return {
291
+ file: match[1].trim(),
292
+ line: match[2],
293
+ column: match[3]
294
+ };
295
+ }
296
+ }
297
+
298
+ return { file: null, line: null, column: null };
299
+ }
300
+
301
+ window.addEventListener('error', function(event) {
302
+ const { file, line, column } = parseErrorStack(event.error || {});
303
+
304
+ showError({
305
+ type: 'Runtime Error',
306
+ message: event.message,
307
+ stack: event.error ? event.error.stack : null,
308
+ file: event.filename || file,
309
+ line: event.lineno || line,
310
+ column: event.colno || column
311
+ });
312
+ });
313
+
314
+ window.addEventListener('unhandledrejection', function(event) {
315
+ const error = event.reason;
316
+ const { file, line, column } = parseErrorStack(error);
317
+
318
+ showError({
319
+ type: 'Unhandled Promise Rejection',
320
+ message: error?.message || String(event.reason),
321
+ stack: error?.stack,
322
+ file,
323
+ line,
324
+ column
325
+ });
326
+ });
327
+
328
+ window.__BERTUI_SHOW_ERROR__ = showError;
329
+ window.__BERTUI_HIDE_ERROR__ = hideError;
330
+
331
+ document.addEventListener('keydown', function(e) {
332
+ if (e.key === 'Escape') {
333
+ hideError();
334
+ }
335
+ });
336
+
337
+ console.log('%c🔥 BertUI Error Overlay Active', 'color: #10b981; font-weight: bold; font-size: 14px');
338
+ })();
339
+ `;
340
+
341
+ return new Response(errorOverlayScript, {
342
+ headers: { 'Content-Type': 'application/javascript' }
343
+ });
344
+ })
119
345
 
120
346
  .ws('/hmr', {
121
347
  open(ws) {
@@ -198,7 +424,6 @@ ws.onclose = () => {
198
424
  function serveHTML(root, hasRouter, config) {
199
425
  const meta = config.meta || {};
200
426
 
201
- // ✅ FIXED: Proper ESM import for fs
202
427
  const srcStylesDir = join(root, 'src', 'styles');
203
428
  let userStylesheets = '';
204
429
 
@@ -254,6 +479,7 @@ ${userStylesheets}
254
479
  </head>
255
480
  <body>
256
481
  <div id="root"></div>
482
+ <script type="module" src="/error-overlay.js"></script>
257
483
  <script type="module" src="/hmr-client.js"></script>
258
484
  <script type="module" src="/compiled/main.js"></script>
259
485
  </body>
@@ -324,6 +550,20 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
324
550
  }
325
551
  } catch (error) {
326
552
  logger.error(`Recompilation failed: ${error.message}`);
553
+
554
+ // Send compilation error to clients
555
+ for (const client of clients) {
556
+ try {
557
+ client.send(JSON.stringify({
558
+ type: 'compilation-error',
559
+ message: error.message,
560
+ stack: error.stack,
561
+ file: filename
562
+ }));
563
+ } catch (e) {
564
+ clients.delete(client);
565
+ }
566
+ }
327
567
  }
328
568
  }
329
569
  });