@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.
- package/dist/Ryunix.esm.js +441 -28
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +442 -27
- package/dist/Ryunix.umd.js.map +1 -1
- package/dist/Ryunix.umd.min.js +1 -1
- package/dist/Ryunix.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/Ryunix.umd.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
1297
|
+
const newRoot = {
|
|
1281
1298
|
dom: activeRoot.dom,
|
|
1282
1299
|
props: activeRoot.props,
|
|
1283
1300
|
alternate: currentState.currentRoot || null,
|
|
1284
1301
|
};
|
|
1285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
2752
|
+
console.log(`[Ryunix Debug] init: hasChildNodes=${hasChildNodes}, has SSR content detected.`);
|
|
2652
2753
|
}
|
|
2653
2754
|
|
|
2654
|
-
if
|
|
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')
|
|
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
|
-
|
|
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;
|