reactoradar 1.5.8 → 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.
Files changed (3) hide show
  1. package/app.js +187 -34
  2. package/package.json +1 -1
  3. package/styles.css +7 -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
- 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) => {
@@ -688,6 +696,14 @@ function createTreeNode(key, val, startCollapsed) {
688
696
  return container;
689
697
  }
690
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
+
691
707
  function createPrimitiveSpan(val) {
692
708
  const s = document.createElement('span');
693
709
  if (val === null) { s.className = 'ov-null'; s.textContent = 'null'; }
@@ -695,7 +711,7 @@ function createPrimitiveSpan(val) {
695
711
  else if (typeof val === 'string') { s.className = 'ov-str'; s.textContent = `"${val}"`; }
696
712
  else if (typeof val === 'number') { s.className = 'ov-num'; s.textContent = String(val); }
697
713
  else if (typeof val === 'boolean') { s.className = 'ov-bool'; s.textContent = String(val); }
698
- else { s.textContent = String(val); }
714
+ else { s.textContent = _safeStr(val); }
699
715
  return s;
700
716
  }
701
717
 
@@ -705,7 +721,7 @@ function renderConsoleArg(arg) {
705
721
  // Backward compat: raw string
706
722
  const s = document.createElement('span');
707
723
  s.className = 'ov-str';
708
- s.textContent = String(arg);
724
+ s.textContent = _safeStr(arg);
709
725
  return s;
710
726
  }
711
727
  const { t, v } = arg;
@@ -723,7 +739,7 @@ function renderConsoleArg(arg) {
723
739
  return createTreeNode(null, v, false);
724
740
  }
725
741
  const s = document.createElement('span');
726
- s.textContent = String(v);
742
+ s.textContent = _safeStr(v);
727
743
  return s;
728
744
  }
729
745
 
@@ -1972,6 +1988,98 @@ function _findLeafChanges(oldVal, newVal, basePath, maxDepth) {
1972
1988
  return changes;
1973
1989
  }
1974
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
+
1975
2083
  function handleReduxEvent(event) {
1976
2084
  if (event.type !== 'redux') return;
1977
2085
  const { action, nextState } = event;
@@ -2004,6 +2112,29 @@ function handleReduxEvent(event) {
2004
2112
  }
2005
2113
  }
2006
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
+
2007
2138
  function renderRedux() {
2008
2139
  const content = $('reduxContent');
2009
2140
  const empty = $('reduxEmpty');
@@ -2031,12 +2162,32 @@ function renderRedux() {
2031
2162
  const header = document.createElement('div');
2032
2163
  header.className = 'rdx-entry-header';
2033
2164
  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>`;
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>`;
2035
2175
  // Toggle: click to expand, click again to collapse
2036
2176
  header.addEventListener('click', () => {
2037
2177
  state.redux.selected = isSelected ? -1 : a.index;
2038
2178
  renderRedux();
2039
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';
2040
2191
  entry.appendChild(header);
2041
2192
 
2042
2193
  // Expanded detail — only for explicitly selected action
@@ -2062,18 +2213,15 @@ function renderRedux() {
2062
2213
  detail.appendChild(createTreeNode(null, a.payload, false));
2063
2214
  }
2064
2215
 
2065
- // Store changes — show ONLY the changed leaf values, not entire state
2216
+ // Store changes — show full Previous and Current state for each changed key
2217
+ // with changed sub-keys highlighted
2066
2218
  const prevS = a.index > 0 ? states[a.index - 1] : null;
2067
2219
  const currS = states[a.index];
2068
2220
  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
2221
  a.changedKeys.forEach(key => {
2075
2222
  const keyWrap = document.createElement('div');
2076
2223
  keyWrap.className = 'rdx-store-diff';
2224
+
2077
2225
  const kLabel = document.createElement('div');
2078
2226
  kLabel.className = 'rdx-store-key-label';
2079
2227
  kLabel.textContent = key;
@@ -2082,29 +2230,34 @@ function renderRedux() {
2082
2230
  const oldVal = prevS ? prevS[key] : undefined;
2083
2231
  const newVal = currS[key];
2084
2232
 
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);
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);
2107
2259
  }
2260
+
2108
2261
  detail.appendChild(keyWrap);
2109
2262
  });
2110
2263
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactoradar",
3
3
  "productName": "ReactoRadar",
4
- "version": "1.5.8",
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/styles.css CHANGED
@@ -968,6 +968,13 @@ body {
968
968
  .rdx-diff-old { color: var(--red); text-decoration: line-through; opacity: 0.8; word-break: break-all; }
969
969
  .rdx-diff-arrow { color: var(--text-dim); font-size: 10px; flex-shrink: 0; }
970
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; }
971
978
  .rdx-diff-sign { font-weight: 700; font-size: 11px; flex-shrink: 0; width: 14px; text-align: center; }
972
979
  .rdx-diff-row.removed .rdx-diff-sign { color: var(--red); }
973
980
  .rdx-diff-row.added .rdx-diff-sign { color: var(--green); }