@alepha/react 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/auth/index.browser.js +603 -242
  2. package/dist/auth/index.browser.js.map +1 -1
  3. package/dist/auth/index.d.ts +6 -6
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +1296 -922
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/core/index.d.ts +128 -128
  8. package/dist/core/index.d.ts.map +1 -1
  9. package/dist/core/index.js +20 -20
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/form/index.d.ts +36 -36
  12. package/dist/form/index.d.ts.map +1 -1
  13. package/dist/form/index.js +15 -15
  14. package/dist/form/index.js.map +1 -1
  15. package/dist/head/index.browser.js +20 -0
  16. package/dist/head/index.browser.js.map +1 -1
  17. package/dist/head/index.d.ts +73 -65
  18. package/dist/head/index.d.ts.map +1 -1
  19. package/dist/head/index.js +20 -0
  20. package/dist/head/index.js.map +1 -1
  21. package/dist/i18n/index.d.ts +37 -37
  22. package/dist/i18n/index.d.ts.map +1 -1
  23. package/dist/i18n/index.js.map +1 -1
  24. package/dist/router/index.browser.js +605 -244
  25. package/dist/router/index.browser.js.map +1 -1
  26. package/dist/router/index.d.ts +539 -550
  27. package/dist/router/index.d.ts.map +1 -1
  28. package/dist/router/index.js +1296 -922
  29. package/dist/router/index.js.map +1 -1
  30. package/dist/websocket/index.d.ts +38 -38
  31. package/dist/websocket/index.d.ts.map +1 -1
  32. package/package.json +6 -6
  33. package/src/auth/__tests__/$auth.spec.ts +162 -147
  34. package/src/auth/index.ts +9 -3
  35. package/src/auth/services/ReactAuth.ts +15 -5
  36. package/src/core/hooks/useAction.ts +1 -2
  37. package/src/core/index.ts +4 -4
  38. package/src/form/errors/FormValidationError.ts +4 -6
  39. package/src/form/hooks/useFormState.ts +1 -1
  40. package/src/form/index.ts +1 -1
  41. package/src/form/services/FormModel.ts +31 -25
  42. package/src/head/helpers/SeoExpander.ts +2 -1
  43. package/src/head/hooks/useHead.spec.tsx +2 -2
  44. package/src/head/index.browser.ts +2 -2
  45. package/src/head/index.ts +4 -4
  46. package/src/head/interfaces/Head.ts +15 -3
  47. package/src/head/primitives/$head.ts +2 -5
  48. package/src/head/providers/BrowserHeadProvider.ts +55 -0
  49. package/src/head/providers/HeadProvider.ts +4 -1
  50. package/src/i18n/__tests__/integration.spec.tsx +1 -1
  51. package/src/i18n/components/Localize.spec.tsx +2 -2
  52. package/src/i18n/hooks/useI18n.browser.spec.tsx +2 -2
  53. package/src/i18n/index.ts +1 -1
  54. package/src/i18n/primitives/$dictionary.ts +1 -1
  55. package/src/i18n/providers/I18nProvider.spec.ts +1 -1
  56. package/src/i18n/providers/I18nProvider.ts +1 -1
  57. package/src/router/__tests__/page-head-browser.browser.spec.ts +5 -1
  58. package/src/router/__tests__/page-head.spec.ts +11 -7
  59. package/src/router/__tests__/seo-head.spec.ts +7 -3
  60. package/src/router/atoms/ssrManifestAtom.ts +2 -11
  61. package/src/router/components/ErrorViewer.tsx +626 -167
  62. package/src/router/components/Link.tsx +4 -2
  63. package/src/router/components/NestedView.tsx +7 -9
  64. package/src/router/components/NotFound.tsx +2 -2
  65. package/src/router/hooks/useQueryParams.ts +1 -1
  66. package/src/router/hooks/useRouter.ts +1 -1
  67. package/src/router/hooks/useRouterState.ts +1 -1
  68. package/src/router/index.browser.ts +10 -11
  69. package/src/router/index.shared.ts +7 -7
  70. package/src/router/index.ts +10 -7
  71. package/src/router/primitives/$page.browser.spec.tsx +6 -1
  72. package/src/router/primitives/$page.spec.tsx +7 -1
  73. package/src/router/primitives/$page.ts +5 -9
  74. package/src/router/providers/ReactBrowserProvider.ts +17 -6
  75. package/src/router/providers/ReactBrowserRouterProvider.ts +1 -1
  76. package/src/router/providers/ReactPageProvider.ts +4 -3
  77. package/src/router/providers/ReactServerProvider.ts +29 -37
  78. package/src/router/providers/ReactServerTemplateProvider.ts +300 -137
  79. package/src/router/providers/SSRManifestProvider.ts +17 -60
  80. package/src/router/services/ReactPageService.ts +4 -1
  81. package/src/router/services/ReactRouter.ts +6 -5
@@ -1,5 +1,5 @@
1
1
  import type { Alepha } from "alepha";
2
- import { type CSSProperties, useState } from "react";
2
+ import { type CSSProperties, useEffect, useRef, useState } from "react";
3
3
 
4
4
  interface ErrorViewerProps {
5
5
  error: Error;
@@ -12,34 +12,169 @@ interface StackFrame {
12
12
  line: string;
13
13
  col: string;
14
14
  raw: string;
15
+ isNodeModules: boolean;
15
16
  }
16
17
 
18
+ const isBrowser = typeof window !== "undefined";
19
+
17
20
  /**
18
- * Error viewer component that displays error details in development mode
21
+ * Error viewer component - Terminal/brutalist aesthetic
19
22
  */
20
23
  const ErrorViewer = ({ error, alepha }: ErrorViewerProps) => {
21
24
  const [expanded, setExpanded] = useState(false);
25
+ const [showNodeModules, setShowNodeModules] = useState(false);
26
+ const [visible, setVisible] = useState(false);
27
+ const containerRef = useRef<HTMLDivElement>(null);
28
+
22
29
  const isProduction = alepha.isProduction();
23
30
 
31
+ // Animate in on mount
32
+ useEffect(() => {
33
+ const timer = setTimeout(() => setVisible(true), 10);
34
+ return () => clearTimeout(timer);
35
+ }, []);
36
+
37
+ // Keyboard shortcuts
38
+ useEffect(() => {
39
+ if (!isBrowser) return;
40
+ const handler = (e: KeyboardEvent) => {
41
+ if (e.key === "c" && !e.metaKey && !e.ctrlKey) {
42
+ copyToClipboard(error.stack || error.message);
43
+ }
44
+ };
45
+ window.addEventListener("keydown", handler);
46
+ return () => window.removeEventListener("keydown", handler);
47
+ }, [error]);
48
+
24
49
  if (isProduction) {
25
50
  return <ErrorViewerProduction />;
26
51
  }
27
52
 
28
53
  const frames = parseStackTrace(error.stack);
29
- const visibleFrames = expanded ? frames : frames.slice(0, 6);
30
- const hiddenCount = frames.length - 6;
54
+ const appFrames = frames.filter((f) => !f.isNodeModules);
55
+ const nodeModulesFrames = frames.filter((f) => f.isNodeModules);
56
+ const visibleAppFrames = expanded ? appFrames : appFrames.slice(0, 5);
57
+ const hiddenAppCount = appFrames.length - 5;
58
+ const timestamp = new Date().toLocaleTimeString("en-US", {
59
+ hour12: false,
60
+ hour: "2-digit",
61
+ minute: "2-digit",
62
+ second: "2-digit",
63
+ });
31
64
 
32
65
  return (
33
- <div style={styles.overlay}>
34
- <div style={styles.container}>
66
+ <div
67
+ ref={containerRef}
68
+ style={{
69
+ ...styles.overlay,
70
+ opacity: visible ? 1 : 0,
71
+ }}
72
+ role="alertdialog"
73
+ aria-modal="true"
74
+ aria-labelledby="error-viewer-title"
75
+ >
76
+ {/* Scan lines effect */}
77
+ <div style={styles.scanlines} aria-hidden="true" />
78
+
79
+ <div
80
+ style={{
81
+ ...styles.container,
82
+ transform: visible ? "translateY(0)" : "translateY(-20px)",
83
+ opacity: visible ? 1 : 0,
84
+ }}
85
+ >
86
+ {/* Terminal header bar */}
87
+ <div style={styles.terminalBar}>
88
+ <div style={styles.terminalDots}>
89
+ <span style={{ ...styles.dot, backgroundColor: "#ff5f57" }} />
90
+ <span style={{ ...styles.dot, backgroundColor: "#febc2e" }} />
91
+ <span style={{ ...styles.dot, backgroundColor: "#28c840" }} />
92
+ </div>
93
+ <div style={styles.terminalTitle}>
94
+ <span style={styles.terminalTitleText}>error — {timestamp}</span>
95
+ </div>
96
+ <div style={styles.terminalActions}>
97
+ <kbd style={styles.kbd}>C</kbd>
98
+ <span style={styles.kbdLabel}>copy</span>
99
+ </div>
100
+ </div>
101
+
102
+ {/* Error header */}
35
103
  <Header error={error} />
36
- <StackTraceSection
37
- frames={frames}
38
- visibleFrames={visibleFrames}
39
- expanded={expanded}
40
- hiddenCount={hiddenCount}
41
- onToggle={() => setExpanded(!expanded)}
42
- />
104
+
105
+ {/* Stack trace */}
106
+ <div style={styles.stackSection}>
107
+ <div style={styles.stackHeader}>
108
+ <span style={styles.stackHeaderText}>STACK TRACE</span>
109
+ <span style={styles.stackCount}>
110
+ {appFrames.length} frames
111
+ {nodeModulesFrames.length > 0 &&
112
+ ` · ${nodeModulesFrames.length} in node_modules`}
113
+ </span>
114
+ </div>
115
+
116
+ <div style={styles.frameList}>
117
+ {visibleAppFrames.map((frame, i) => (
118
+ <StackFrameRow
119
+ key={`${frame.raw}-${i}`}
120
+ frame={frame}
121
+ index={i}
122
+ />
123
+ ))}
124
+
125
+ {hiddenAppCount > 0 && !expanded && (
126
+ <ExpandButton
127
+ onClick={() => setExpanded(true)}
128
+ label={`Show ${hiddenAppCount} more frames`}
129
+ />
130
+ )}
131
+
132
+ {expanded && hiddenAppCount > 0 && (
133
+ <ExpandButton
134
+ onClick={() => setExpanded(false)}
135
+ label="Collapse"
136
+ />
137
+ )}
138
+
139
+ {nodeModulesFrames.length > 0 && (
140
+ <>
141
+ <button
142
+ type="button"
143
+ onClick={() => setShowNodeModules(!showNodeModules)}
144
+ style={styles.nodeModulesToggle}
145
+ >
146
+ <span style={styles.nodeModulesIcon}>
147
+ {showNodeModules ? "▼" : "▶"}
148
+ </span>
149
+ <span style={styles.nodeModulesLabel}>node_modules</span>
150
+ <span style={styles.nodeModulesCount}>
151
+ {nodeModulesFrames.length}
152
+ </span>
153
+ </button>
154
+
155
+ {showNodeModules && (
156
+ <div style={styles.nodeModulesFrames}>
157
+ {nodeModulesFrames.map((frame, i) => (
158
+ <StackFrameRow
159
+ key={`nm-${frame.raw}-${i}`}
160
+ frame={frame}
161
+ index={appFrames.length + i}
162
+ dimmed
163
+ />
164
+ ))}
165
+ </div>
166
+ )}
167
+ </>
168
+ )}
169
+ </div>
170
+ </div>
171
+
172
+ {/* Footer */}
173
+ <div style={styles.footer}>
174
+ <span style={styles.footerText}>
175
+ Press <kbd style={styles.kbdInline}>C</kbd> to copy stack trace
176
+ </span>
177
+ </div>
43
178
  </div>
44
179
  </div>
45
180
  );
@@ -49,9 +184,6 @@ export default ErrorViewer;
49
184
 
50
185
  // ---------------------------------------------------------------------------------------------------------------------
51
186
 
52
- /**
53
- * Parse stack trace string into structured frames
54
- */
55
187
  function parseStackTrace(stack?: string): StackFrame[] {
56
188
  if (!stack) return [];
57
189
 
@@ -69,10 +201,9 @@ function parseStackTrace(stack?: string): StackFrame[] {
69
201
  return frames;
70
202
  }
71
203
 
72
- /**
73
- * Parse a single stack trace line into a structured frame
74
- */
75
204
  function parseStackLine(line: string): StackFrame | null {
205
+ const isNodeModules = line.includes("node_modules") || line.includes("node:");
206
+
76
207
  const withFn = line.match(/^at\s+(.+?)\s+\((.+):(\d+):(\d+)\)$/);
77
208
  if (withFn) {
78
209
  return {
@@ -81,6 +212,7 @@ function parseStackLine(line: string): StackFrame | null {
81
212
  line: withFn[3],
82
213
  col: withFn[4],
83
214
  raw: line,
215
+ isNodeModules,
84
216
  };
85
217
  }
86
218
 
@@ -92,82 +224,74 @@ function parseStackLine(line: string): StackFrame | null {
92
224
  line: withoutFn[2],
93
225
  col: withoutFn[3],
94
226
  raw: line,
227
+ isNodeModules,
95
228
  };
96
229
  }
97
230
 
98
- return { fn: "", file: line.replace(/^at\s+/, ""), line: "", col: "", raw: line };
231
+ return {
232
+ fn: "",
233
+ file: line.replace(/^at\s+/, ""),
234
+ line: "",
235
+ col: "",
236
+ raw: line,
237
+ isNodeModules,
238
+ };
99
239
  }
100
240
 
101
- /**
102
- * Copy text to clipboard
103
- */
104
- function copyToClipboard(text: string): void {
105
- navigator.clipboard.writeText(text).catch((err) => {
106
- console.error("Clipboard error:", err);
107
- });
241
+ function copyToClipboard(text: string): Promise<boolean> {
242
+ if (!isBrowser || !navigator.clipboard) {
243
+ return Promise.resolve(false);
244
+ }
245
+ return navigator.clipboard
246
+ .writeText(text)
247
+ .then(() => true)
248
+ .catch(() => false);
108
249
  }
109
250
 
110
251
  /**
111
- * Header section with error type and message
252
+ * Header with error badge and message
112
253
  */
113
254
  function Header({ error }: { error: Error }) {
114
255
  const [copied, setCopied] = useState(false);
256
+ const [hovered, setHovered] = useState(false);
115
257
 
116
- const handleCopy = () => {
117
- copyToClipboard(error.stack || error.message);
118
- setCopied(true);
119
- setTimeout(() => setCopied(false), 2000);
258
+ useEffect(() => {
259
+ if (!copied) return;
260
+ const timer = setTimeout(() => setCopied(false), 2000);
261
+ return () => clearTimeout(timer);
262
+ }, [copied]);
263
+
264
+ const handleCopy = async () => {
265
+ const success = await copyToClipboard(error.stack || error.message);
266
+ if (success) setCopied(true);
120
267
  };
121
268
 
122
269
  return (
123
270
  <div style={styles.header}>
124
- <div style={styles.headerTop}>
125
- <div style={styles.badge}>{error.name}</div>
126
- <button type="button" onClick={handleCopy} style={styles.copyBtn}>
127
- {copied ? "Copied" : "Copy Stack"}
271
+ <div style={styles.headerRow}>
272
+ {/* Glowing error indicator */}
273
+ <div style={styles.errorIndicator}>
274
+ <div style={styles.errorGlow} />
275
+ <div style={styles.errorBadge}>{error.name}</div>
276
+ </div>
277
+
278
+ <button
279
+ type="button"
280
+ onClick={handleCopy}
281
+ onMouseEnter={() => setHovered(true)}
282
+ onMouseLeave={() => setHovered(false)}
283
+ style={{
284
+ ...styles.copyBtn,
285
+ ...(hovered ? styles.copyBtnHover : {}),
286
+ }}
287
+ >
288
+ {copied ? "✓ Copied" : "Copy"}
128
289
  </button>
129
290
  </div>
130
- <h1 style={styles.message}>{error.message}</h1>
131
- </div>
132
- );
133
- }
134
291
 
135
- /**
136
- * Stack trace section with expandable frames
137
- */
138
- function StackTraceSection({
139
- frames,
140
- visibleFrames,
141
- expanded,
142
- hiddenCount,
143
- onToggle,
144
- }: {
145
- frames: StackFrame[];
146
- visibleFrames: StackFrame[];
147
- expanded: boolean;
148
- hiddenCount: number;
149
- onToggle: () => void;
150
- }) {
151
- if (frames.length === 0) return null;
152
-
153
- return (
154
- <div style={styles.stackSection}>
155
- <div style={styles.stackHeader}>Call Stack</div>
156
- <div style={styles.frameList}>
157
- {visibleFrames.map((frame, i) => (
158
- <StackFrameRow key={i} frame={frame} index={i} />
159
- ))}
160
- {!expanded && hiddenCount > 0 && (
161
- <button type="button" onClick={onToggle} style={styles.expandBtn}>
162
- Show {hiddenCount} more frames
163
- </button>
164
- )}
165
- {expanded && hiddenCount > 0 && (
166
- <button type="button" onClick={onToggle} style={styles.expandBtn}>
167
- Show less
168
- </button>
169
- )}
170
- </div>
292
+ <h1 id="error-viewer-title" style={styles.message}>
293
+ {error.message}
294
+ </h1>
171
295
  </div>
172
296
  );
173
297
  }
@@ -175,239 +299,574 @@ function StackTraceSection({
175
299
  /**
176
300
  * Single stack frame row
177
301
  */
178
- function StackFrameRow({ frame, index }: { frame: StackFrame; index: number }) {
179
- const isFirst = index === 0;
302
+ function StackFrameRow({
303
+ frame,
304
+ index,
305
+ dimmed = false,
306
+ }: {
307
+ frame: StackFrame;
308
+ index: number;
309
+ dimmed?: boolean;
310
+ }) {
311
+ const [hovered, setHovered] = useState(false);
312
+ const isFirst = index === 0 && !dimmed;
180
313
  const fileName = frame.file.split("/").pop() || frame.file;
181
314
  const dirPath = frame.file.substring(0, frame.file.length - fileName.length);
182
315
 
183
- return (
184
- <div
185
- style={{
186
- ...styles.frame,
187
- ...(isFirst ? styles.frameFirst : {}),
188
- }}
189
- >
190
- <div style={styles.frameIndex}>{index + 1}</div>
316
+ // Build vscode:// link for clickable paths
317
+ const vsCodeLink =
318
+ frame.file && frame.line
319
+ ? `vscode://file${frame.file}:${frame.line}:${frame.col || 1}`
320
+ : null;
321
+
322
+ const content = (
323
+ <>
324
+ <div
325
+ style={{
326
+ ...styles.frameIndex,
327
+ color: isFirst ? "#ff6b6b" : dimmed ? "#555" : "#666",
328
+ }}
329
+ >
330
+ {String(index + 1).padStart(2, "0")}
331
+ </div>
191
332
  <div style={styles.frameContent}>
192
333
  {frame.fn && (
193
- <div style={styles.fnName}>
194
- {frame.fn}
334
+ <div
335
+ style={{
336
+ ...styles.fnName,
337
+ color: dimmed ? "#888" : "#f0f0f0",
338
+ }}
339
+ >
340
+ {formatFunctionName(frame.fn)}
195
341
  </div>
196
342
  )}
197
343
  <div style={styles.filePath}>
198
- <span style={styles.dirPath}>{dirPath}</span>
199
- <span style={styles.fileName}>{fileName}</span>
344
+ <span style={{ ...styles.dirPath, opacity: dimmed ? 0.6 : 0.8 }}>
345
+ {dirPath}
346
+ </span>
347
+ <span
348
+ style={{
349
+ ...styles.fileName,
350
+ color: dimmed ? "#5a9aba" : "#7cc4eb",
351
+ }}
352
+ >
353
+ {fileName}
354
+ </span>
200
355
  {frame.line && (
201
- <span style={styles.lineCol}>
202
- :{frame.line}:{frame.col}
356
+ <span
357
+ style={{
358
+ ...styles.lineCol,
359
+ color: dimmed ? "#9a8a40" : "#e5b83a",
360
+ }}
361
+ >
362
+ :{frame.line}
363
+ {frame.col && `:${frame.col}`}
203
364
  </span>
204
365
  )}
205
366
  </div>
206
367
  </div>
207
- </div>
368
+ </>
208
369
  );
370
+
371
+ const rowStyles: CSSProperties = {
372
+ ...styles.frame,
373
+ ...(isFirst ? styles.frameFirst : {}),
374
+ backgroundColor: hovered ? "rgba(255,255,255,0.03)" : "transparent",
375
+ };
376
+
377
+ if (vsCodeLink && isBrowser) {
378
+ return (
379
+ <a
380
+ href={vsCodeLink}
381
+ style={{ ...rowStyles, textDecoration: "none" }}
382
+ onMouseEnter={() => setHovered(true)}
383
+ onMouseLeave={() => setHovered(false)}
384
+ >
385
+ {content}
386
+ </a>
387
+ );
388
+ }
389
+
390
+ return <div style={rowStyles}>{content}</div>;
391
+ }
392
+
393
+ /**
394
+ * Format function name with syntax highlighting
395
+ */
396
+ function formatFunctionName(fn: string): React.ReactNode {
397
+ // Highlight async/Object/Class prefixes
398
+ const asyncMatch = fn.match(/^(async\s+)?(.+)$/);
399
+ if (asyncMatch?.[1]) {
400
+ return (
401
+ <>
402
+ <span style={{ color: "#c678dd" }}>async </span>
403
+ <span>{asyncMatch[2]}</span>
404
+ </>
405
+ );
406
+ }
407
+
408
+ // Highlight method calls like Object.method
409
+ const methodMatch = fn.match(/^(.+)\.([^.]+)$/);
410
+ if (methodMatch) {
411
+ return (
412
+ <>
413
+ <span style={{ color: "#e5c07b" }}>{methodMatch[1]}</span>
414
+ <span style={{ color: "#666" }}>.</span>
415
+ <span>{methodMatch[2]}</span>
416
+ </>
417
+ );
418
+ }
419
+
420
+ return fn;
209
421
  }
210
422
 
211
423
  /**
212
- * Production error view - minimal information
424
+ * Expand/collapse button
425
+ */
426
+ function ExpandButton({
427
+ onClick,
428
+ label,
429
+ }: {
430
+ onClick: () => void;
431
+ label: string;
432
+ }) {
433
+ const [hovered, setHovered] = useState(false);
434
+
435
+ return (
436
+ <button
437
+ type="button"
438
+ onClick={onClick}
439
+ onMouseEnter={() => setHovered(true)}
440
+ onMouseLeave={() => setHovered(false)}
441
+ style={{
442
+ ...styles.expandBtn,
443
+ backgroundColor: hovered ? "rgba(255,255,255,0.05)" : "transparent",
444
+ color: hovered ? "#aaa" : "#777",
445
+ }}
446
+ >
447
+ {label}
448
+ </button>
449
+ );
450
+ }
451
+
452
+ /**
453
+ * Production error view - minimal, user-friendly
213
454
  */
214
455
  function ErrorViewerProduction() {
456
+ const [hovered, setHovered] = useState(false);
457
+
458
+ const handleReload = () => {
459
+ if (isBrowser) window.location.reload();
460
+ };
461
+
215
462
  return (
216
- <div style={styles.overlay}>
463
+ <div style={styles.overlay} role="alertdialog" aria-modal="true">
217
464
  <div style={styles.prodContainer}>
218
- <div style={styles.prodIcon}>!</div>
219
- <h1 style={styles.prodTitle}>Application Error</h1>
465
+ <div style={styles.prodIcon}>
466
+ <svg
467
+ width="32"
468
+ height="32"
469
+ viewBox="0 0 24 24"
470
+ fill="none"
471
+ stroke="currentColor"
472
+ strokeWidth="2"
473
+ >
474
+ <circle cx="12" cy="12" r="10" />
475
+ <line x1="12" y1="8" x2="12" y2="12" />
476
+ <line x1="12" y1="16" x2="12.01" y2="16" />
477
+ </svg>
478
+ </div>
479
+ <h1 style={styles.prodTitle}>Something went wrong</h1>
220
480
  <p style={styles.prodMessage}>
221
- An unexpected error occurred. Please try again later.
481
+ We encountered an unexpected error. Please try again.
222
482
  </p>
223
483
  <button
224
484
  type="button"
225
- onClick={() => window.location.reload()}
226
- style={styles.prodButton}
485
+ onClick={handleReload}
486
+ onMouseEnter={() => setHovered(true)}
487
+ onMouseLeave={() => setHovered(false)}
488
+ style={{
489
+ ...styles.prodButton,
490
+ backgroundColor: hovered ? "#333" : "#222",
491
+ borderColor: hovered ? "#555" : "#444",
492
+ }}
227
493
  >
228
- Reload Page
494
+ Reload page
229
495
  </button>
230
496
  </div>
231
497
  </div>
232
498
  );
233
499
  }
234
500
 
501
+ // ---------------------------------------------------------------------------------------------------------------------
502
+ // Styles
503
+ // ---------------------------------------------------------------------------------------------------------------------
504
+
505
+ const MONO_FONT =
506
+ 'ui-monospace, "JetBrains Mono", "Fira Code", SFMono-Regular, Menlo, Monaco, Consolas, monospace';
507
+
235
508
  const styles: Record<string, CSSProperties> = {
236
509
  overlay: {
237
510
  position: "fixed",
238
511
  inset: 0,
239
- backgroundColor: "rgba(0, 0, 0, 0.8)",
512
+ backgroundColor: "rgba(0, 0, 0, 0.92)",
240
513
  display: "flex",
241
514
  alignItems: "flex-start",
242
515
  justifyContent: "center",
243
516
  padding: "40px 20px",
244
517
  overflow: "auto",
245
- fontFamily:
246
- '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
518
+ fontFamily: MONO_FONT,
519
+ fontSize: "13px",
247
520
  zIndex: 99999,
521
+ transition: "opacity 0.2s ease-out",
522
+ },
523
+
524
+ scanlines: {
525
+ position: "fixed",
526
+ inset: 0,
527
+ background:
528
+ "repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px)",
529
+ pointerEvents: "none",
530
+ zIndex: 100000,
248
531
  },
532
+
249
533
  container: {
250
534
  width: "100%",
251
- maxWidth: "960px",
252
- backgroundColor: "#1a1a1a",
253
- borderRadius: "12px",
535
+ maxWidth: "900px",
536
+ backgroundColor: "#0d0d0d",
537
+ borderRadius: "8px",
254
538
  overflow: "hidden",
255
- boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
539
+ boxShadow: "0 0 0 1px #333, 0 25px 80px -12px rgba(0, 0, 0, 0.8)",
540
+ transition: "transform 0.3s ease-out, opacity 0.3s ease-out",
541
+ },
542
+
543
+ // Terminal bar
544
+ terminalBar: {
545
+ display: "flex",
546
+ alignItems: "center",
547
+ padding: "12px 16px",
548
+ backgroundColor: "#1a1a1a",
549
+ borderBottom: "1px solid #333",
550
+ },
551
+
552
+ terminalDots: {
553
+ display: "flex",
554
+ gap: "8px",
555
+ },
556
+
557
+ dot: {
558
+ width: "12px",
559
+ height: "12px",
560
+ borderRadius: "50%",
561
+ },
562
+
563
+ terminalTitle: {
564
+ flex: 1,
565
+ textAlign: "center",
566
+ },
567
+
568
+ terminalTitleText: {
569
+ color: "#777",
570
+ fontSize: "12px",
571
+ letterSpacing: "0.5px",
572
+ },
573
+
574
+ terminalActions: {
575
+ display: "flex",
576
+ alignItems: "center",
577
+ gap: "6px",
578
+ },
579
+
580
+ kbd: {
581
+ display: "inline-block",
582
+ padding: "2px 6px",
583
+ backgroundColor: "#2a2a2a",
584
+ borderRadius: "4px",
585
+ fontSize: "11px",
586
+ color: "#aaa",
587
+ border: "1px solid #444",
588
+ },
589
+
590
+ kbdInline: {
591
+ display: "inline-block",
592
+ padding: "1px 5px",
593
+ backgroundColor: "#222",
594
+ borderRadius: "3px",
595
+ fontSize: "11px",
596
+ color: "#888",
597
+ border: "1px solid #444",
598
+ marginLeft: "4px",
599
+ marginRight: "4px",
600
+ },
601
+
602
+ kbdLabel: {
603
+ color: "#777",
604
+ fontSize: "11px",
256
605
  },
606
+
607
+ // Header
257
608
  header: {
258
- padding: "24px 28px",
609
+ padding: "24px",
259
610
  borderBottom: "1px solid #333",
260
- background: "linear-gradient(to bottom, #1f1f1f, #1a1a1a)",
261
611
  },
262
- headerTop: {
612
+
613
+ headerRow: {
263
614
  display: "flex",
264
615
  alignItems: "center",
265
616
  justifyContent: "space-between",
266
617
  marginBottom: "16px",
267
618
  },
268
- badge: {
619
+
620
+ errorIndicator: {
621
+ position: "relative",
622
+ display: "inline-flex",
623
+ },
624
+
625
+ errorGlow: {
626
+ position: "absolute",
627
+ inset: "-4px",
628
+ background:
629
+ "radial-gradient(ellipse at center, rgba(255,80,80,0.3) 0%, transparent 70%)",
630
+ borderRadius: "12px",
631
+ filter: "blur(8px)",
632
+ },
633
+
634
+ errorBadge: {
635
+ position: "relative",
269
636
  display: "inline-block",
270
- padding: "6px 12px",
271
- backgroundColor: "#dc2626",
272
- color: "#fff",
637
+ padding: "6px 14px",
638
+ backgroundColor: "#3d1a1a",
639
+ color: "#ff7b7b",
273
640
  fontSize: "12px",
274
641
  fontWeight: 600,
275
642
  borderRadius: "6px",
276
- letterSpacing: "0.025em",
643
+ border: "1px solid #5a2828",
644
+ letterSpacing: "0.5px",
277
645
  },
646
+
278
647
  copyBtn: {
279
- padding: "8px 16px",
648
+ padding: "8px 14px",
280
649
  backgroundColor: "transparent",
281
650
  color: "#888",
282
- fontSize: "13px",
651
+ fontSize: "12px",
283
652
  fontWeight: 500,
284
- border: "1px solid #444",
653
+ borderWidth: "1px",
654
+ borderStyle: "solid",
655
+ borderColor: "#444",
285
656
  borderRadius: "6px",
286
657
  cursor: "pointer",
287
658
  transition: "all 0.15s",
659
+ fontFamily: MONO_FONT,
288
660
  },
661
+
662
+ copyBtnHover: {
663
+ backgroundColor: "#252525",
664
+ color: "#bbb",
665
+ borderColor: "#555",
666
+ },
667
+
289
668
  message: {
290
669
  margin: 0,
291
- fontSize: "20px",
292
- fontWeight: 500,
293
- color: "#fff",
294
- lineHeight: 1.5,
670
+ fontSize: "18px",
671
+ fontWeight: 400,
672
+ color: "#e8e8e8",
673
+ lineHeight: 1.6,
295
674
  wordBreak: "break-word",
675
+ fontFamily: MONO_FONT,
296
676
  },
677
+
678
+ // Stack section
297
679
  stackSection: {
298
- padding: "0",
680
+ borderTop: "1px solid #2a2a2a",
299
681
  },
682
+
300
683
  stackHeader: {
301
- padding: "16px 28px",
302
- fontSize: "11px",
684
+ display: "flex",
685
+ alignItems: "center",
686
+ justifyContent: "space-between",
687
+ padding: "14px 24px",
688
+ borderBottom: "1px solid #2a2a2a",
689
+ },
690
+
691
+ stackHeaderText: {
692
+ fontSize: "10px",
303
693
  fontWeight: 600,
304
694
  color: "#666",
305
- textTransform: "uppercase",
306
- letterSpacing: "0.1em",
307
- borderBottom: "1px solid #2a2a2a",
695
+ letterSpacing: "1.5px",
308
696
  },
697
+
698
+ stackCount: {
699
+ fontSize: "11px",
700
+ color: "#555",
701
+ },
702
+
309
703
  frameList: {
310
704
  display: "flex",
311
705
  flexDirection: "column",
312
706
  },
707
+
313
708
  frame: {
314
709
  display: "flex",
315
710
  alignItems: "flex-start",
316
- padding: "14px 28px",
317
- borderBottom: "1px solid #252525",
318
- transition: "background-color 0.15s",
711
+ padding: "12px 24px",
712
+ borderBottom: "1px solid #222",
713
+ transition: "background-color 0.1s",
714
+ cursor: "pointer",
319
715
  },
716
+
320
717
  frameFirst: {
321
- backgroundColor: "rgba(220, 38, 38, 0.1)",
718
+ backgroundColor: "rgba(255, 80, 80, 0.08)",
719
+ borderLeft: "2px solid #ff6b6b",
322
720
  },
721
+
323
722
  frameIndex: {
324
723
  width: "28px",
325
724
  flexShrink: 0,
326
- fontSize: "12px",
725
+ fontSize: "11px",
327
726
  fontWeight: 500,
328
- color: "#555",
329
- fontFamily: "monospace",
727
+ fontFamily: MONO_FONT,
330
728
  },
729
+
331
730
  frameContent: {
332
731
  flex: 1,
333
732
  minWidth: 0,
334
733
  },
734
+
335
735
  fnName: {
336
- fontSize: "14px",
736
+ fontSize: "13px",
337
737
  fontWeight: 500,
338
- color: "#e5e5e5",
339
738
  marginBottom: "4px",
340
- fontFamily:
341
- 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
739
+ fontFamily: MONO_FONT,
342
740
  },
741
+
343
742
  filePath: {
344
- fontSize: "13px",
743
+ fontSize: "12px",
345
744
  color: "#888",
346
- fontFamily:
347
- 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
745
+ fontFamily: MONO_FONT,
348
746
  wordBreak: "break-all",
349
747
  },
748
+
350
749
  dirPath: {
351
- color: "#555",
750
+ color: "#666",
352
751
  },
752
+
353
753
  fileName: {
354
- color: "#0ea5e9",
754
+ color: "#7cc4eb",
355
755
  },
756
+
356
757
  lineCol: {
357
- color: "#eab308",
758
+ color: "#e5b83a",
358
759
  },
760
+
359
761
  expandBtn: {
360
- padding: "16px 28px",
762
+ width: "100%",
763
+ padding: "14px 24px",
361
764
  backgroundColor: "transparent",
362
- color: "#666",
363
- fontSize: "13px",
765
+ color: "#777",
766
+ fontSize: "12px",
364
767
  fontWeight: 500,
365
768
  border: "none",
366
- borderTop: "1px solid #252525",
769
+ borderTop: "1px solid #2a2a2a",
367
770
  cursor: "pointer",
368
771
  textAlign: "left",
369
772
  transition: "all 0.15s",
773
+ fontFamily: MONO_FONT,
774
+ },
775
+
776
+ nodeModulesToggle: {
777
+ display: "flex",
778
+ alignItems: "center",
779
+ gap: "10px",
780
+ width: "100%",
781
+ padding: "12px 24px",
782
+ backgroundColor: "#0a0a0a",
783
+ color: "#666",
784
+ fontSize: "11px",
785
+ fontWeight: 500,
786
+ border: "none",
787
+ borderTop: "1px solid #2a2a2a",
788
+ cursor: "pointer",
789
+ textAlign: "left",
790
+ fontFamily: MONO_FONT,
791
+ },
792
+
793
+ nodeModulesIcon: {
794
+ fontSize: "8px",
795
+ color: "#555",
796
+ },
797
+
798
+ nodeModulesLabel: {
799
+ flex: 1,
800
+ letterSpacing: "0.5px",
370
801
  },
802
+
803
+ nodeModulesCount: {
804
+ color: "#555",
805
+ },
806
+
807
+ nodeModulesFrames: {
808
+ backgroundColor: "#080808",
809
+ },
810
+
811
+ footer: {
812
+ padding: "14px 24px",
813
+ borderTop: "1px solid #2a2a2a",
814
+ backgroundColor: "#0a0a0a",
815
+ },
816
+
817
+ footerText: {
818
+ fontSize: "11px",
819
+ color: "#555",
820
+ },
821
+
822
+ // Production styles
371
823
  prodContainer: {
372
824
  textAlign: "center",
373
825
  padding: "60px 40px",
374
- backgroundColor: "#1a1a1a",
375
- borderRadius: "12px",
826
+ backgroundColor: "#0d0d0d",
827
+ borderRadius: "8px",
376
828
  maxWidth: "400px",
829
+ border: "1px solid #333",
377
830
  },
831
+
378
832
  prodIcon: {
379
833
  width: "64px",
380
834
  height: "64px",
381
835
  margin: "0 auto 24px",
382
- backgroundColor: "#dc2626",
383
- borderRadius: "50%",
836
+ color: "#666",
384
837
  display: "flex",
385
838
  alignItems: "center",
386
839
  justifyContent: "center",
387
- fontSize: "32px",
388
- fontWeight: 700,
389
- color: "#fff",
390
840
  },
841
+
391
842
  prodTitle: {
392
843
  margin: "0 0 12px",
393
- fontSize: "24px",
394
- fontWeight: 600,
395
- color: "#fff",
844
+ fontSize: "18px",
845
+ fontWeight: 500,
846
+ color: "#f0f0f0",
847
+ fontFamily: MONO_FONT,
396
848
  },
849
+
397
850
  prodMessage: {
398
851
  margin: "0 0 28px",
399
- fontSize: "15px",
852
+ fontSize: "13px",
400
853
  color: "#888",
401
854
  lineHeight: 1.6,
855
+ fontFamily: MONO_FONT,
402
856
  },
857
+
403
858
  prodButton: {
404
859
  padding: "12px 24px",
405
- backgroundColor: "#fff",
406
- color: "#000",
407
- fontSize: "14px",
408
- fontWeight: 600,
409
- border: "none",
410
- borderRadius: "8px",
860
+ backgroundColor: "#222",
861
+ color: "#bbb",
862
+ fontSize: "13px",
863
+ fontWeight: 500,
864
+ borderWidth: "1px",
865
+ borderStyle: "solid",
866
+ borderColor: "#444",
867
+ borderRadius: "6px",
411
868
  cursor: "pointer",
869
+ transition: "all 0.15s",
870
+ fontFamily: MONO_FONT,
412
871
  },
413
872
  };