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 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
- window.electronAPI.on('focus-search', () => {
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">Add RNDebugSDK.js to your app</div>
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
- if (levelFilters && !levelFilters[l.level]) return;
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.style.display = 'none';
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 500
545
+ // Keep DOM size manageable — remove oldest rows if over limit
532
546
  const rows = list.querySelectorAll('.log-row');
533
- const MAX_DOM_ROWS = 500;
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 = String(val); }
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 = String(arg);
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 = String(v);
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
- if (levelFilters && !levelFilters[l.level]) return false;
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
- empty.style.display = visible.length ? 'none' : 'flex';
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 500 visible rows for performance
924
- const MAX_RENDER = 500;
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
- state.redux.selected = idx;
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
- // Also add to console logs if Redux is enabled in console dropdown
1942
- if (state.console.showRedux) {
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' : '') + (isPrev ? ' is-prev' : '') + (isNext ? ' is-next' : '');
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
- 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(); });
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 for selected / prev / next
1989
- if (isSelected || isPrev || isNext) {
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, !isSelected));
2213
+ detail.appendChild(createTreeNode(null, a.payload, false));
2009
2214
  }
2010
2215
 
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
- }
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
- // 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' });
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 = ['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.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 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,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); }