reactoradar 1.5.7 → 1.5.9
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/app.js +286 -71
- package/bin/setup.js +33 -3
- package/package.json +1 -1
- package/sdk/RNDebugSDK.js +6 -5
- package/styles.css +12 -0
package/app.js
CHANGED
|
@@ -67,7 +67,7 @@ function renderJSON(val) {
|
|
|
67
67
|
try {
|
|
68
68
|
const str = typeof val === 'string' ? val : JSON.stringify(val, null, 2);
|
|
69
69
|
return syntaxHighlight(esc(str));
|
|
70
|
-
} catch { return esc(String(val)); }
|
|
70
|
+
} catch { return esc(typeof val === 'object' ? JSON.stringify(val) : String(val)); }
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function tryURL(url) { try { return new URL(url); } catch { return null; } }
|
|
@@ -239,7 +239,7 @@ if (window.electronAPI) {
|
|
|
239
239
|
window.electronAPI.on('clear-all-ui', clearAll);
|
|
240
240
|
|
|
241
241
|
// Cmd+F — focus the search input for the active panel
|
|
242
|
-
|
|
242
|
+
function _handleFind() {
|
|
243
243
|
const searchMap = {
|
|
244
244
|
console: 'consoleSearch',
|
|
245
245
|
network: 'netSearchInput',
|
|
@@ -257,6 +257,14 @@ if (window.electronAPI) {
|
|
|
257
257
|
const bar = $('consoleFindBar');
|
|
258
258
|
if (bar) { bar.style.display = 'flex'; $('consoleFindInput')?.focus(); }
|
|
259
259
|
}
|
|
260
|
+
}
|
|
261
|
+
window.electronAPI.on('focus-search', _handleFind);
|
|
262
|
+
// Direct keyboard fallback — Electron menu accelerators can miss in some contexts
|
|
263
|
+
document.addEventListener('keydown', (e) => {
|
|
264
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'f') {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
_handleFind();
|
|
267
|
+
}
|
|
260
268
|
});
|
|
261
269
|
|
|
262
270
|
window.electronAPI.on('app-version', (version) => {
|
|
@@ -373,7 +381,7 @@ function initConsolePanel() {
|
|
|
373
381
|
<div class="empty-state" id="consoleEmpty">
|
|
374
382
|
<div class="icon">⬛</div>
|
|
375
383
|
<div class="label">No logs yet</div>
|
|
376
|
-
<div class="hint">
|
|
384
|
+
<div class="hint">Logs will appear here automatically</div>
|
|
377
385
|
</div>
|
|
378
386
|
</div>
|
|
379
387
|
<div class="console-find-bar" id="consoleFindBar" style="display:none">
|
|
@@ -519,23 +527,29 @@ function flushConsoleBatch() {
|
|
|
519
527
|
let added = 0;
|
|
520
528
|
|
|
521
529
|
batch.forEach(l => {
|
|
522
|
-
|
|
530
|
+
// Redux logs use showRedux flag; regular logs use levelFilters
|
|
531
|
+
if (l.level === 'redux') {
|
|
532
|
+
if (!state.console.showRedux) return;
|
|
533
|
+
} else if (levelFilters && !levelFilters[l.level]) return;
|
|
523
534
|
if (searchFilter && !l.message?.toLowerCase().includes(searchFilter)) return;
|
|
524
535
|
frag.appendChild(buildLogRow(l));
|
|
525
536
|
added++;
|
|
526
537
|
});
|
|
527
538
|
|
|
528
539
|
if (added > 0) {
|
|
529
|
-
empty
|
|
540
|
+
// Hide empty state as soon as we have visible rows
|
|
541
|
+
if (empty) empty.style.display = 'none';
|
|
542
|
+
// Auto-scroll only if user is already near the bottom (within 150px)
|
|
543
|
+
const wasAtBottom = (list.scrollHeight - list.scrollTop - list.clientHeight) < 150;
|
|
530
544
|
list.appendChild(frag);
|
|
531
|
-
// Keep DOM size manageable — remove oldest rows if over
|
|
545
|
+
// Keep DOM size manageable — remove oldest rows if over limit
|
|
532
546
|
const rows = list.querySelectorAll('.log-row');
|
|
533
|
-
const MAX_DOM_ROWS =
|
|
547
|
+
const MAX_DOM_ROWS = 5000;
|
|
534
548
|
if (rows.length > MAX_DOM_ROWS) {
|
|
535
549
|
const toRemove = rows.length - MAX_DOM_ROWS;
|
|
536
550
|
for (let i = 0; i < toRemove; i++) rows[i].remove();
|
|
537
551
|
}
|
|
538
|
-
list.scrollTop = list.scrollHeight;
|
|
552
|
+
if (wasAtBottom) list.scrollTop = list.scrollHeight;
|
|
539
553
|
}
|
|
540
554
|
}
|
|
541
555
|
|
|
@@ -682,6 +696,14 @@ function createTreeNode(key, val, startCollapsed) {
|
|
|
682
696
|
return container;
|
|
683
697
|
}
|
|
684
698
|
|
|
699
|
+
function _safeStr(val) {
|
|
700
|
+
if (val === null) return 'null';
|
|
701
|
+
if (val === undefined) return 'undefined';
|
|
702
|
+
if (typeof val === 'string') return val;
|
|
703
|
+
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
704
|
+
try { return JSON.stringify(val, null, 2); } catch { return String(val); }
|
|
705
|
+
}
|
|
706
|
+
|
|
685
707
|
function createPrimitiveSpan(val) {
|
|
686
708
|
const s = document.createElement('span');
|
|
687
709
|
if (val === null) { s.className = 'ov-null'; s.textContent = 'null'; }
|
|
@@ -689,7 +711,7 @@ function createPrimitiveSpan(val) {
|
|
|
689
711
|
else if (typeof val === 'string') { s.className = 'ov-str'; s.textContent = `"${val}"`; }
|
|
690
712
|
else if (typeof val === 'number') { s.className = 'ov-num'; s.textContent = String(val); }
|
|
691
713
|
else if (typeof val === 'boolean') { s.className = 'ov-bool'; s.textContent = String(val); }
|
|
692
|
-
else { s.textContent =
|
|
714
|
+
else { s.textContent = _safeStr(val); }
|
|
693
715
|
return s;
|
|
694
716
|
}
|
|
695
717
|
|
|
@@ -699,7 +721,7 @@ function renderConsoleArg(arg) {
|
|
|
699
721
|
// Backward compat: raw string
|
|
700
722
|
const s = document.createElement('span');
|
|
701
723
|
s.className = 'ov-str';
|
|
702
|
-
s.textContent =
|
|
724
|
+
s.textContent = _safeStr(arg);
|
|
703
725
|
return s;
|
|
704
726
|
}
|
|
705
727
|
const { t, v } = arg;
|
|
@@ -717,7 +739,7 @@ function renderConsoleArg(arg) {
|
|
|
717
739
|
return createTreeNode(null, v, false);
|
|
718
740
|
}
|
|
719
741
|
const s = document.createElement('span');
|
|
720
|
-
s.textContent =
|
|
742
|
+
s.textContent = _safeStr(v);
|
|
721
743
|
return s;
|
|
722
744
|
}
|
|
723
745
|
|
|
@@ -912,16 +934,31 @@ function renderConsole() {
|
|
|
912
934
|
|
|
913
935
|
const { levelFilters, searchFilter } = state.console;
|
|
914
936
|
const visible = state.console.logs.filter(l => {
|
|
915
|
-
|
|
937
|
+
// Redux logs use showRedux flag; regular logs use levelFilters
|
|
938
|
+
if (l.level === 'redux') {
|
|
939
|
+
if (!state.console.showRedux) return false;
|
|
940
|
+
} else if (levelFilters && !levelFilters[l.level]) return false;
|
|
916
941
|
if (searchFilter && !l.message?.toLowerCase().includes(searchFilter)) return false;
|
|
917
942
|
return true;
|
|
918
943
|
});
|
|
919
944
|
|
|
920
945
|
list.querySelectorAll('.log-row').forEach(e => e.remove());
|
|
921
|
-
|
|
946
|
+
if (visible.length > 0) {
|
|
947
|
+
empty.style.display = 'none';
|
|
948
|
+
} else if (state.console.logs.length > 0) {
|
|
949
|
+
// Logs exist but are all filtered out
|
|
950
|
+
empty.querySelector('.label').textContent = 'No matching logs';
|
|
951
|
+
empty.querySelector('.hint').textContent = 'Adjust level filters or clear search to see logs';
|
|
952
|
+
empty.style.display = 'flex';
|
|
953
|
+
} else {
|
|
954
|
+
// No logs at all
|
|
955
|
+
empty.querySelector('.label').textContent = 'No logs yet';
|
|
956
|
+
empty.querySelector('.hint').textContent = 'Logs will appear here automatically';
|
|
957
|
+
empty.style.display = 'flex';
|
|
958
|
+
}
|
|
922
959
|
|
|
923
|
-
// Render only the last
|
|
924
|
-
const MAX_RENDER =
|
|
960
|
+
// Render only the last N visible rows for performance
|
|
961
|
+
const MAX_RENDER = 5000;
|
|
925
962
|
const toRender = visible.length > MAX_RENDER ? visible.slice(-MAX_RENDER) : visible;
|
|
926
963
|
if (visible.length > MAX_RENDER) {
|
|
927
964
|
const info = document.createElement('div');
|
|
@@ -1919,6 +1956,130 @@ function _deepEqual(a, b) {
|
|
|
1919
1956
|
} catch { return false; }
|
|
1920
1957
|
}
|
|
1921
1958
|
|
|
1959
|
+
// Find leaf-level changes between two values (for Redux store diff)
|
|
1960
|
+
function _findLeafChanges(oldVal, newVal, basePath, maxDepth) {
|
|
1961
|
+
const changes = [];
|
|
1962
|
+
if (maxDepth === undefined) maxDepth = 5;
|
|
1963
|
+
|
|
1964
|
+
function walk(a, b, path, depth) {
|
|
1965
|
+
if (depth > maxDepth) {
|
|
1966
|
+
if (!_deepEqual(a, b)) changes.push({ path, oldVal: a, newVal: b });
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (a === b) return;
|
|
1970
|
+
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object' || Array.isArray(a) !== Array.isArray(b)) {
|
|
1971
|
+
changes.push({ path, oldVal: a, newVal: b });
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
1975
|
+
allKeys.forEach(k => {
|
|
1976
|
+
if (!_deepEqual(a[k], b[k])) {
|
|
1977
|
+
const childPath = path ? `${path}.${k}` : k;
|
|
1978
|
+
if (a[k] != null && b[k] != null && typeof a[k] === 'object' && typeof b[k] === 'object' && !Array.isArray(a[k])) {
|
|
1979
|
+
walk(a[k], b[k], childPath, depth + 1);
|
|
1980
|
+
} else {
|
|
1981
|
+
changes.push({ path: childPath, oldVal: a[k], newVal: b[k] });
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
walk(oldVal, newVal, '', 0);
|
|
1988
|
+
return changes;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// Create a tree node with changed paths highlighted in a different color
|
|
1992
|
+
function _createHighlightedTree(key, val, changedPaths, currentPath, isOld) {
|
|
1993
|
+
const isArray = Array.isArray(val);
|
|
1994
|
+
const isObj = val !== null && typeof val === 'object';
|
|
1995
|
+
const myPath = key !== null ? (currentPath ? `${currentPath}.${key}` : String(key)) : currentPath;
|
|
1996
|
+
const isChanged = changedPaths.has(myPath);
|
|
1997
|
+
|
|
1998
|
+
if (!isObj) {
|
|
1999
|
+
// Leaf value
|
|
2000
|
+
const row = document.createElement('div');
|
|
2001
|
+
row.className = 'ov-leaf' + (isChanged ? ' rdx-highlight' : '');
|
|
2002
|
+
if (isChanged) row.style.cssText = isOld
|
|
2003
|
+
? 'background:rgba(255,94,114,.12);border-radius:3px;padding:1px 4px;'
|
|
2004
|
+
: 'background:rgba(61,214,140,.12);border-radius:3px;padding:1px 4px;';
|
|
2005
|
+
if (key !== null) {
|
|
2006
|
+
const k = document.createElement('span');
|
|
2007
|
+
k.className = 'ov-key';
|
|
2008
|
+
k.style.color = isChanged ? (isOld ? 'var(--red)' : 'var(--green)') : '';
|
|
2009
|
+
k.textContent = `${key}: `;
|
|
2010
|
+
row.appendChild(k);
|
|
2011
|
+
}
|
|
2012
|
+
const v = document.createElement('span');
|
|
2013
|
+
v.className = 'ov-prim';
|
|
2014
|
+
if (isChanged) v.style.fontWeight = '700';
|
|
2015
|
+
if (val === null) { v.textContent = 'null'; v.style.color = isChanged ? (isOld ? 'var(--red)' : 'var(--green)') : 'var(--text-dim)'; }
|
|
2016
|
+
else if (typeof val === 'string') { v.textContent = `"${val}"`; v.style.color = isChanged ? (isOld ? 'var(--red)' : 'var(--green)') : 'var(--green)'; }
|
|
2017
|
+
else if (typeof val === 'number') { v.textContent = String(val); v.style.color = isChanged ? (isOld ? 'var(--red)' : 'var(--green)') : 'var(--accent2)'; }
|
|
2018
|
+
else if (typeof val === 'boolean') { v.textContent = String(val); v.style.color = isChanged ? (isOld ? 'var(--red)' : 'var(--green)') : 'var(--accent2)'; }
|
|
2019
|
+
else { v.textContent = _safeStr(val); }
|
|
2020
|
+
row.appendChild(v);
|
|
2021
|
+
return row;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Object/Array — check if any descendants changed
|
|
2025
|
+
const hasChangedDescendant = [...changedPaths].some(p => p === myPath || p.startsWith(myPath ? myPath + '.' : ''));
|
|
2026
|
+
const container = document.createElement('div');
|
|
2027
|
+
container.className = 'ov-node';
|
|
2028
|
+
|
|
2029
|
+
const header = document.createElement('div');
|
|
2030
|
+
header.className = 'ov-header';
|
|
2031
|
+
|
|
2032
|
+
const arrow = document.createElement('span');
|
|
2033
|
+
arrow.className = 'ov-arrow';
|
|
2034
|
+
arrow.textContent = '\u25B6';
|
|
2035
|
+
header.appendChild(arrow);
|
|
2036
|
+
|
|
2037
|
+
if (key !== null) {
|
|
2038
|
+
const k = document.createElement('span');
|
|
2039
|
+
k.className = 'ov-key';
|
|
2040
|
+
if (hasChangedDescendant) k.style.color = isOld ? 'var(--red)' : 'var(--green)';
|
|
2041
|
+
k.textContent = `${key}: `;
|
|
2042
|
+
header.appendChild(k);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
const preview = document.createElement('span');
|
|
2046
|
+
preview.className = 'ov-preview';
|
|
2047
|
+
preview.textContent = isArray ? `Array(${val.length})` : `{${Object.keys(val).length} keys}`;
|
|
2048
|
+
header.appendChild(preview);
|
|
2049
|
+
|
|
2050
|
+
container.appendChild(header);
|
|
2051
|
+
|
|
2052
|
+
const children = document.createElement('div');
|
|
2053
|
+
children.className = 'ov-children';
|
|
2054
|
+
// Auto-expand if this node has changed descendants, otherwise collapse
|
|
2055
|
+
children.style.display = hasChangedDescendant ? 'block' : 'none';
|
|
2056
|
+
if (hasChangedDescendant) { arrow.textContent = '\u25BC'; arrow.classList.add('open'); }
|
|
2057
|
+
|
|
2058
|
+
let populated = false;
|
|
2059
|
+
function populate() {
|
|
2060
|
+
if (populated) return;
|
|
2061
|
+
populated = true;
|
|
2062
|
+
const entries = isArray ? val.map((v, i) => [i, v]) : Object.entries(val);
|
|
2063
|
+
entries.forEach(([k, v]) => {
|
|
2064
|
+
children.appendChild(_createHighlightedTree(k, v, changedPaths, myPath, isOld));
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Populate immediately if expanded, otherwise lazy
|
|
2069
|
+
if (hasChangedDescendant) populate();
|
|
2070
|
+
|
|
2071
|
+
header.addEventListener('click', (e) => {
|
|
2072
|
+
e.stopPropagation();
|
|
2073
|
+
const open = children.style.display !== 'none';
|
|
2074
|
+
children.style.display = open ? 'none' : 'block';
|
|
2075
|
+
arrow.textContent = open ? '\u25B6' : '\u25BC';
|
|
2076
|
+
if (!open) populate();
|
|
2077
|
+
});
|
|
2078
|
+
|
|
2079
|
+
container.appendChild(children);
|
|
2080
|
+
return container;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1922
2083
|
function handleReduxEvent(event) {
|
|
1923
2084
|
if (event.type !== 'redux') return;
|
|
1924
2085
|
const { action, nextState } = event;
|
|
@@ -1934,12 +2095,12 @@ function handleReduxEvent(event) {
|
|
|
1934
2095
|
const actionEntry = { type: action?.type || '?', payload: action, ts: event.ts, index: idx, changedKeys };
|
|
1935
2096
|
state.redux.actions.push(actionEntry);
|
|
1936
2097
|
state.redux.states.push(nextState);
|
|
1937
|
-
|
|
2098
|
+
// Don't auto-select — keep all collapsed until user clicks
|
|
1938
2099
|
$('rBadge').textContent = state.redux.actions.length;
|
|
1939
2100
|
renderRedux();
|
|
1940
2101
|
|
|
1941
|
-
//
|
|
1942
|
-
|
|
2102
|
+
// Always add Redux actions to console logs — visibility controlled by showRedux filter
|
|
2103
|
+
{
|
|
1943
2104
|
const msg = `[Redux] ${actionEntry.type}` + (changedKeys.length ? ` (changed: ${changedKeys.join(', ')})` : '');
|
|
1944
2105
|
addConsoleLog({
|
|
1945
2106
|
level: 'redux',
|
|
@@ -1951,6 +2112,29 @@ function handleReduxEvent(event) {
|
|
|
1951
2112
|
}
|
|
1952
2113
|
}
|
|
1953
2114
|
|
|
2115
|
+
// Assign a consistent color to each Redux action category (e.g. ANALYTICS, CART, USER)
|
|
2116
|
+
const _reduxCatColors = {};
|
|
2117
|
+
const _reduxColorPalette = [
|
|
2118
|
+
'var(--accent)', // blue
|
|
2119
|
+
'var(--green)', // green
|
|
2120
|
+
'var(--orange)', // orange
|
|
2121
|
+
'var(--accent2)', // purple
|
|
2122
|
+
'#e06c75', // coral
|
|
2123
|
+
'#56b6c2', // teal
|
|
2124
|
+
'#c678dd', // magenta
|
|
2125
|
+
'#d19a66', // gold
|
|
2126
|
+
'#98c379', // lime
|
|
2127
|
+
'#e5c07b', // yellow
|
|
2128
|
+
];
|
|
2129
|
+
let _reduxColorIdx = 0;
|
|
2130
|
+
function _reduxCategoryColor(category) {
|
|
2131
|
+
if (!_reduxCatColors[category]) {
|
|
2132
|
+
_reduxCatColors[category] = _reduxColorPalette[_reduxColorIdx % _reduxColorPalette.length];
|
|
2133
|
+
_reduxColorIdx++;
|
|
2134
|
+
}
|
|
2135
|
+
return _reduxCatColors[category];
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1954
2138
|
function renderRedux() {
|
|
1955
2139
|
const content = $('reduxContent');
|
|
1956
2140
|
const empty = $('reduxEmpty');
|
|
@@ -1965,28 +2149,49 @@ function renderRedux() {
|
|
|
1965
2149
|
if (!visible.length) return;
|
|
1966
2150
|
|
|
1967
2151
|
const ttLabel = $('ttLabel');
|
|
1968
|
-
if (ttLabel) ttLabel.textContent = `${selected + 1}/${actions.length}`;
|
|
2152
|
+
if (ttLabel) ttLabel.textContent = selected >= 0 ? `${selected + 1}/${actions.length}` : `—/${actions.length}`;
|
|
1969
2153
|
|
|
1970
2154
|
const frag = document.createDocumentFragment();
|
|
1971
2155
|
visible.forEach(a => {
|
|
1972
2156
|
const isSelected = a.index === selected;
|
|
1973
|
-
const isPrev = a.index === selected - 1;
|
|
1974
|
-
const isNext = a.index === selected + 1;
|
|
1975
2157
|
|
|
1976
2158
|
const entry = document.createElement('div');
|
|
1977
|
-
entry.className = 'rdx-entry' + (isSelected ? ' selected' : '')
|
|
2159
|
+
entry.className = 'rdx-entry' + (isSelected ? ' selected' : '');
|
|
1978
2160
|
|
|
1979
2161
|
// Row header — always visible
|
|
1980
2162
|
const header = document.createElement('div');
|
|
1981
2163
|
header.className = 'rdx-entry-header';
|
|
1982
|
-
const changesBadge = a.changedKeys?.length ? `<span class="rdx-changes">${a.changedKeys.length}</span>` : '';
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2164
|
+
const changesBadge = a.changedKeys?.length ? `<span class="rdx-changes">${a.changedKeys.length} changed</span>` : '';
|
|
2165
|
+
// Color-code action type by category prefix (e.g. ANALYTICS/, CART/, USER/)
|
|
2166
|
+
const typeParts = a.type.split('/');
|
|
2167
|
+
let typeHtml;
|
|
2168
|
+
if (typeParts.length >= 2) {
|
|
2169
|
+
const catColor = _reduxCategoryColor(typeParts[0]);
|
|
2170
|
+
typeHtml = `<span class="rdx-type-cat" style="color:${catColor}">${esc(typeParts[0])}/</span><span class="rdx-type-name">${esc(typeParts.slice(1).join('/'))}</span>`;
|
|
2171
|
+
} else {
|
|
2172
|
+
typeHtml = `<span class="rdx-type">${esc(a.type)}</span>`;
|
|
2173
|
+
}
|
|
2174
|
+
header.innerHTML = `<span class="rdx-index">#${a.index}</span>${typeHtml}${changesBadge}<span class="rdx-time">${ts(a.ts)}</span>`;
|
|
2175
|
+
// Toggle: click to expand, click again to collapse
|
|
2176
|
+
header.addEventListener('click', () => {
|
|
2177
|
+
state.redux.selected = isSelected ? -1 : a.index;
|
|
2178
|
+
renderRedux();
|
|
2179
|
+
});
|
|
2180
|
+
// Right-click to copy action type
|
|
2181
|
+
header.addEventListener('contextmenu', (e) => {
|
|
2182
|
+
e.preventDefault();
|
|
2183
|
+
e.stopPropagation();
|
|
2184
|
+
showContextMenu(e, [
|
|
2185
|
+
{ label: 'Copy Action Type', action: () => navigator.clipboard.writeText(a.type) },
|
|
2186
|
+
{ label: 'Copy Action Payload', action: () => navigator.clipboard.writeText(JSON.stringify(a.payload, null, 2)) },
|
|
2187
|
+
]);
|
|
2188
|
+
});
|
|
2189
|
+
// Allow text selection on the action type
|
|
2190
|
+
header.style.userSelect = 'text';
|
|
1986
2191
|
entry.appendChild(header);
|
|
1987
2192
|
|
|
1988
|
-
// Expanded detail
|
|
1989
|
-
if (isSelected
|
|
2193
|
+
// Expanded detail — only for explicitly selected action
|
|
2194
|
+
if (isSelected) {
|
|
1990
2195
|
const detail = document.createElement('div');
|
|
1991
2196
|
detail.className = 'rdx-entry-detail';
|
|
1992
2197
|
|
|
@@ -2003,46 +2208,58 @@ function renderRedux() {
|
|
|
2003
2208
|
if (a.payload) {
|
|
2004
2209
|
const pLabel = document.createElement('div');
|
|
2005
2210
|
pLabel.className = 'redux-section-title';
|
|
2006
|
-
pLabel.textContent = 'Payload';
|
|
2211
|
+
pLabel.textContent = 'Action Payload';
|
|
2007
2212
|
detail.appendChild(pLabel);
|
|
2008
|
-
detail.appendChild(createTreeNode(null, a.payload,
|
|
2213
|
+
detail.appendChild(createTreeNode(null, a.payload, false));
|
|
2009
2214
|
}
|
|
2010
2215
|
|
|
2011
|
-
// Store changes
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2216
|
+
// Store changes — show full Previous and Current state for each changed key
|
|
2217
|
+
// with changed sub-keys highlighted
|
|
2218
|
+
const prevS = a.index > 0 ? states[a.index - 1] : null;
|
|
2219
|
+
const currS = states[a.index];
|
|
2220
|
+
if (currS && typeof currS === 'object' && a.changedKeys?.length > 0) {
|
|
2221
|
+
a.changedKeys.forEach(key => {
|
|
2222
|
+
const keyWrap = document.createElement('div');
|
|
2223
|
+
keyWrap.className = 'rdx-store-diff';
|
|
2224
|
+
|
|
2225
|
+
const kLabel = document.createElement('div');
|
|
2226
|
+
kLabel.className = 'rdx-store-key-label';
|
|
2227
|
+
kLabel.textContent = key;
|
|
2228
|
+
keyWrap.appendChild(kLabel);
|
|
2229
|
+
|
|
2230
|
+
const oldVal = prevS ? prevS[key] : undefined;
|
|
2231
|
+
const newVal = currS[key];
|
|
2232
|
+
|
|
2233
|
+
// Find which sub-keys changed (for highlighting)
|
|
2234
|
+
const changedPaths = new Set();
|
|
2235
|
+
_findLeafChanges(oldVal, newVal, '').forEach(c => changedPaths.add(c.path));
|
|
2236
|
+
|
|
2237
|
+
// Previous state
|
|
2238
|
+
if (oldVal !== undefined) {
|
|
2239
|
+
const prevLabel = document.createElement('div');
|
|
2240
|
+
prevLabel.className = 'rdx-state-label prev';
|
|
2241
|
+
prevLabel.textContent = '- Previous';
|
|
2242
|
+
keyWrap.appendChild(prevLabel);
|
|
2243
|
+
const prevTree = document.createElement('div');
|
|
2244
|
+
prevTree.className = 'rdx-state-tree prev';
|
|
2245
|
+
prevTree.appendChild(_createHighlightedTree(null, oldVal, changedPaths, '', true));
|
|
2246
|
+
keyWrap.appendChild(prevTree);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// Current state
|
|
2250
|
+
if (newVal !== undefined) {
|
|
2251
|
+
const currLabel = document.createElement('div');
|
|
2252
|
+
currLabel.className = 'rdx-state-label curr';
|
|
2253
|
+
currLabel.textContent = '+ Current';
|
|
2254
|
+
keyWrap.appendChild(currLabel);
|
|
2255
|
+
const currTree = document.createElement('div');
|
|
2256
|
+
currTree.className = 'rdx-state-tree curr';
|
|
2257
|
+
currTree.appendChild(_createHighlightedTree(null, newVal, changedPaths, '', false));
|
|
2258
|
+
keyWrap.appendChild(currTree);
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
detail.appendChild(keyWrap);
|
|
2262
|
+
});
|
|
2046
2263
|
}
|
|
2047
2264
|
|
|
2048
2265
|
entry.appendChild(detail);
|
|
@@ -2052,12 +2269,10 @@ function renderRedux() {
|
|
|
2052
2269
|
});
|
|
2053
2270
|
|
|
2054
2271
|
content.appendChild(frag);
|
|
2055
|
-
//
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const selEl = content.querySelector('.rdx-entry.selected');
|
|
2060
|
-
if (selEl) selEl.scrollIntoView({ block: 'nearest' });
|
|
2272
|
+
// Scroll selected entry into view
|
|
2273
|
+
const selEl = content.querySelector('.rdx-entry.selected');
|
|
2274
|
+
if (selEl) {
|
|
2275
|
+
selEl.scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
2061
2276
|
}
|
|
2062
2277
|
}
|
|
2063
2278
|
|
package/bin/setup.js
CHANGED
|
@@ -116,8 +116,14 @@ function findEntryFile(projectDir) {
|
|
|
116
116
|
|
|
117
117
|
// ─── Find Redux store file ───────────────────────────────────────────────────
|
|
118
118
|
function findStoreFile(projectDir) {
|
|
119
|
-
const searchDirs = [
|
|
120
|
-
|
|
119
|
+
const searchDirs = [
|
|
120
|
+
'src', 'app', 'store', 'redux', 'state',
|
|
121
|
+
'src/store', 'src/redux', 'src/state', 'src/app', 'src/app/store',
|
|
122
|
+
'app/store', 'app/redux', 'app/state',
|
|
123
|
+
'src/features', 'src/lib', 'src/config', 'src/core',
|
|
124
|
+
'src/services', 'src/utils',
|
|
125
|
+
];
|
|
126
|
+
const storeNames = ['store.ts', 'store.js', 'store.tsx', 'store.jsx', 'index.ts', 'index.js', 'index.tsx'];
|
|
121
127
|
|
|
122
128
|
for (const dir of searchDirs) {
|
|
123
129
|
for (const name of storeNames) {
|
|
@@ -130,7 +136,31 @@ function findStoreFile(projectDir) {
|
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
|
-
|
|
139
|
+
|
|
140
|
+
// Deep search: recursively find any file with configureStore/createStore
|
|
141
|
+
try {
|
|
142
|
+
const glob = (dir, depth) => {
|
|
143
|
+
if (depth > 4) return null;
|
|
144
|
+
let entries;
|
|
145
|
+
try { entries = fs.readdirSync(path.join(projectDir, dir), { withFileTypes: true }); } catch { return null; }
|
|
146
|
+
for (const e of entries) {
|
|
147
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
148
|
+
const rel = path.join(dir, e.name);
|
|
149
|
+
if (e.isFile() && /\.(ts|js|tsx|jsx)$/.test(e.name) && /store/i.test(e.name)) {
|
|
150
|
+
const content = fs.readFileSync(path.join(projectDir, rel), 'utf8');
|
|
151
|
+
if (content.includes('configureStore') || content.includes('createStore')) {
|
|
152
|
+
return rel;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (e.isDirectory()) {
|
|
156
|
+
const found = glob(rel, depth + 1);
|
|
157
|
+
if (found) return found;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
return glob('src', 0) || glob('app', 0) || glob('.', 1);
|
|
163
|
+
} catch { return null; }
|
|
134
164
|
}
|
|
135
165
|
|
|
136
166
|
// ─── Install ─────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.9",
|
|
5
5
|
"description": "macOS debugger for React Native — Console, Sources, Network, Performance, Memory, Redux, AsyncStorage, React tree. Supports RN 0.74+ with Hermes and New Architecture.",
|
|
6
6
|
"main": "main.js",
|
|
7
7
|
"bin": {
|
package/sdk/RNDebugSDK.js
CHANGED
|
@@ -50,16 +50,17 @@ let _debuggerDetected = false;
|
|
|
50
50
|
let _debuggerCheckInterval = null;
|
|
51
51
|
|
|
52
52
|
function _checkDebuggerAttached() {
|
|
53
|
-
// Method 1: Check if Hermes debugger globals are set
|
|
53
|
+
// Method 1: Check if Hermes debugger globals are set (only when actively connected)
|
|
54
54
|
const hermesDebugger = !!(global.__DEBUGGER_CONNECTED__ || global.__HERMES_DEBUGGER_CONNECTED__);
|
|
55
|
-
// Method 2: Check React DevTools hook for debugger
|
|
55
|
+
// Method 2: Check React DevTools hook for active debugger session
|
|
56
56
|
const rdtHook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
57
57
|
const rdtDebugger = !!(rdtHook && rdtHook._debuggerAttached);
|
|
58
|
-
//
|
|
59
|
-
|
|
58
|
+
// Note: global.__inspector and global.__inspectorGlobalObject are always present
|
|
59
|
+
// on Hermes as part of the built-in inspector infrastructure. They do NOT indicate
|
|
60
|
+
// that a debugger is actively connected. Only check explicit connection flags.
|
|
60
61
|
|
|
61
62
|
const wasDetected = _debuggerDetected;
|
|
62
|
-
_debuggerDetected = hermesDebugger || rdtDebugger
|
|
63
|
+
_debuggerDetected = hermesDebugger || rdtDebugger;
|
|
63
64
|
|
|
64
65
|
if (_debuggerDetected && !wasDetected) {
|
|
65
66
|
_console.log('[RNDebugSDK] Debugger detected — SDK interception paused to avoid inspector conflicts. Use the ReactoRadar app to resume.');
|
package/styles.css
CHANGED
|
@@ -963,6 +963,18 @@ body {
|
|
|
963
963
|
.rdx-diff-row { display: flex; align-items: flex-start; gap: 6px; padding: 2px 6px; border-radius: 3px; margin: 2px 0; }
|
|
964
964
|
.rdx-diff-row.removed { background: rgba(255,94,114,.06); }
|
|
965
965
|
.rdx-diff-row.added { background: rgba(61,214,140,.06); }
|
|
966
|
+
.rdx-diff-leaf { display: flex; align-items: baseline; gap: 6px; padding: 3px 8px; margin: 2px 0; font-size: 11px; border-radius: 3px; background: var(--bg3); flex-wrap: wrap; }
|
|
967
|
+
.rdx-diff-path { color: var(--accent); font-weight: 600; font-size: 10px; min-width: 60px; }
|
|
968
|
+
.rdx-diff-old { color: var(--red); text-decoration: line-through; opacity: 0.8; word-break: break-all; }
|
|
969
|
+
.rdx-diff-arrow { color: var(--text-dim); font-size: 10px; flex-shrink: 0; }
|
|
970
|
+
.rdx-diff-new { color: var(--green); font-weight: 600; word-break: break-all; }
|
|
971
|
+
.rdx-state-label { font-size: 10px; font-weight: 700; padding: 4px 8px 2px; margin-top: 4px; letter-spacing: 0.5px; }
|
|
972
|
+
.rdx-state-label.prev { color: var(--red); }
|
|
973
|
+
.rdx-state-label.curr { color: var(--green); }
|
|
974
|
+
.rdx-state-tree { padding: 2px 0 4px 4px; border-left: 2px solid var(--border); margin-left: 4px; }
|
|
975
|
+
.rdx-state-tree.prev { border-left-color: rgba(255,94,114,.3); }
|
|
976
|
+
.rdx-state-tree.curr { border-left-color: rgba(61,214,140,.3); }
|
|
977
|
+
.rdx-highlight { font-weight: 600; }
|
|
966
978
|
.rdx-diff-sign { font-weight: 700; font-size: 11px; flex-shrink: 0; width: 14px; text-align: center; }
|
|
967
979
|
.rdx-diff-row.removed .rdx-diff-sign { color: var(--red); }
|
|
968
980
|
.rdx-diff-row.added .rdx-diff-sign { color: var(--green); }
|