juxscript 1.1.154 → 1.1.155

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.
@@ -508,6 +508,12 @@ export class JuxCompiler {
508
508
  fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
509
509
  console.log(`📸 Source snapshot written`);
510
510
 
511
+ // ✅ Also write to dist root so the error overlay can fetch it via /__jux_sources.json
512
+ const distSnapshotPath = path.join(this.config.distDir, '__jux_sources.json');
513
+ if (distSnapshotPath !== snapshotPath) {
514
+ fs.writeFileSync(distSnapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
515
+ }
516
+
511
517
  const validation = this.reportValidationIssues();
512
518
  if (!validation.isValid) {
513
519
  console.log('🛑 BUILD FAILED\n');
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Generates an injectable client-side error collector script.
3
3
  * Full-screen overlay similar to Vite's error screen.
4
+ * Resolves bundle line numbers back to original .jux source using __jux_sources.json.
4
5
  *
5
6
  * @param {Object} options
6
7
  * @param {boolean} [options.enabled=true]
@@ -22,6 +23,114 @@ export function generateErrorCollector(options = {}) {
22
23
  var __maxErrors = ${maxErrors};
23
24
  var __overlay = null;
24
25
  var __list = null;
26
+ var __sources = null;
27
+ var __sourcesFetched = false;
28
+ var __bundleLines = null;
29
+
30
+ // Fetch source map + bundle on first error
31
+ function loadSources(cb) {
32
+ if (__sourcesFetched) return cb();
33
+ __sourcesFetched = true;
34
+
35
+ var done = 0;
36
+ function check() { if (++done >= 2) cb(); }
37
+
38
+ // Fetch source snapshot
39
+ var xhr1 = new XMLHttpRequest();
40
+ xhr1.open('GET', '/__jux_sources.json', true);
41
+ xhr1.onload = function() {
42
+ try { __sources = JSON.parse(xhr1.responseText); } catch(e) {}
43
+ check();
44
+ };
45
+ xhr1.onerror = check;
46
+ xhr1.send();
47
+
48
+ // Fetch bundle to split into lines
49
+ var xhr2 = new XMLHttpRequest();
50
+ xhr2.open('GET', '/bundle.js', true);
51
+ xhr2.onload = function() {
52
+ try { __bundleLines = xhr2.responseText.split('\\n'); } catch(e) {}
53
+ check();
54
+ };
55
+ xhr2.onerror = check;
56
+ xhr2.send();
57
+ }
58
+
59
+ // Given a bundle line number, find which view function it belongs to
60
+ // and map back to the original .jux source line
61
+ function resolveSourceLocation(bundleLine) {
62
+ if (!__sources || !__bundleLines || !bundleLine) return null;
63
+
64
+ // Find the renderJuxN function that contains this line
65
+ // by scanning backwards from bundleLine for "async function renderJux"
66
+ var funcName = null;
67
+ var funcStartLine = 0;
68
+ for (var i = bundleLine - 1; i >= 0; i--) {
69
+ var line = __bundleLines[i];
70
+ if (!line) continue;
71
+ var match = line.match(/async\\s+function\\s+(renderJux\\d+)/);
72
+ if (match) {
73
+ funcName = match[1];
74
+ funcStartLine = i + 1; // 1-based
75
+ break;
76
+ }
77
+ }
78
+
79
+ if (!funcName) return null;
80
+
81
+ // Find the source entry that maps to this function
82
+ var sourceEntry = null;
83
+ for (var key in __sources) {
84
+ if (__sources[key].functionName === funcName) {
85
+ sourceEntry = __sources[key];
86
+ break;
87
+ }
88
+ }
89
+
90
+ if (!sourceEntry) return null;
91
+
92
+ // Calculate approximate original line
93
+ // bundleLine is inside the function body, funcStartLine is the function declaration
94
+ // +1 because function body starts after the declaration line
95
+ var offsetInFunc = bundleLine - funcStartLine - 1;
96
+ var originalLines = sourceEntry.lines || [];
97
+ var approxLine = Math.max(0, Math.min(offsetInFunc, originalLines.length - 1));
98
+
99
+ return {
100
+ file: sourceEntry.file,
101
+ name: sourceEntry.name,
102
+ functionName: funcName,
103
+ originalLine: approxLine + 1,
104
+ originalCode: originalLines[approxLine] || '',
105
+ // Provide surrounding context (3 lines before/after)
106
+ context: originalLines.slice(
107
+ Math.max(0, approxLine - 3),
108
+ Math.min(originalLines.length, approxLine + 4)
109
+ ),
110
+ contextStartLine: Math.max(0, approxLine - 3) + 1,
111
+ highlightLine: approxLine + 1
112
+ };
113
+ }
114
+
115
+ // Parse stack trace and extract bundle line numbers
116
+ function parseStack(stack) {
117
+ if (!stack) return [];
118
+ var frames = [];
119
+ var lines = stack.split('\\n');
120
+ for (var i = 0; i < lines.length; i++) {
121
+ // Match patterns like "at funcName (url:line:col)" or "at url:line:col"
122
+ var m = lines[i].match(/(?:at\\s+(.+?)\\s+\\()?(?:https?:\\/\\/[^:]+):([\\d]+):([\\d]+)\\)?/);
123
+ if (m) {
124
+ frames.push({
125
+ raw: lines[i].trim(),
126
+ func: m[1] || '(anonymous)',
127
+ line: parseInt(m[2], 10),
128
+ col: parseInt(m[3], 10)
129
+ });
130
+ }
131
+ }
132
+ return frames;
133
+ }
25
134
 
26
135
  function createOverlay() {
27
136
  __overlay = document.createElement('div');
@@ -53,7 +162,6 @@ export function generateErrorCollector(options = {}) {
53
162
  'background:#1a1a1a'
54
163
  ].join(';'));
55
164
 
56
- // Title bar
57
165
  var header = document.createElement('div');
58
166
  header.setAttribute('style', [
59
167
  'display:flex',
@@ -120,6 +228,38 @@ export function generateErrorCollector(options = {}) {
120
228
  if (__overlay) __overlay.style.display = 'none';
121
229
  }
122
230
 
231
+ function renderSourceContext(resolved) {
232
+ if (!resolved) return '';
233
+
234
+ var lines = resolved.context || [];
235
+ var startLine = resolved.contextStartLine || 1;
236
+ var highlight = resolved.highlightLine || -1;
237
+
238
+ var html = '<div style="margin-top:8px;padding:8px;background:#111;border:1px solid #333;border-radius:4px;">';
239
+ html += '<div style="color:#ff5555;font-size:12px;margin-bottom:6px;font-weight:bold;">' +
240
+ escapeHtml(resolved.file) + ':' + resolved.originalLine +
241
+ '</div>';
242
+
243
+ for (var i = 0; i < lines.length; i++) {
244
+ var lineNum = startLine + i;
245
+ var isHighlight = lineNum === highlight;
246
+ var bg = isHighlight ? 'background:#3a1a1a;' : '';
247
+ var color = isHighlight ? 'color:#ff8888;' : 'color:#888;';
248
+ var numColor = isHighlight ? 'color:#ff5555;' : 'color:#555;';
249
+ var marker = isHighlight ? '>' : ' ';
250
+
251
+ html += '<div style="' + bg + 'padding:1px 8px;white-space:pre;">' +
252
+ '<span style="' + numColor + 'display:inline-block;width:40px;text-align:right;margin-right:12px;user-select:none;">' +
253
+ marker + ' ' + lineNum +
254
+ '</span>' +
255
+ '<span style="' + color + '">' + escapeHtml(lines[i]) + '</span>' +
256
+ '</div>';
257
+ }
258
+
259
+ html += '</div>';
260
+ return html;
261
+ }
262
+
123
263
  function addError(type, message, source, line, col, stack) {
124
264
  if (__errors.length >= __maxErrors) __errors.shift();
125
265
 
@@ -148,34 +288,57 @@ export function generateErrorCollector(options = {}) {
148
288
 
149
289
  var typeLabel = type === 'error' ? 'Error' : type === 'rejection' ? 'Unhandled Rejection' : 'console.error';
150
290
 
151
- var meta = entry.source ? entry.source + ':' + entry.line + ':' + entry.col : '';
152
-
153
291
  item.innerHTML =
154
292
  '<div style="color:#888;font-size:12px;margin-bottom:4px;">' +
155
- typeLabel + (meta ? ' ' + escapeHtml(meta) : '') +
293
+ typeLabel +
156
294
  '<span style="float:right;">' + entry.time + '</span>' +
157
295
  '</div>' +
158
- '<div style="color:#ff8888;white-space:pre-wrap;word-break:break-word;">' +
296
+ '<div style="color:#ff8888;font-size:14px;white-space:pre-wrap;word-break:break-word;font-weight:bold;">' +
159
297
  escapeHtml(entry.message) +
160
- '</div>' +
161
- (entry.stack ?
162
- '<pre style="margin:8px 0 0;padding:8px;font-size:12px;color:#888;' +
163
- 'white-space:pre-wrap;background:#1a1a1a;border-radius:4px;' +
164
- 'border:1px solid #333;max-height:200px;overflow-y:auto;">' +
165
- escapeHtml(cleanStack(entry.stack)) +
166
- '</pre>'
167
- : '');
298
+ '</div>';
168
299
 
169
300
  __list.appendChild(item);
170
- }
171
301
 
172
- function cleanStack(stack) {
173
- // Remove the first line if it duplicates the message
174
- var lines = stack.split('\\n');
175
- if (lines.length > 1 && lines[0].indexOf('Error:') !== -1) {
176
- lines.shift();
177
- }
178
- return lines.join('\\n').trim();
302
+ // Resolve source location asynchronously
303
+ loadSources(function() {
304
+ var frames = parseStack(entry.stack);
305
+ var resolved = null;
306
+
307
+ // Try each frame until we find one that maps to a .jux source
308
+ for (var i = 0; i < frames.length; i++) {
309
+ var r = resolveSourceLocation(frames[i].line);
310
+ if (r) {
311
+ resolved = r;
312
+ break;
313
+ }
314
+ }
315
+
316
+ // Also try the direct line/col from the error itself
317
+ if (!resolved && entry.line) {
318
+ resolved = resolveSourceLocation(entry.line);
319
+ }
320
+
321
+ if (resolved) {
322
+ // Add source context block
323
+ var sourceBlock = document.createElement('div');
324
+ sourceBlock.innerHTML = renderSourceContext(resolved);
325
+ item.appendChild(sourceBlock);
326
+ }
327
+
328
+ // Add raw stack (collapsed) if present
329
+ if (entry.stack) {
330
+ var stackBlock = document.createElement('details');
331
+ stackBlock.setAttribute('style', 'margin-top:8px;');
332
+ stackBlock.innerHTML =
333
+ '<summary style="cursor:pointer;color:#555;font-size:12px;user-select:none;">Raw stack trace</summary>' +
334
+ '<pre style="margin:4px 0 0;padding:8px;font-size:12px;color:#666;' +
335
+ 'white-space:pre-wrap;background:#111;border-radius:4px;' +
336
+ 'border:1px solid #333;max-height:200px;overflow-y:auto;">' +
337
+ escapeHtml(entry.stack) +
338
+ '</pre>';
339
+ item.appendChild(stackBlock);
340
+ }
341
+ });
179
342
  }
180
343
 
181
344
  function escapeHtml(str) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.154",
3
+ "version": "1.1.155",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",