reactoradar 1.6.3 → 1.6.4

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
@@ -303,6 +303,17 @@ if (window.electronAPI) {
303
303
 
304
304
  // Cmd+F — focus the search input for the active panel
305
305
  function _handleFind() {
306
+ // If network detail is open, focus the detail search
307
+ if (state.activePanel === 'network' && state.network.selectedId) {
308
+ const wrap = $('detailSearchWrap');
309
+ const input = $('detailSearchInput');
310
+ if (wrap && input) {
311
+ wrap.style.display = 'flex';
312
+ input.focus();
313
+ input.select();
314
+ return;
315
+ }
316
+ }
306
317
  const searchMap = {
307
318
  console: 'consoleSearch',
308
319
  network: 'netSearchInput',
@@ -366,6 +377,7 @@ function _applyUpdateBanner() {
366
377
  if (!info) return;
367
378
  const { current, latest, autoUpdate } = info;
368
379
  const downloaded = state._updateDownloaded;
380
+ const targetVersion = downloaded || latest;
369
381
 
370
382
  const el = $('aboutVersion');
371
383
  if (el) {
@@ -376,39 +388,86 @@ function _applyUpdateBanner() {
376
388
  }
377
389
  }
378
390
 
379
- // Remove old button if state changed
391
+ // Remove old buttons if state changed
380
392
  const oldBtn = $('updateBtn');
381
393
  if (oldBtn && downloaded && !oldBtn.dataset.isRestart) oldBtn.parentElement?.remove();
394
+ const oldChangelog = $('changelogBtn');
395
+ if (oldChangelog && downloaded && !oldChangelog.dataset.updated) oldChangelog.remove();
396
+
397
+ const aboutEl = document.querySelector('.settings-about');
398
+ if (!aboutEl) return;
399
+
400
+ // Add "What's new?" link
401
+ if (!$('changelogBtn')) {
402
+ const link = document.createElement('div');
403
+ link.style.cssText = 'margin-top:6px;text-align:center';
404
+ link.innerHTML = `<span id="changelogBtn" class="about-link" style="font-size:10px;cursor:pointer" data-updated="${downloaded ? '1' : ''}">What's new in v${targetVersion}?</span>`;
405
+ aboutEl.appendChild(link);
406
+ $('changelogBtn')?.addEventListener('click', () => _showChangelog(targetVersion));
407
+ }
382
408
 
383
- // Add update button in settings if not already there
409
+ // Add update button
384
410
  if (!$('updateBtn')) {
385
- const aboutEl = document.querySelector('.settings-about');
386
- if (aboutEl) {
387
- const btn = document.createElement('div');
388
- btn.style.cssText = 'margin-top:10px';
389
- if (downloaded) {
390
- // Update is downloaded — show "Restart & Update"
391
- btn.innerHTML = '<button id="updateBtn" data-is-restart="1" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Restart & Update to v' + downloaded + '</button>';
392
- aboutEl.appendChild(btn);
393
- $('updateBtn')?.addEventListener('click', () => {
394
- window.electronAPI?.installUpdate();
395
- });
396
- } else if (autoUpdate) {
397
- // Auto-update in progress — show downloading status
398
- btn.innerHTML = '<button id="updateBtn" class="tb-btn" style="font-size:11px;padding:6px 16px;opacity:0.7" disabled>Downloading v' + latest + '...</button>';
399
- aboutEl.appendChild(btn);
400
- } else {
401
- // npx/manual — show download link
402
- btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Download v' + latest + '</button>';
403
- aboutEl.appendChild(btn);
404
- $('updateBtn')?.addEventListener('click', () => {
405
- window.electronAPI?.openExternal('https://github.com/sharanagouda/reactoradar/releases');
406
- });
407
- }
411
+ const btn = document.createElement('div');
412
+ btn.style.cssText = 'margin-top:8px;text-align:center';
413
+ if (downloaded) {
414
+ btn.innerHTML = '<button id="updateBtn" data-is-restart="1" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Restart & Update to v' + downloaded + '</button>';
415
+ aboutEl.appendChild(btn);
416
+ $('updateBtn')?.addEventListener('click', () => window.electronAPI?.installUpdate());
417
+ } else if (autoUpdate) {
418
+ btn.innerHTML = '<button id="updateBtn" class="tb-btn" style="font-size:11px;padding:6px 16px;opacity:0.7" disabled>Downloading v' + latest + '...</button>';
419
+ aboutEl.appendChild(btn);
420
+ } else {
421
+ btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Download v' + latest + '</button>';
422
+ aboutEl.appendChild(btn);
423
+ $('updateBtn')?.addEventListener('click', () => window.electronAPI?.openExternal('https://github.com/sharanagouda/reactoradar/releases'));
408
424
  }
409
425
  }
410
426
  }
411
427
 
428
+ async function _showChangelog(version) {
429
+ // Remove existing modal
430
+ $('changelogModal')?.remove();
431
+
432
+ const modal = document.createElement('div');
433
+ modal.id = 'changelogModal';
434
+ modal.className = 'changelog-modal-overlay';
435
+ modal.innerHTML = `
436
+ <div class="changelog-modal">
437
+ <div class="changelog-header">
438
+ <span class="changelog-title">What's New in v${esc(version)}</span>
439
+ <button class="changelog-close" id="changelogClose">&times;</button>
440
+ </div>
441
+ <div class="changelog-body" id="changelogBody">
442
+ <div style="color:var(--text-dim);padding:20px;text-align:center">Loading release notes...</div>
443
+ </div>
444
+ </div>`;
445
+ document.body.appendChild(modal);
446
+
447
+ // Close handlers
448
+ $('changelogClose')?.addEventListener('click', () => modal.remove());
449
+ modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
450
+
451
+ // Fetch changelog
452
+ try {
453
+ const notes = await window.electronAPI?.fetchChangelog(version);
454
+ const body = $('changelogBody');
455
+ if (body && notes) {
456
+ // Simple markdown-like rendering
457
+ body.innerHTML = notes
458
+ .replace(/^### (.+)$/gm, '<h3 style="color:var(--accent);font-size:12px;font-weight:700;margin:12px 0 6px">$1</h3>')
459
+ .replace(/^## (.+)$/gm, '<h2 style="color:var(--text);font-size:14px;font-weight:700;margin:16px 0 8px">$1</h2>')
460
+ .replace(/^- \*\*(.+?)\*\*(.*)$/gm, '<div style="margin:3px 0;font-size:11px;line-height:1.6"><b style="color:var(--text)">$1</b><span style="color:var(--text-dim)">$2</span></div>')
461
+ .replace(/^- (.+)$/gm, '<div style="margin:3px 0;font-size:11px;line-height:1.6;color:var(--text-mid)">• $1</div>')
462
+ .replace(/`([^`]+)`/g, '<code style="background:var(--bg3);padding:1px 4px;border-radius:3px;color:var(--accent);font-size:10px">$1</code>')
463
+ .replace(/\n\n/g, '<br/>');
464
+ }
465
+ } catch {
466
+ const body = $('changelogBody');
467
+ if (body) body.innerHTML = '<div style="color:var(--red);padding:20px;text-align:center">Could not fetch release notes</div>';
468
+ }
469
+ }
470
+
412
471
  function updateDeviceBanner(service, connected) {
413
472
  state.connections[service] = connected;
414
473
  const el = $('deviceStatus');
@@ -1255,6 +1314,13 @@ function initNetworkPanel() {
1255
1314
  <div class="net-detail-pane" id="netDetailPane">
1256
1315
  <div class="net-detail-bar">
1257
1316
  <div class="detail-tabs" id="netDetailTabs"></div>
1317
+ <div class="detail-search-wrap" id="detailSearchWrap" style="display:none">
1318
+ <input id="detailSearchInput" class="detail-search-input" placeholder="Search key or value..." />
1319
+ <span id="detailSearchCount" class="detail-search-count"></span>
1320
+ <button class="detail-search-nav" id="detailSearchPrev" title="Previous">&#9650;</button>
1321
+ <button class="detail-search-nav" id="detailSearchNext" title="Next">&#9660;</button>
1322
+ <button class="detail-search-close" id="detailSearchClose" title="Close search">&times;</button>
1323
+ </div>
1258
1324
  <button class="detail-close" id="netDetailClose" title="Close">&times;</button>
1259
1325
  </div>
1260
1326
  <div class="detail-content" id="netDetailContent"></div>
@@ -1408,6 +1474,121 @@ function initNetworkPanel() {
1408
1474
  // Close detail button
1409
1475
  $('netDetailClose').addEventListener('click', closeNetDetail);
1410
1476
 
1477
+ // Detail panel search
1478
+ let _detailSearchMatches = [];
1479
+ let _detailSearchIdx = -1;
1480
+
1481
+ function _detailSearch() {
1482
+ const term = $('detailSearchInput')?.value?.trim().toLowerCase();
1483
+ const body = $('netDetailContent');
1484
+ if (!body || !term) { _detailClearSearch(); return; }
1485
+
1486
+ // Remove old highlights
1487
+ body.querySelectorAll('.detail-search-hl').forEach(el => {
1488
+ const parent = el.parentNode;
1489
+ parent.replaceChild(document.createTextNode(el.textContent), el);
1490
+ parent.normalize();
1491
+ });
1492
+
1493
+ _detailSearchMatches = [];
1494
+ _detailSearchIdx = -1;
1495
+
1496
+ // Walk all text nodes and highlight matches
1497
+ const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null);
1498
+ const textNodes = [];
1499
+ while (walker.nextNode()) textNodes.push(walker.currentNode);
1500
+
1501
+ textNodes.forEach(node => {
1502
+ const text = node.textContent;
1503
+ const lower = text.toLowerCase();
1504
+ if (!lower.includes(term)) return;
1505
+
1506
+ const frag = document.createDocumentFragment();
1507
+ let lastIdx = 0;
1508
+ let idx;
1509
+ while ((idx = lower.indexOf(term, lastIdx)) !== -1) {
1510
+ if (idx > lastIdx) frag.appendChild(document.createTextNode(text.slice(lastIdx, idx)));
1511
+ const hl = document.createElement('span');
1512
+ hl.className = 'detail-search-hl';
1513
+ hl.textContent = text.slice(idx, idx + term.length);
1514
+ _detailSearchMatches.push(hl);
1515
+ frag.appendChild(hl);
1516
+ lastIdx = idx + term.length;
1517
+ }
1518
+ if (lastIdx < text.length) frag.appendChild(document.createTextNode(text.slice(lastIdx)));
1519
+ node.parentNode.replaceChild(frag, node);
1520
+ });
1521
+
1522
+ // Update count
1523
+ const countEl = $('detailSearchCount');
1524
+ if (countEl) countEl.textContent = _detailSearchMatches.length ? `${_detailSearchMatches.length} found` : 'No match';
1525
+
1526
+ // Navigate to first match
1527
+ if (_detailSearchMatches.length) _detailNavTo(0);
1528
+ }
1529
+
1530
+ function _detailNavTo(idx) {
1531
+ // Remove active highlight from previous
1532
+ if (_detailSearchIdx >= 0 && _detailSearchMatches[_detailSearchIdx]) {
1533
+ _detailSearchMatches[_detailSearchIdx].classList.remove('active');
1534
+ }
1535
+ _detailSearchIdx = idx;
1536
+ const el = _detailSearchMatches[idx];
1537
+ if (!el) return;
1538
+ el.classList.add('active');
1539
+ el.scrollIntoView({ block: 'center', behavior: 'smooth' });
1540
+ // Update count
1541
+ const countEl = $('detailSearchCount');
1542
+ if (countEl) countEl.textContent = `${idx + 1}/${_detailSearchMatches.length}`;
1543
+ }
1544
+
1545
+ function _detailClearSearch() {
1546
+ const body = $('netDetailContent');
1547
+ if (body) {
1548
+ body.querySelectorAll('.detail-search-hl').forEach(el => {
1549
+ const parent = el.parentNode;
1550
+ parent.replaceChild(document.createTextNode(el.textContent), el);
1551
+ parent.normalize();
1552
+ });
1553
+ }
1554
+ _detailSearchMatches = [];
1555
+ _detailSearchIdx = -1;
1556
+ const countEl = $('detailSearchCount');
1557
+ if (countEl) countEl.textContent = '';
1558
+ }
1559
+
1560
+ $('detailSearchInput')?.addEventListener('input', () => {
1561
+ clearTimeout($('detailSearchInput')._debounce);
1562
+ $('detailSearchInput')._debounce = setTimeout(_detailSearch, 200);
1563
+ });
1564
+ $('detailSearchInput')?.addEventListener('keydown', (e) => {
1565
+ if (e.key === 'Enter') {
1566
+ e.preventDefault();
1567
+ if (!_detailSearchMatches.length) return;
1568
+ const next = e.shiftKey
1569
+ ? (_detailSearchIdx - 1 + _detailSearchMatches.length) % _detailSearchMatches.length
1570
+ : (_detailSearchIdx + 1) % _detailSearchMatches.length;
1571
+ _detailNavTo(next);
1572
+ }
1573
+ if (e.key === 'Escape') {
1574
+ _detailClearSearch();
1575
+ $('detailSearchWrap').style.display = 'none';
1576
+ }
1577
+ });
1578
+ $('detailSearchNext')?.addEventListener('click', () => {
1579
+ if (!_detailSearchMatches.length) return;
1580
+ _detailNavTo((_detailSearchIdx + 1) % _detailSearchMatches.length);
1581
+ });
1582
+ $('detailSearchPrev')?.addEventListener('click', () => {
1583
+ if (!_detailSearchMatches.length) return;
1584
+ _detailNavTo((_detailSearchIdx - 1 + _detailSearchMatches.length) % _detailSearchMatches.length);
1585
+ });
1586
+ $('detailSearchClose')?.addEventListener('click', () => {
1587
+ _detailClearSearch();
1588
+ $('detailSearchInput').value = '';
1589
+ $('detailSearchWrap').style.display = 'none';
1590
+ });
1591
+
1411
1592
  buildNetHeader();
1412
1593
  }
1413
1594
 
@@ -1828,14 +2009,38 @@ function closeNetDetail() {
1828
2009
  );
1829
2010
  }
1830
2011
 
2012
+ function _estimateSize(val) {
2013
+ if (val == null) return 0;
2014
+ if (typeof val === 'string') return val.length;
2015
+ try { return JSON.stringify(val).length; } catch { return 0; }
2016
+ }
2017
+
2018
+ function _formatBytes(bytes) {
2019
+ if (bytes < 1024) return `${bytes}B`;
2020
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
2021
+ return `${(bytes / 1048576).toFixed(1)}MB`;
2022
+ }
2023
+
1831
2024
  function renderNetDetailTabs(r) {
1832
2025
  const tabs = $('netDetailTabs');
1833
2026
  tabs.innerHTML = '';
1834
- ['Headers', 'Request', 'Preview', 'Response'].forEach(label => {
1835
- const key = label.toLowerCase();
2027
+
2028
+ const tabDefs = [
2029
+ { label: 'Headers', key: 'headers' },
2030
+ { label: 'Request', key: 'request', sizeFrom: 'requestBody' },
2031
+ { label: 'Preview', key: 'preview', sizeFrom: 'responseBody' },
2032
+ { label: 'Response', key: 'response', sizeFrom: 'responseBody' },
2033
+ ];
2034
+
2035
+ tabDefs.forEach(({ label, key, sizeFrom }) => {
1836
2036
  const btn = document.createElement('button');
1837
2037
  btn.className = 'detail-tab' + (r._tab === key ? ' active' : '');
1838
- btn.textContent = label;
2038
+ let text = label;
2039
+ if (sizeFrom && r[sizeFrom]) {
2040
+ const size = _estimateSize(r[sizeFrom]);
2041
+ if (size > 0) text += ` (${_formatBytes(size)})`;
2042
+ }
2043
+ btn.textContent = text;
1839
2044
  btn.addEventListener('click', () => {
1840
2045
  r._tab = key;
1841
2046
  tabs.querySelectorAll('.detail-tab').forEach(b => b.classList.remove('active'));
@@ -1844,6 +2049,12 @@ function renderNetDetailTabs(r) {
1844
2049
  });
1845
2050
  tabs.appendChild(btn);
1846
2051
  });
2052
+
2053
+ // Show search box for Preview/Response tabs
2054
+ const searchWrap = $('detailSearchWrap');
2055
+ if (searchWrap) {
2056
+ searchWrap.style.display = (r._tab === 'preview' || r._tab === 'response' || r._tab === 'headers') ? 'flex' : 'none';
2057
+ }
1847
2058
  }
1848
2059
 
1849
2060
  function renderNetDetailContent(r) {
package/main.js CHANGED
@@ -588,6 +588,21 @@ function setupIPC() {
588
588
  }
589
589
  });
590
590
 
591
+ ipcMain.handle('fetch-changelog', async (_, version) => {
592
+ return new Promise((resolve) => {
593
+ https.get(`https://api.github.com/repos/sharanagouda/reactoradar/releases/tags/v${version}`, {
594
+ headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
595
+ }, (res) => {
596
+ let data = '';
597
+ res.on('data', d => data += d);
598
+ res.on('end', () => {
599
+ try { resolve(JSON.parse(data).body || 'No release notes available.'); }
600
+ catch { resolve('Could not fetch release notes.'); }
601
+ });
602
+ }).on('error', () => resolve('Could not connect to GitHub.'));
603
+ });
604
+ });
605
+
591
606
  ipcMain.on('install-update', () => {
592
607
  if (autoUpdater) {
593
608
  autoUpdater.quitAndInstall(false, true);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactoradar",
3
3
  "productName": "ReactoRadar",
4
- "version": "1.6.3",
4
+ "version": "1.6.4",
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/preload.js CHANGED
@@ -35,4 +35,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
35
35
  startNativeLogs: (platform) => ipcRenderer.send('start-native-logs', platform),
36
36
  stopNativeLogs: () => ipcRenderer.send('stop-native-logs'),
37
37
  detectNativePlatform: () => ipcRenderer.invoke('detect-native-platform'),
38
+ fetchChangelog: (version) => ipcRenderer.invoke('fetch-changelog', version),
38
39
  });
package/styles.css CHANGED
@@ -1682,6 +1682,27 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
1682
1682
  .native-fatal { background: rgba(255,94,114,.08); }
1683
1683
  .native-fatal .native-log-msg { color: var(--red); font-weight: 700; }
1684
1684
 
1685
+ /* ── Detail Panel Search ───────────────────────────────────────────────────── */
1686
+ .detail-search-wrap { display: flex; align-items: center; gap: 4px; margin-left: auto; padding: 0 6px; }
1687
+ .detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--bg1); color: var(--text); border-radius: 3px; outline: none; }
1688
+ .detail-search-input:focus { border-color: var(--accent); }
1689
+ .detail-search-count { font-size: 9px; color: var(--text-dim); white-space: nowrap; min-width: 45px; }
1690
+ .detail-search-nav { background: transparent; border: 1px solid var(--border); color: var(--text-dim); font-size: 9px; width: 18px; height: 18px; border-radius: 3px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
1691
+ .detail-search-nav:hover { background: var(--bg3); color: var(--text); }
1692
+ .detail-search-close { background: transparent; border: none; color: var(--text-dim); font-size: 12px; cursor: pointer; padding: 0 2px; }
1693
+ .detail-search-close:hover { color: var(--text); }
1694
+ .detail-search-hl { background: rgba(255,213,79,.3); border-radius: 2px; padding: 0 1px; }
1695
+ .detail-search-hl.active { background: rgba(255,213,79,.7); outline: 1px solid rgba(255,213,79,.9); }
1696
+
1697
+ /* ── Changelog Modal ───────────────────────────────────────────────────────── */
1698
+ .changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 9999; display: flex; align-items: center; justify-content: center; }
1699
+ .changelog-modal { background: var(--bg1); border: 1px solid var(--border); border-radius: 10px; width: 520px; max-width: 90vw; max-height: 70vh; display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,.4); }
1700
+ .changelog-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
1701
+ .changelog-title { font-size: 13px; font-weight: 700; color: var(--text); }
1702
+ .changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding: 0 4px; }
1703
+ .changelog-close:hover { color: var(--text); }
1704
+ .changelog-body { flex: 1; overflow-y: auto; padding: 16px; font-size: 11px; line-height: 1.6; color: var(--text-mid); }
1705
+
1685
1706
  /* ── Support Button ────────────────────────────────────────────────────────── */
1686
1707
  .support-btn { background: linear-gradient(135deg, #ff813f, #ff5e72); color: #fff; border: none; padding: 8px 20px; border-radius: 8px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.15s; letter-spacing: 0.3px; }
1687
1708
  .support-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(255,94,114,.3); }