@unsetsoft/ryunixjs 1.2.5-canary.0 → 1.2.5-canary.2

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.
@@ -7,7 +7,6 @@
7
7
  // Improved state management - avoid global mutable object
8
8
  // Instead, create a state manager that can be instantiated per render tree
9
9
 
10
-
11
10
  const rIC =
12
11
  typeof requestIdleCallback !== 'undefined'
13
12
  ? requestIdleCallback
@@ -24,7 +23,6 @@
24
23
  effects: [],
25
24
  });
26
25
 
27
- // Singleton for backward compatibility, but allows testing with isolated instances
28
26
  let globalState = createRenderState();
29
27
 
30
28
  const getState = () => globalState;
@@ -102,7 +100,12 @@
102
100
 
103
101
  const nextValidSibling$1 = (node) => {
104
102
  let next = node;
105
- while (next && (next.nodeType === 3 && !next.nodeValue.trim() || next.nodeType === 8)) {
103
+ while (
104
+ next &&
105
+ ((next.nodeType === 3 && !next.nodeValue.trim()) ||
106
+ next.nodeType === 8 ||
107
+ (next.nodeType === 1 && next.hasAttribute('data-ryunix-ssr')))
108
+ ) {
106
109
  next = next.nextSibling;
107
110
  }
108
111
  return next
@@ -257,6 +260,7 @@
257
260
  */
258
261
  const isGone = (next) => (key) => !(key in next);
259
262
 
263
+
260
264
  /**
261
265
  * Cancel effects for a single fiber
262
266
  * @param {Object} fiber - Fiber node
@@ -718,6 +722,8 @@
718
722
  });
719
723
  };
720
724
 
725
+
726
+
721
727
  /**
722
728
  * Clear all children from a DOM element
723
729
  * @param {HTMLElement} container - DOM element to clear
@@ -1077,6 +1083,7 @@
1077
1083
  alternate: matchedFiber,
1078
1084
  effectTag: EFFECT_TAGS.UPDATE,
1079
1085
  hooks: matchedFiber.hooks,
1086
+ stateError: matchedFiber.stateError,
1080
1087
  key: element.key,
1081
1088
  index,
1082
1089
  };
@@ -1221,6 +1228,11 @@
1221
1228
  };
1222
1229
 
1223
1230
  const useStore = (initialState, priority = getCurrentPriority()) => {
1231
+ // SSR safety check - more reliable than state.isServerRendering
1232
+ if (typeof window === 'undefined') {
1233
+ return [is.function(initialState) ? initialState() : initialState, () => { }]
1234
+ }
1235
+
1224
1236
  const state = getState();
1225
1237
  if (state.isServerRendering) {
1226
1238
  return [is.function(initialState) ? initialState() : initialState, () => { }]
@@ -1233,6 +1245,11 @@
1233
1245
 
1234
1246
 
1235
1247
  const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPriority()) => {
1248
+ // SSR safety check - more reliable than state.isServerRendering
1249
+ if (typeof window === 'undefined') {
1250
+ return [init ? init(initialState) : initialState, () => { }]
1251
+ }
1252
+
1236
1253
  const state = getState();
1237
1254
  if (state.isServerRendering) {
1238
1255
  return [init ? init(initialState) : initialState, () => { }]
@@ -1277,14 +1294,12 @@
1277
1294
 
1278
1295
  if (!activeRoot) return
1279
1296
 
1280
- currentState.wipRoot = {
1297
+ const newRoot = {
1281
1298
  dom: activeRoot.dom,
1282
1299
  props: activeRoot.props,
1283
1300
  alternate: currentState.currentRoot || null,
1284
1301
  };
1285
- currentState.deletions = [];
1286
- currentState.hookIndex = 0;
1287
- queueUpdate(() => scheduleWork$1(currentState.wipRoot, priority));
1302
+ queueUpdate(() => scheduleWork$1(newRoot, priority));
1288
1303
  };
1289
1304
 
1290
1305
  wipFiber.hooks[hookIndex] = hook;
@@ -1304,6 +1319,11 @@
1304
1319
  * of the values in the `deps` array have changed since the last render. If the `deps` array
1305
1320
  */
1306
1321
  const useEffect = (callback, deps) => {
1322
+ // SSR safety check - more reliable than state.isServerRendering
1323
+ if (typeof window === 'undefined') {
1324
+ return
1325
+ }
1326
+
1307
1327
  const state = getState();
1308
1328
  if (state.isServerRendering) {
1309
1329
  return
@@ -1343,6 +1363,11 @@
1343
1363
  * contains the initial value passed to the `useRef` function.
1344
1364
  */
1345
1365
  const useRef = (initialValue) => {
1366
+ // SSR safety check - more reliable than state.isServerRendering
1367
+ if (typeof window === 'undefined') {
1368
+ return { current: initialValue }
1369
+ }
1370
+
1346
1371
  const state = getState();
1347
1372
  if (state.isServerRendering) {
1348
1373
  return { current: initialValue }
@@ -1377,6 +1402,11 @@
1377
1402
  * @returns The `useMemo` function is returning the `value` calculated by the `compute` function.
1378
1403
  */
1379
1404
  const useMemo = (compute, deps) => {
1405
+ // SSR safety check - more reliable than state.isServerRendering
1406
+ if (typeof window === 'undefined') {
1407
+ return compute()
1408
+ }
1409
+
1380
1410
  const state = getState();
1381
1411
  if (state.isServerRendering) {
1382
1412
  return compute()
@@ -1655,8 +1685,26 @@
1655
1685
  * `value` prop set to `contextValue`, and wrapping the `children` within a `Fragment`.
1656
1686
  */
1657
1687
  const RouterProvider = ({ routes, children }) => {
1688
+ // SSR: Return server-safe version without hooks
1689
+ if (typeof window === 'undefined') {
1690
+ const location = '/';
1691
+ const currentRouteData = findRoute(routes, location) || {};
1692
+ const contextValue = {
1693
+ location,
1694
+ params: currentRouteData.params || {},
1695
+ query: {},
1696
+ navigate: () => {},
1697
+ route: currentRouteData.route,
1698
+ };
1699
+ return createElement(
1700
+ RouterContext.Provider,
1701
+ { value: contextValue },
1702
+ Fragment({ children }),
1703
+ )
1704
+ }
1705
+
1658
1706
  const [location, setLocation] = useStore(
1659
- typeof window !== 'undefined' ? window.location.pathname : '/'
1707
+ window.location.pathname
1660
1708
  );
1661
1709
 
1662
1710
  useEffect(() => {
@@ -1961,6 +2009,11 @@
1961
2009
  * @param {Array} deps - Dependencies array
1962
2010
  */
1963
2011
  const useLayoutEffect = (callback, deps) => {
2012
+ // SSR safety check - more reliable than state.isServerRendering
2013
+ if (typeof window === 'undefined') {
2014
+ return
2015
+ }
2016
+
1964
2017
  const state = getState();
1965
2018
  if (state.isServerRendering) {
1966
2019
  return
@@ -1995,6 +2048,14 @@
1995
2048
  // Counter for deterministic ID generation
1996
2049
  let idCounter = 0;
1997
2050
 
2051
+ /**
2052
+ * Reset the idCounter for useId - call this before each SSR renderToString
2053
+ * to ensure deterministic IDs across multiple renders
2054
+ */
2055
+ const resetIdCounter = () => {
2056
+ idCounter = 0;
2057
+ };
2058
+
1998
2059
  /**
1999
2060
  * useId - Generate a deterministic, unique ID that is stable across SSR and hydration.
2000
2061
  * @returns {string} A unique ID string
@@ -2082,6 +2143,7 @@
2082
2143
  NavLink: NavLink,
2083
2144
  RouterProvider: RouterProvider,
2084
2145
  createContext: createContext,
2146
+ resetIdCounter: resetIdCounter,
2085
2147
  useCallback: useCallback,
2086
2148
  useDebounce: useDebounce,
2087
2149
  useDeferredValue: useDeferredValue,
@@ -2116,6 +2178,21 @@
2116
2178
  fiber.effectTag = EFFECT_TAGS.HYDRATE;
2117
2179
  }
2118
2180
 
2181
+ // Memo bailout: skip re-render if props haven't changed
2182
+ if (fiber.type._isMemo && fiber.alternate) {
2183
+ const { children: _pc, ...prevRest } = fiber.alternate.props || {};
2184
+ const { children: _nc, ...nextRest } = fiber.props || {};
2185
+ if (fiber.type._arePropsEqual(prevRest, nextRest)) {
2186
+ fiber.hooks = fiber.alternate.hooks;
2187
+ const oldChild = fiber.alternate.child;
2188
+ if (oldChild) {
2189
+ oldChild.parent = fiber;
2190
+ fiber.child = oldChild;
2191
+ }
2192
+ return
2193
+ }
2194
+ }
2195
+
2119
2196
  let children = [fiber.type(fiber.props)];
2120
2197
 
2121
2198
  if (fiber.type._contextId && fiber.props.value !== undefined) {
@@ -2435,6 +2512,22 @@
2435
2512
  } catch (error) {
2436
2513
  if (process.env.NODE_ENV !== 'production') {
2437
2514
  console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
2515
+
2516
+ try {
2517
+ // Attempt to attach original JSX source map for DevOverlay lookup
2518
+ const src = fiber.props && fiber.props.__source;
2519
+ if (src && error && typeof error === 'object') {
2520
+ error.__ryunix_source = src;
2521
+ }
2522
+
2523
+ let targetFiber = fiber;
2524
+ while (!error.__ryunix_source && targetFiber) {
2525
+ if (targetFiber.props && targetFiber.props.__source) {
2526
+ error.__ryunix_source = targetFiber.props.__source;
2527
+ }
2528
+ targetFiber = targetFiber.parent;
2529
+ }
2530
+ } catch(e) {}
2438
2531
  }
2439
2532
 
2440
2533
  // Traverse upwards to find nearest ErrorBoundary
@@ -2536,7 +2629,7 @@
2536
2629
  }
2537
2630
  };
2538
2631
 
2539
- // ... performUnitOfWork stays same ...
2632
+
2540
2633
 
2541
2634
  const scheduleWork = (root, priority = getCurrentPriority()) => {
2542
2635
  const state = getState();
@@ -2609,7 +2702,12 @@
2609
2702
 
2610
2703
  const nextValidSibling = (node) => {
2611
2704
  let next = node;
2612
- while (next && (next.nodeType === 3 && !next.nodeValue.trim() || next.nodeType === 8)) {
2705
+ while (
2706
+ next &&
2707
+ ((next.nodeType === 3 && !next.nodeValue.trim()) ||
2708
+ next.nodeType === 8 ||
2709
+ (next.nodeType === 1 && next.hasAttribute('data-ryunix-ssr')))
2710
+ ) {
2613
2711
  next = next.nextSibling;
2614
2712
  }
2615
2713
  return next
@@ -2647,11 +2745,17 @@
2647
2745
  state.isHydrating = false;
2648
2746
  state.hydrationFailed = false;
2649
2747
 
2748
+ // Auto-detect SSR based on child nodes - no need to manually set process.env.RYUNIX_SSR
2749
+ const hasChildNodes = state.containerRoot && state.containerRoot.hasChildNodes();
2750
+
2650
2751
  if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
2651
- console.log(`[Ryunix Debug] init: RYUNIX_SSR=${process.env.RYUNIX_SSR}, hasChildNodes=${state.containerRoot.hasChildNodes()}`);
2752
+ console.log(`[Ryunix Debug] init: hasChildNodes=${hasChildNodes}, has SSR content detected.`);
2652
2753
  }
2653
2754
 
2654
- if (process.env.RYUNIX_SSR && state.containerRoot.hasChildNodes()) {
2755
+ // Auto-detect: if there's existing content, try to hydrate (SSR)
2756
+ // If explicitly disabled via RYUNIX_SSR=false, skip hydration
2757
+ const ssrEnabled = process.env.RYUNIX_SSR !== 'false';
2758
+ if (hasChildNodes && ssrEnabled) {
2655
2759
  if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
2656
2760
  console.log(`[Ryunix Debug] init: SSR content detected. Starting hydration on #${root}`);
2657
2761
  }
@@ -2782,7 +2886,7 @@
2782
2886
  if (value) {
2783
2887
  attributes += ` class="${escapeHtml(value)}"`;
2784
2888
  }
2785
- } else if (!key.startsWith('on')) { // Ignore event listeners
2889
+ } else if (!key.startsWith('on') && key !== '__source' && key !== '__self') {
2786
2890
  if (typeof value === 'boolean') {
2787
2891
  if (value) attributes += ` ${key}=""`;
2788
2892
  } else if (value != null) {
@@ -2940,7 +3044,7 @@ function $RC(id, templateId) {
2940
3044
  if (value) {
2941
3045
  attributes += ` class="${escapeHtml(value)}"`;
2942
3046
  }
2943
- } else if (!key.startsWith('on')) {
3047
+ } else if (!key.startsWith('on') && key !== '__source' && key !== '__self') {
2944
3048
  if (typeof value === 'boolean') {
2945
3049
  if (value) attributes += ` ${key}=""`;
2946
3050
  } else if (value != null) {
@@ -2974,6 +3078,9 @@ function $RC(id, templateId) {
2974
3078
  const state = getState();
2975
3079
  const encoder = new TextEncoder();
2976
3080
 
3081
+ // Reset idCounter for deterministic useId values
3082
+ resetIdCounter();
3083
+
2977
3084
  return new ReadableStream({
2978
3085
  async start(controller) {
2979
3086
  const wasServerRendering = state.isServerRendering;
@@ -2986,7 +3093,7 @@ function $RC(id, templateId) {
2986
3093
  try {
2987
3094
  // 0. Inject RC helper script first
2988
3095
  const nonceAttr = options.nonce ? ` nonce="${options.nonce}"` : '';
2989
- push(`<script${nonceAttr}>${RC_SCRIPT}</script>`);
3096
+ push(`<script${nonceAttr} data-ryunix-ssr>${RC_SCRIPT}</script>`);
2990
3097
 
2991
3098
  // 1. Render initial tree (with fallbacks)
2992
3099
  await renderToStreamImpl(element, push, suspenseTasks);
@@ -2998,8 +3105,8 @@ function $RC(id, templateId) {
2998
3105
  const task = suspenseTasks.shift();
2999
3106
  const res = await task;
3000
3107
  if (res.success) {
3001
- push(`<template id="P:${res.id}">${res.content}</template>`);
3002
- push(`<script${nonceAttr}>$RC("S:${res.id}", "P:${res.id}")</script>`);
3108
+ push(`<template id="P:${res.id}" data-ryunix-ssr>${res.content}</template>`);
3109
+ push(`<script${nonceAttr} data-ryunix-ssr>$RC("S:${res.id}", "P:${res.id}")</script>`);
3003
3110
  }
3004
3111
  }
3005
3112
 
@@ -3018,6 +3125,10 @@ function $RC(id, templateId) {
3018
3125
  const wasServerRendering = state.isServerRendering;
3019
3126
  state.isServerRendering = true;
3020
3127
  state.ssrMetadata = {};
3128
+
3129
+ // Reset idCounter for deterministic useId values
3130
+ resetIdCounter();
3131
+
3021
3132
  try {
3022
3133
  return renderToStringImpl(element)
3023
3134
  } finally {
@@ -3048,18 +3159,12 @@ function $RC(id, templateId) {
3048
3159
  * @returns {Function} Memoized component
3049
3160
  */
3050
3161
  const memo = (Component, arePropsEqual = shallowEqual) => {
3051
- let prevProps = null;
3052
- let prevResult = null;
3053
-
3054
3162
  const MemoizedComponent = (props) => {
3055
- if (prevProps && arePropsEqual(prevProps, props)) {
3056
- return prevResult
3057
- }
3058
- prevProps = props;
3059
- prevResult = Component(props);
3060
- return prevResult
3163
+ return Component(props)
3061
3164
  };
3062
-
3165
+ MemoizedComponent._isMemo = true;
3166
+ MemoizedComponent._wrappedComponent = Component;
3167
+ MemoizedComponent._arePropsEqual = arePropsEqual;
3063
3168
  MemoizedComponent.displayName = `Memo(${Component.displayName || Component.name || 'Component'})`;
3064
3169
  return MemoizedComponent
3065
3170
  };
@@ -3300,6 +3405,312 @@ function $RC(id, templateId) {
3300
3405
  };
3301
3406
  }
3302
3407
 
3408
+ function RyunixDevOverlay(propsOrError) {
3409
+ // If propsOrError is an event or wrapped object, try to extract error
3410
+ const rawError = propsOrError && propsOrError.nativeEvent
3411
+ ? propsOrError.error
3412
+ : propsOrError;
3413
+
3414
+ let error = rawError instanceof Error || (rawError && rawError.message)
3415
+ ? rawError
3416
+ : (rawError?.error || rawError);
3417
+
3418
+ // Debug string if error is broken
3419
+ const debugObjectStr = JSON.stringify(propsOrError, Object.getOwnPropertyNames(propsOrError || {}));
3420
+
3421
+ const [snippet, setSnippet] = useStore(null);
3422
+ const [startLine, setStartLine] = useStore(1);
3423
+ const [errorFile, setErrorFile] = useStore('');
3424
+ const [errorLine, setErrorLine] = useStore(0);
3425
+
3426
+ // Normalize stack ensuring we have lines
3427
+ let stackLines = [];
3428
+ if (error && error.stack) {
3429
+ stackLines = typeof error.stack === 'string' ? error.stack.split('\n').filter(line => {
3430
+ const trimmed = line.trim();
3431
+ if (!trimmed) return false;
3432
+ if (trimmed.includes('node_modules')) return false;
3433
+ // Filter out internal Ryunix core framework files to isolate user code
3434
+ const isInternal = [
3435
+ 'components.js', 'workers.js', 'reconciler.js', 'commits.js',
3436
+ 'hooks.js', 'errorBoundary.js', 'serverBoundary.js', 'app-router.js',
3437
+ 'app-router-server.js', 'render.js', 'createElement.js', 'index.js'
3438
+ ].some(file => trimmed.includes(file));
3439
+ return !isInternal;
3440
+ }) : error.stack;
3441
+ }
3442
+
3443
+ const errorName = error && error.name ? error.name : 'Unknown Error Type';
3444
+ const errorMessage = error && error.message ? error.message : `Raw unhandled error. Debug: ${debugObjectStr}`;
3445
+
3446
+ useEffect(() => {
3447
+ let targetPath = null;
3448
+ let targetLine = null;
3449
+
3450
+ // 1. Direct JSX __source mapping (injected by Webpack/SWC)
3451
+ if (error && error.__ryunix_source && error.__ryunix_source.fileName) {
3452
+ targetPath = error.__ryunix_source.fileName;
3453
+ targetLine = error.__ryunix_source.lineNumber;
3454
+ }
3455
+
3456
+ // 2. Fallback to Regex Stack Parsing
3457
+ if (!targetPath || !targetLine) {
3458
+ for (let i = 0; i < stackLines.length; i++) {
3459
+ const line = stackLines[i];
3460
+ if (!line.includes(':')) continue;
3461
+
3462
+ // Deterministic string-based parsing (no regex on uncontrolled data)
3463
+ let matchedPath = null;
3464
+ let matchedLine = null;
3465
+
3466
+ // V8 format: "at fn (file:line:col)" — extract content between parens
3467
+ const parenOpen = line.indexOf('(');
3468
+ const parenClose = line.lastIndexOf(')');
3469
+ if (parenOpen !== -1 && parenClose > parenOpen) {
3470
+ const inner = line.slice(parenOpen + 1, parenClose);
3471
+ const c2 = inner.lastIndexOf(':');
3472
+ const c1 = c2 > 0 ? inner.lastIndexOf(':', c2 - 1) : -1;
3473
+ if (c1 > 0) {
3474
+ const col = inner.slice(c2 + 1);
3475
+ const ln = inner.slice(c1 + 1, c2);
3476
+ if (/^\d+$/.test(ln) && /^\d+$/.test(col)) {
3477
+ matchedPath = inner.slice(0, c1);
3478
+ matchedLine = parseInt(ln, 10);
3479
+ }
3480
+ }
3481
+ }
3482
+
3483
+ // V8 format without parens: "at file:line:col"
3484
+ if (!matchedPath) {
3485
+ const trimmed = line.trim();
3486
+ if (trimmed.startsWith('at ')) {
3487
+ const rest = trimmed.slice(3).trim();
3488
+ const c2 = rest.lastIndexOf(':');
3489
+ const c1 = c2 > 0 ? rest.lastIndexOf(':', c2 - 1) : -1;
3490
+ if (c1 > 0) {
3491
+ const col = rest.slice(c2 + 1);
3492
+ const ln = rest.slice(c1 + 1, c2);
3493
+ if (/^\d+$/.test(ln) && /^\d+$/.test(col)) {
3494
+ matchedPath = rest.slice(0, c1);
3495
+ matchedLine = parseInt(ln, 10);
3496
+ }
3497
+ }
3498
+ }
3499
+ }
3500
+
3501
+ // Ryunix format: "file.ryx:line" or "file.jsx:line"
3502
+ if (!matchedPath) {
3503
+ const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
3504
+ const c1 = line.lastIndexOf(':');
3505
+ if (c1 > 0) {
3506
+ const ln = line.slice(c1 + 1).trim();
3507
+ const filePart = line.slice(0, c1).trim();
3508
+ if (/^\d+$/.test(ln) && exts.some(ext => filePart.endsWith(ext))) {
3509
+ matchedPath = filePart;
3510
+ matchedLine = parseInt(ln, 10);
3511
+ }
3512
+ }
3513
+ }
3514
+
3515
+ if (matchedPath && matchedLine) {
3516
+ targetPath = matchedPath;
3517
+ targetLine = matchedLine;
3518
+ break;
3519
+ }
3520
+ }
3521
+ }
3522
+
3523
+ if (targetPath && targetLine) {
3524
+ setErrorFile(targetPath);
3525
+ setErrorLine(targetLine);
3526
+ fetch(`/_ryunix/source?file=${encodeURIComponent(targetPath)}&line=${targetLine}`)
3527
+ .then(res => res.json())
3528
+ .then(data => {
3529
+ if (data.snippet) {
3530
+ setSnippet(data.snippet);
3531
+ setStartLine(data.startLine);
3532
+ }
3533
+ })
3534
+ .catch(err => console.error('Failed to fetch source snippet', err));
3535
+ }
3536
+ }, [error]);
3537
+
3538
+ const overlayStyle = {
3539
+ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: 2147483647,
3540
+ backgroundColor: 'rgba(0, 0, 0, 0.85)', backdropFilter: 'blur(8px)',
3541
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
3542
+ padding: '20px', fontFamily: 'system-ui, -apple-system, sans-serif'
3543
+ };
3544
+
3545
+ const modalStyle = {
3546
+ backgroundColor: '#0c0c0c', width: '100%', maxWidth: '1000px', maxHeight: '90vh',
3547
+ borderRadius: '12px', boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.8)',
3548
+ display: 'flex', flexDirection: 'column', overflow: 'hidden',
3549
+ border: '1px solid #333'
3550
+ };
3551
+
3552
+ const headerStyle = {
3553
+ backgroundColor: '#161616', padding: '16px 24px', borderBottom: '1px solid #333',
3554
+ display: 'flex', justifyContent: 'space-between', alignItems: 'center'
3555
+ };
3556
+
3557
+ const badgeStyle = {
3558
+ backgroundColor: 'rgba(239, 68, 68, 0.2)', color: '#ef4444', padding: '4px 8px',
3559
+ borderRadius: '4px', fontSize: '13px', fontWeight: 'bold', textTransform: 'uppercase',
3560
+ letterSpacing: '0.05em'
3561
+ };
3562
+
3563
+ const contentStyle = {
3564
+ padding: '32px', overflowY: 'auto', flex: 1, color: '#fff'
3565
+ };
3566
+
3567
+ const titleStyle = {
3568
+ fontSize: '24px', fontWeight: 'bold', marginBottom: '24px',
3569
+ fontFamily: 'ui-monospace, monospace', wordBreak: 'break-word', lineHeight: 1.4
3570
+ };
3571
+
3572
+ const snippetContainerStyle = {
3573
+ backgroundColor: '#000', borderRadius: '8px', border: '1px solid #333',
3574
+ padding: '16px', fontFamily: 'ui-monospace, monospace', fontSize: '14px',
3575
+ overflowX: 'auto', color: '#d1d5db', marginBottom: '32px',
3576
+ whiteSpace: 'pre-wrap',
3577
+ maxHeight: '150px',
3578
+ height: 'auto'
3579
+ };
3580
+
3581
+ const lineStyle = (isErrorLine) => ({
3582
+ display: 'flex',
3583
+ backgroundColor: isErrorLine ? 'rgba(239, 68, 68, 0.15)' : 'transparent',
3584
+ padding: '2px 8px',
3585
+ borderRadius: '4px',
3586
+ borderLeft: isErrorLine ? '3px solid #ef4444' : '3px solid transparent'
3587
+ });
3588
+
3589
+ const snippetLines = snippet ? snippet.split('\n') : [];
3590
+
3591
+ let badgeText = 'UNHANDLED RUNTIME ERROR';
3592
+ if (errorName && errorName !== 'Error' && errorName !== 'Unknown Error Type') {
3593
+ badgeText = errorName.replace(/([a-z])([A-Z])/g, '$1 $2').toUpperCase();
3594
+ }
3595
+
3596
+ return createElement('div', { style: overlayStyle },
3597
+ createElement('div', { style: modalStyle },
3598
+ createElement('div', { style: headerStyle },
3599
+ createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '12px' } },
3600
+ createElement('span', { style: badgeStyle }, badgeText),
3601
+ createElement('span', { style: { color: '#9ca3af', fontSize: '14px' } }, 'Ryunix Development')
3602
+ ),
3603
+ createElement('button', {
3604
+ onClick: () => window.location.reload(),
3605
+ style: { background: 'none', border: 'none', color: '#9ca3af', cursor: 'pointer', outline: 'none' },
3606
+ title: 'Reload page'
3607
+ },
3608
+ createElement('svg', { width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' },
3609
+ createElement('path', { d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }),
3610
+ createElement('path', { d: 'M3 3v5h5' })
3611
+ )
3612
+ )
3613
+ ),
3614
+ createElement('div', { style: contentStyle },
3615
+ createElement('h1', { style: titleStyle },
3616
+ createElement('span', { style: { color: '#f87171' } }, errorName),
3617
+ ': ', errorMessage
3618
+ ),
3619
+
3620
+ errorFile && createElement('div', { style: { marginBottom: '16px', color: '#9ca3af', fontSize: '14px', display: 'flex', alignItems: 'center', gap: '8px' } },
3621
+ createElement('svg', { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' },
3622
+ createElement('path', { d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z' }),
3623
+ createElement('polyline', { points: '13 2 13 9 20 9' })
3624
+ ),
3625
+ errorFile, ':', errorLine
3626
+ ),
3627
+
3628
+ snippet && createElement('div', { style: snippetContainerStyle },
3629
+ createElement('div', { style: { display: 'flex', flexDirection: 'column' } },
3630
+ ...snippetLines.map((lineText, index) => {
3631
+ const currentLineNumber = startLine + index;
3632
+ const isErrorLine = currentLineNumber === errorLine;
3633
+ return createElement('div', { key: index, style: lineStyle(isErrorLine) },
3634
+ createElement('span', { style: { color: '#6b7280', width: '40px', userSelect: 'none', textAlign: 'right', marginRight: '16px', display: 'inline-block' } }, currentLineNumber),
3635
+ createElement('span', { style: { color: isErrorLine ? '#f87171' : '#e5e7eb', whiteSpace: 'pre' } }, lineText || ' ')
3636
+ );
3637
+ })
3638
+ )
3639
+ ),
3640
+
3641
+ createElement('div', { style: { marginBottom: '16px' } },
3642
+ createElement('p', { style: { color: '#9ca3af', fontSize: '14px', marginBottom: '8px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em' } }, 'Call Stack'),
3643
+ createElement('div', { style: snippetContainerStyle },
3644
+ stackLines.length > 0 ? createElement('ul', { style: { listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '12px' } },
3645
+ ...stackLines.map((line, i) => {
3646
+ if (i === 0 && (line.startsWith('Error:') || line.startsWith('TypeError:'))) return null;
3647
+
3648
+ // Deterministic string-based stack frame parsing (no polynomial regex)
3649
+ const trimmed = line.trim();
3650
+ let fnName = '<anonymous>';
3651
+ let filePath = line;
3652
+
3653
+ // V8 format: "at fnName (file:line:col)" or "at file:line:col"
3654
+ if (trimmed.startsWith('at ')) {
3655
+ const rest = trimmed.slice(3);
3656
+ const parenOpen = rest.indexOf('(');
3657
+ const parenClose = rest.lastIndexOf(')');
3658
+ if (parenOpen !== -1 && parenClose > parenOpen) {
3659
+ fnName = rest.slice(0, parenOpen).trim() || '<anonymous>';
3660
+ filePath = rest.slice(parenOpen + 1, parenClose);
3661
+ } else {
3662
+ // "at file:line:col" — no function name
3663
+ if (rest.includes(':')) {
3664
+ fnName = '<anonymous>';
3665
+ } else {
3666
+ fnName = rest;
3667
+ }
3668
+ filePath = rest;
3669
+ }
3670
+ }
3671
+ // Firefox format: "fnName@file:line:col"
3672
+ else if (trimmed.includes('@')) {
3673
+ const atIdx = trimmed.indexOf('@');
3674
+ fnName = trimmed.slice(0, atIdx) || '<anonymous>';
3675
+ filePath = trimmed.slice(atIdx + 1);
3676
+ }
3677
+ // Ryunix format: "fnName file.ext:line"
3678
+ else {
3679
+ const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
3680
+ const parts = trimmed.split(/\s+/);
3681
+ if (parts.length >= 2) {
3682
+ const lastPart = parts[parts.length - 1];
3683
+ const colonIdx = lastPart.indexOf(':');
3684
+ const fileCandidate = colonIdx > 0 ? lastPart.slice(0, colonIdx) : lastPart;
3685
+ if (exts.some(ext => fileCandidate.endsWith(ext))) {
3686
+ fnName = parts.slice(0, -1).join(' ');
3687
+ filePath = lastPart;
3688
+ } else {
3689
+ fnName = parts[0];
3690
+ filePath = parts.slice(1).join(' ');
3691
+ }
3692
+ }
3693
+ }
3694
+
3695
+ return createElement('li', { key: i },
3696
+ createElement('span', { style: { color: '#60a5fa', fontWeight: 600 } }, fnName),
3697
+ createElement('div', { style: { color: '#6b7280', marginTop: '4px', paddingLeft: '16px', display: 'flex', alignItems: 'center', gap: '8px' } },
3698
+ createElement('svg', { width: '12', height: '12', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' },
3699
+ createElement('path', { d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z' }),
3700
+ createElement('polyline', { points: '13 2 13 9 20 9' })
3701
+ ),
3702
+ filePath
3703
+ )
3704
+ );
3705
+ })
3706
+ ) : createElement('div', { style: { color: '#6b7280', fontStyle: 'italic' } }, 'No stack trace available.')
3707
+ )
3708
+ )
3709
+ )
3710
+ )
3711
+ );
3712
+ }
3713
+
3303
3714
  var Ryunix = /*#__PURE__*/Object.freeze({
3304
3715
  __proto__: null,
3305
3716
  Children: Children,
@@ -3310,6 +3721,7 @@ function $RC(id, templateId) {
3310
3721
  NavLink: NavLink,
3311
3722
  Priority: Priority,
3312
3723
  RouterProvider: RouterProvider,
3724
+ RyunixDevOverlay: RyunixDevOverlay,
3313
3725
  ServerBoundary: ServerBoundary,
3314
3726
  Suspense: Suspense,
3315
3727
  batchUpdates: batchUpdates,
@@ -3333,6 +3745,7 @@ function $RC(id, templateId) {
3333
3745
  renderToReadableStream: renderToReadableStream,
3334
3746
  renderToString: renderToString,
3335
3747
  renderToStringAsync: renderToStringAsync,
3748
+ resetIdCounter: resetIdCounter,
3336
3749
  safeRender: safeRender,
3337
3750
  shallowEqual: shallowEqual,
3338
3751
  useCallback: useCallback,
@@ -3374,6 +3787,7 @@ function $RC(id, templateId) {
3374
3787
  exports.NavLink = NavLink;
3375
3788
  exports.Priority = Priority;
3376
3789
  exports.RouterProvider = RouterProvider;
3790
+ exports.RyunixDevOverlay = RyunixDevOverlay;
3377
3791
  exports.ServerBoundary = ServerBoundary;
3378
3792
  exports.Suspense = Suspense;
3379
3793
  exports.batchUpdates = batchUpdates;
@@ -3400,6 +3814,7 @@ function $RC(id, templateId) {
3400
3814
  exports.renderToReadableStream = renderToReadableStream;
3401
3815
  exports.renderToString = renderToString;
3402
3816
  exports.renderToStringAsync = renderToStringAsync;
3817
+ exports.resetIdCounter = resetIdCounter;
3403
3818
  exports.safeRender = safeRender;
3404
3819
  exports.shallowEqual = shallowEqual;
3405
3820
  exports.useCallback = useCallback;