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 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">Add RNDebugSDK.js to your app</div>
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
- if (levelFilters && !levelFilters[l.level]) return;
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.style.display = 'none';
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 500
537
+ // Keep DOM size manageable — remove oldest rows if over limit
532
538
  const rows = list.querySelectorAll('.log-row');
533
- const MAX_DOM_ROWS = 500;
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
- if (levelFilters && !levelFilters[l.level]) return false;
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
- empty.style.display = visible.length ? 'none' : 'flex';
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 500 visible rows for performance
924
- const MAX_RENDER = 500;
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
- state.redux.selected = idx;
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
- // Also add to console logs if Redux is enabled in console dropdown
1942
- if (state.console.showRedux) {
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' : '') + (isPrev ? ' is-prev' : '') + (isNext ? ' is-next' : '');
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
- const roleTag = isPrev ? '<span class="rdx-role prev">PREV</span>' : isNext ? '<span class="rdx-role next">NEXT</span>' : isSelected ? '<span class="rdx-role current">CURRENT</span>' : '';
1984
- header.innerHTML = `<span class="rdx-index">#${a.index}</span>${roleTag}<span class="rdx-type">${esc(a.type)}</span>${changesBadge}<span class="rdx-time">${ts(a.ts)}</span>`;
1985
- header.addEventListener('click', () => { state.redux.selected = a.index; renderRedux(); });
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 for selected / prev / next
1989
- if (isSelected || isPrev || isNext) {
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, !isSelected));
2062
+ detail.appendChild(createTreeNode(null, a.payload, false));
2009
2063
  }
2010
2064
 
2011
- // Store changes (only for selected)
2012
- if (isSelected) {
2013
- const prevS = a.index > 0 ? states[a.index - 1] : null;
2014
- const currS = states[a.index];
2015
- if (currS && typeof currS === 'object' && a.changedKeys?.length > 0) {
2016
- const sLabel = document.createElement('div');
2017
- sLabel.className = 'redux-section-title';
2018
- sLabel.textContent = 'Store Changes';
2019
- detail.appendChild(sLabel);
2020
-
2021
- a.changedKeys.forEach(key => {
2022
- const keyWrap = document.createElement('div');
2023
- keyWrap.className = 'rdx-store-diff';
2024
- const kLabel = document.createElement('div');
2025
- kLabel.className = 'rdx-store-key-label';
2026
- kLabel.textContent = key;
2027
- keyWrap.appendChild(kLabel);
2028
-
2029
- if (prevS && prevS[key] !== undefined) {
2030
- const prevRow = document.createElement('div');
2031
- prevRow.className = 'rdx-diff-row removed';
2032
- prevRow.innerHTML = '<span class="rdx-diff-sign">-</span>';
2033
- prevRow.appendChild(createTreeNode(null, prevS[key], true));
2034
- keyWrap.appendChild(prevRow);
2035
- }
2036
- if (currS[key] !== undefined) {
2037
- const newRow = document.createElement('div');
2038
- newRow.className = 'rdx-diff-row added';
2039
- newRow.innerHTML = '<span class="rdx-diff-sign">+</span>';
2040
- newRow.appendChild(createTreeNode(null, currS[key], true));
2041
- keyWrap.appendChild(newRow);
2042
- }
2043
- detail.appendChild(keyWrap);
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
- // Auto-scroll: if asc (latest at bottom), scroll to bottom; otherwise scroll selected into view
2056
- if (state.redux.sortDir === 'asc') {
2057
- content.scrollTop = content.scrollHeight;
2058
- } else {
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 = ['src', 'app', 'store', 'redux', 'src/store', 'src/redux', 'app/store', 'app/redux', 'src/app/store'];
120
- const storeNames = ['store.ts', 'store.js', 'store.tsx', 'index.ts', 'index.js'];
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
- return null;
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.7",
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 attachment
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
- // Method 3: Check if CDP is connected via the inspector agent
59
- const inspectorConnected = !!(global.__inspectorGlobalObject || global.__inspector);
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 || inspectorConnected;
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); }