reactoradar 1.5.7 → 1.5.8
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 +128 -66
- package/bin/setup.js +33 -3
- package/package.json +1 -1
- package/sdk/RNDebugSDK.js +6 -5
- package/styles.css +5 -0
package/app.js
CHANGED
|
@@ -373,7 +373,7 @@ function initConsolePanel() {
|
|
|
373
373
|
<div class="empty-state" id="consoleEmpty">
|
|
374
374
|
<div class="icon">⬛</div>
|
|
375
375
|
<div class="label">No logs yet</div>
|
|
376
|
-
<div class="hint">
|
|
376
|
+
<div class="hint">Logs will appear here automatically</div>
|
|
377
377
|
</div>
|
|
378
378
|
</div>
|
|
379
379
|
<div class="console-find-bar" id="consoleFindBar" style="display:none">
|
|
@@ -519,23 +519,29 @@ function flushConsoleBatch() {
|
|
|
519
519
|
let added = 0;
|
|
520
520
|
|
|
521
521
|
batch.forEach(l => {
|
|
522
|
-
|
|
522
|
+
// Redux logs use showRedux flag; regular logs use levelFilters
|
|
523
|
+
if (l.level === 'redux') {
|
|
524
|
+
if (!state.console.showRedux) return;
|
|
525
|
+
} else if (levelFilters && !levelFilters[l.level]) return;
|
|
523
526
|
if (searchFilter && !l.message?.toLowerCase().includes(searchFilter)) return;
|
|
524
527
|
frag.appendChild(buildLogRow(l));
|
|
525
528
|
added++;
|
|
526
529
|
});
|
|
527
530
|
|
|
528
531
|
if (added > 0) {
|
|
529
|
-
empty
|
|
532
|
+
// Hide empty state as soon as we have visible rows
|
|
533
|
+
if (empty) empty.style.display = 'none';
|
|
534
|
+
// Auto-scroll only if user is already near the bottom (within 150px)
|
|
535
|
+
const wasAtBottom = (list.scrollHeight - list.scrollTop - list.clientHeight) < 150;
|
|
530
536
|
list.appendChild(frag);
|
|
531
|
-
// Keep DOM size manageable — remove oldest rows if over
|
|
537
|
+
// Keep DOM size manageable — remove oldest rows if over limit
|
|
532
538
|
const rows = list.querySelectorAll('.log-row');
|
|
533
|
-
const MAX_DOM_ROWS =
|
|
539
|
+
const MAX_DOM_ROWS = 5000;
|
|
534
540
|
if (rows.length > MAX_DOM_ROWS) {
|
|
535
541
|
const toRemove = rows.length - MAX_DOM_ROWS;
|
|
536
542
|
for (let i = 0; i < toRemove; i++) rows[i].remove();
|
|
537
543
|
}
|
|
538
|
-
list.scrollTop = list.scrollHeight;
|
|
544
|
+
if (wasAtBottom) list.scrollTop = list.scrollHeight;
|
|
539
545
|
}
|
|
540
546
|
}
|
|
541
547
|
|
|
@@ -912,16 +918,31 @@ function renderConsole() {
|
|
|
912
918
|
|
|
913
919
|
const { levelFilters, searchFilter } = state.console;
|
|
914
920
|
const visible = state.console.logs.filter(l => {
|
|
915
|
-
|
|
921
|
+
// Redux logs use showRedux flag; regular logs use levelFilters
|
|
922
|
+
if (l.level === 'redux') {
|
|
923
|
+
if (!state.console.showRedux) return false;
|
|
924
|
+
} else if (levelFilters && !levelFilters[l.level]) return false;
|
|
916
925
|
if (searchFilter && !l.message?.toLowerCase().includes(searchFilter)) return false;
|
|
917
926
|
return true;
|
|
918
927
|
});
|
|
919
928
|
|
|
920
929
|
list.querySelectorAll('.log-row').forEach(e => e.remove());
|
|
921
|
-
|
|
930
|
+
if (visible.length > 0) {
|
|
931
|
+
empty.style.display = 'none';
|
|
932
|
+
} else if (state.console.logs.length > 0) {
|
|
933
|
+
// Logs exist but are all filtered out
|
|
934
|
+
empty.querySelector('.label').textContent = 'No matching logs';
|
|
935
|
+
empty.querySelector('.hint').textContent = 'Adjust level filters or clear search to see logs';
|
|
936
|
+
empty.style.display = 'flex';
|
|
937
|
+
} else {
|
|
938
|
+
// No logs at all
|
|
939
|
+
empty.querySelector('.label').textContent = 'No logs yet';
|
|
940
|
+
empty.querySelector('.hint').textContent = 'Logs will appear here automatically';
|
|
941
|
+
empty.style.display = 'flex';
|
|
942
|
+
}
|
|
922
943
|
|
|
923
|
-
// Render only the last
|
|
924
|
-
const MAX_RENDER =
|
|
944
|
+
// Render only the last N visible rows for performance
|
|
945
|
+
const MAX_RENDER = 5000;
|
|
925
946
|
const toRender = visible.length > MAX_RENDER ? visible.slice(-MAX_RENDER) : visible;
|
|
926
947
|
if (visible.length > MAX_RENDER) {
|
|
927
948
|
const info = document.createElement('div');
|
|
@@ -1919,6 +1940,38 @@ function _deepEqual(a, b) {
|
|
|
1919
1940
|
} catch { return false; }
|
|
1920
1941
|
}
|
|
1921
1942
|
|
|
1943
|
+
// Find leaf-level changes between two values (for Redux store diff)
|
|
1944
|
+
function _findLeafChanges(oldVal, newVal, basePath, maxDepth) {
|
|
1945
|
+
const changes = [];
|
|
1946
|
+
if (maxDepth === undefined) maxDepth = 5;
|
|
1947
|
+
|
|
1948
|
+
function walk(a, b, path, depth) {
|
|
1949
|
+
if (depth > maxDepth) {
|
|
1950
|
+
if (!_deepEqual(a, b)) changes.push({ path, oldVal: a, newVal: b });
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (a === b) return;
|
|
1954
|
+
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object' || Array.isArray(a) !== Array.isArray(b)) {
|
|
1955
|
+
changes.push({ path, oldVal: a, newVal: b });
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
1959
|
+
allKeys.forEach(k => {
|
|
1960
|
+
if (!_deepEqual(a[k], b[k])) {
|
|
1961
|
+
const childPath = path ? `${path}.${k}` : k;
|
|
1962
|
+
if (a[k] != null && b[k] != null && typeof a[k] === 'object' && typeof b[k] === 'object' && !Array.isArray(a[k])) {
|
|
1963
|
+
walk(a[k], b[k], childPath, depth + 1);
|
|
1964
|
+
} else {
|
|
1965
|
+
changes.push({ path: childPath, oldVal: a[k], newVal: b[k] });
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
walk(oldVal, newVal, '', 0);
|
|
1972
|
+
return changes;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1922
1975
|
function handleReduxEvent(event) {
|
|
1923
1976
|
if (event.type !== 'redux') return;
|
|
1924
1977
|
const { action, nextState } = event;
|
|
@@ -1934,12 +1987,12 @@ function handleReduxEvent(event) {
|
|
|
1934
1987
|
const actionEntry = { type: action?.type || '?', payload: action, ts: event.ts, index: idx, changedKeys };
|
|
1935
1988
|
state.redux.actions.push(actionEntry);
|
|
1936
1989
|
state.redux.states.push(nextState);
|
|
1937
|
-
|
|
1990
|
+
// Don't auto-select — keep all collapsed until user clicks
|
|
1938
1991
|
$('rBadge').textContent = state.redux.actions.length;
|
|
1939
1992
|
renderRedux();
|
|
1940
1993
|
|
|
1941
|
-
//
|
|
1942
|
-
|
|
1994
|
+
// Always add Redux actions to console logs — visibility controlled by showRedux filter
|
|
1995
|
+
{
|
|
1943
1996
|
const msg = `[Redux] ${actionEntry.type}` + (changedKeys.length ? ` (changed: ${changedKeys.join(', ')})` : '');
|
|
1944
1997
|
addConsoleLog({
|
|
1945
1998
|
level: 'redux',
|
|
@@ -1965,28 +2018,29 @@ function renderRedux() {
|
|
|
1965
2018
|
if (!visible.length) return;
|
|
1966
2019
|
|
|
1967
2020
|
const ttLabel = $('ttLabel');
|
|
1968
|
-
if (ttLabel) ttLabel.textContent = `${selected + 1}/${actions.length}`;
|
|
2021
|
+
if (ttLabel) ttLabel.textContent = selected >= 0 ? `${selected + 1}/${actions.length}` : `—/${actions.length}`;
|
|
1969
2022
|
|
|
1970
2023
|
const frag = document.createDocumentFragment();
|
|
1971
2024
|
visible.forEach(a => {
|
|
1972
2025
|
const isSelected = a.index === selected;
|
|
1973
|
-
const isPrev = a.index === selected - 1;
|
|
1974
|
-
const isNext = a.index === selected + 1;
|
|
1975
2026
|
|
|
1976
2027
|
const entry = document.createElement('div');
|
|
1977
|
-
entry.className = 'rdx-entry' + (isSelected ? ' selected' : '')
|
|
2028
|
+
entry.className = 'rdx-entry' + (isSelected ? ' selected' : '');
|
|
1978
2029
|
|
|
1979
2030
|
// Row header — always visible
|
|
1980
2031
|
const header = document.createElement('div');
|
|
1981
2032
|
header.className = 'rdx-entry-header';
|
|
1982
|
-
const changesBadge = a.changedKeys?.length ? `<span class="rdx-changes">${a.changedKeys.length}</span>` : '';
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
header.addEventListener('click', () => {
|
|
2033
|
+
const changesBadge = a.changedKeys?.length ? `<span class="rdx-changes">${a.changedKeys.length} changed</span>` : '';
|
|
2034
|
+
header.innerHTML = `<span class="rdx-index">#${a.index}</span><span class="rdx-type">${esc(a.type)}</span>${changesBadge}<span class="rdx-time">${ts(a.ts)}</span>`;
|
|
2035
|
+
// Toggle: click to expand, click again to collapse
|
|
2036
|
+
header.addEventListener('click', () => {
|
|
2037
|
+
state.redux.selected = isSelected ? -1 : a.index;
|
|
2038
|
+
renderRedux();
|
|
2039
|
+
});
|
|
1986
2040
|
entry.appendChild(header);
|
|
1987
2041
|
|
|
1988
|
-
// Expanded detail
|
|
1989
|
-
if (isSelected
|
|
2042
|
+
// Expanded detail — only for explicitly selected action
|
|
2043
|
+
if (isSelected) {
|
|
1990
2044
|
const detail = document.createElement('div');
|
|
1991
2045
|
detail.className = 'rdx-entry-detail';
|
|
1992
2046
|
|
|
@@ -2003,46 +2057,56 @@ function renderRedux() {
|
|
|
2003
2057
|
if (a.payload) {
|
|
2004
2058
|
const pLabel = document.createElement('div');
|
|
2005
2059
|
pLabel.className = 'redux-section-title';
|
|
2006
|
-
pLabel.textContent = 'Payload';
|
|
2060
|
+
pLabel.textContent = 'Action Payload';
|
|
2007
2061
|
detail.appendChild(pLabel);
|
|
2008
|
-
detail.appendChild(createTreeNode(null, a.payload,
|
|
2062
|
+
detail.appendChild(createTreeNode(null, a.payload, false));
|
|
2009
2063
|
}
|
|
2010
2064
|
|
|
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
|
-
keyWrap.appendChild(
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2065
|
+
// Store changes — show ONLY the changed leaf values, not entire state
|
|
2066
|
+
const prevS = a.index > 0 ? states[a.index - 1] : null;
|
|
2067
|
+
const currS = states[a.index];
|
|
2068
|
+
if (currS && typeof currS === 'object' && a.changedKeys?.length > 0) {
|
|
2069
|
+
const sLabel = document.createElement('div');
|
|
2070
|
+
sLabel.className = 'redux-section-title';
|
|
2071
|
+
sLabel.textContent = 'Store Changes';
|
|
2072
|
+
detail.appendChild(sLabel);
|
|
2073
|
+
|
|
2074
|
+
a.changedKeys.forEach(key => {
|
|
2075
|
+
const keyWrap = document.createElement('div');
|
|
2076
|
+
keyWrap.className = 'rdx-store-diff';
|
|
2077
|
+
const kLabel = document.createElement('div');
|
|
2078
|
+
kLabel.className = 'rdx-store-key-label';
|
|
2079
|
+
kLabel.textContent = key;
|
|
2080
|
+
keyWrap.appendChild(kLabel);
|
|
2081
|
+
|
|
2082
|
+
const oldVal = prevS ? prevS[key] : undefined;
|
|
2083
|
+
const newVal = currS[key];
|
|
2084
|
+
|
|
2085
|
+
// Deep diff: find only the leaf values that actually changed
|
|
2086
|
+
const leafChanges = _findLeafChanges(oldVal, newVal, key);
|
|
2087
|
+
if (leafChanges.length > 0) {
|
|
2088
|
+
leafChanges.forEach(change => {
|
|
2089
|
+
const row = document.createElement('div');
|
|
2090
|
+
row.className = 'rdx-diff-leaf';
|
|
2091
|
+
row.innerHTML = `<span class="rdx-diff-path">${esc(change.path)}</span>`
|
|
2092
|
+
+ `<span class="rdx-diff-old">${esc(String(change.oldVal ?? 'undefined'))}</span>`
|
|
2093
|
+
+ `<span class="rdx-diff-arrow">→</span>`
|
|
2094
|
+
+ `<span class="rdx-diff-new">${esc(String(change.newVal ?? 'undefined'))}</span>`;
|
|
2095
|
+
keyWrap.appendChild(row);
|
|
2096
|
+
});
|
|
2097
|
+
} else {
|
|
2098
|
+
// Fallback: show old → new for the whole key
|
|
2099
|
+
const row = document.createElement('div');
|
|
2100
|
+
row.className = 'rdx-diff-leaf';
|
|
2101
|
+
const oldStr = oldVal === undefined ? 'undefined' : (typeof oldVal === 'object' ? JSON.stringify(oldVal) : String(oldVal));
|
|
2102
|
+
const newStr = newVal === undefined ? 'undefined' : (typeof newVal === 'object' ? JSON.stringify(newVal) : String(newVal));
|
|
2103
|
+
row.innerHTML = `<span class="rdx-diff-old">${esc(oldStr)}</span>`
|
|
2104
|
+
+ `<span class="rdx-diff-arrow">→</span>`
|
|
2105
|
+
+ `<span class="rdx-diff-new">${esc(newStr)}</span>`;
|
|
2106
|
+
keyWrap.appendChild(row);
|
|
2107
|
+
}
|
|
2108
|
+
detail.appendChild(keyWrap);
|
|
2109
|
+
});
|
|
2046
2110
|
}
|
|
2047
2111
|
|
|
2048
2112
|
entry.appendChild(detail);
|
|
@@ -2052,12 +2116,10 @@ function renderRedux() {
|
|
|
2052
2116
|
});
|
|
2053
2117
|
|
|
2054
2118
|
content.appendChild(frag);
|
|
2055
|
-
//
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const selEl = content.querySelector('.rdx-entry.selected');
|
|
2060
|
-
if (selEl) selEl.scrollIntoView({ block: 'nearest' });
|
|
2119
|
+
// Scroll selected entry into view
|
|
2120
|
+
const selEl = content.querySelector('.rdx-entry.selected');
|
|
2121
|
+
if (selEl) {
|
|
2122
|
+
selEl.scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
2061
2123
|
}
|
|
2062
2124
|
}
|
|
2063
2125
|
|
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.8",
|
|
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,11 @@ 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; }
|
|
966
971
|
.rdx-diff-sign { font-weight: 700; font-size: 11px; flex-shrink: 0; width: 14px; text-align: center; }
|
|
967
972
|
.rdx-diff-row.removed .rdx-diff-sign { color: var(--red); }
|
|
968
973
|
.rdx-diff-row.added .rdx-diff-sign { color: var(--green); }
|