reactoradar 1.6.6 → 1.6.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/AGENTS.md +341 -0
- package/app.js +18 -19
- package/init.js +199 -0
- package/package.json +4 -2
- package/panels/console.js +789 -0
- package/panels/ga4.js +331 -0
- package/panels/native.js +260 -0
- package/panels/network.js +972 -0
- package/panels/performance.js +188 -0
- package/panels/react.js +23 -0
- package/panels/redux.js +441 -0
- package/panels/settings.js +791 -0
- package/panels/sources.js +289 -0
- package/panels/storage.js +191 -0
- package/sdk/RNDebugSDK.js +100 -38
package/AGENTS.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# ReactoRadar — Architecture & Coding Rules
|
|
2
|
+
|
|
3
|
+
> **Read this file before making ANY code changes.**
|
|
4
|
+
> This document defines the architecture, panel ownership, state contracts, and rules
|
|
5
|
+
> that must be followed to avoid breaking existing functionality.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## File Structure
|
|
10
|
+
|
|
11
|
+
### Main Process
|
|
12
|
+
| File | Role |
|
|
13
|
+
|------|------|
|
|
14
|
+
| `main.js` | Electron main process — windows, IPC, bridges, native logs, menus |
|
|
15
|
+
| `preload.js` | Context bridge — IPC allowlist, renderer API surface |
|
|
16
|
+
|
|
17
|
+
### Renderer (loaded in this order via `<script>` tags in index.html)
|
|
18
|
+
| File | Role |
|
|
19
|
+
|------|------|
|
|
20
|
+
| `app.js` | **Shared state**, helpers (`$`, `esc`, `ts`), navigation, `clearAll`, `freeMemory`, `clearActiveTab`, `updateDeviceBanner`, `takeScreenshot` |
|
|
21
|
+
| `panels/settings.js` | Settings panel + **shared utilities** used by other panels: `TAB_CONFIG`, `isTabEnabled`, `applyTheme`, `getTabVisibility`, `applyTabVisibility`, hidden URLs, localStorage helpers, `_applyUpdateBanner`, `_showChangelog`, `_loadVersionHistory` |
|
|
22
|
+
| `panels/console.js` | Console panel + **shared renderers**: `collectEntries`, `objPreview`, `createTreeNode`, `showContextMenu`, `showToast`, `addConsoleLog` |
|
|
23
|
+
| `panels/network.js` | Network panel — `handleNetworkEvent`, `renderNetwork`, HAR export, detail view |
|
|
24
|
+
| `panels/ga4.js` | GA4 Events panel — `ga4State`, `handleGA4Event`, `renderGA4List`, `renderGA4Summary` |
|
|
25
|
+
| `panels/redux.js` | Redux panel — `handleReduxEvent`, `renderRedux`, `_createHighlightedTree`, state diff |
|
|
26
|
+
| `panels/storage.js` | AsyncStorage panel — `handleStorageEvent`, `renderStorage`, `formatSize` |
|
|
27
|
+
| `panels/performance.js` | Performance + Memory panels — `perfState`, `handlePerfEvent`, `handleMemoryEvent`, `initMemoryPanel` |
|
|
28
|
+
| `panels/native.js` | Native Logs panel — `_nativeState`, `initNativeLogsPanel`, `_appendNativeLog` |
|
|
29
|
+
| `panels/react.js` | React Tree panel — just a connect button |
|
|
30
|
+
| `panels/sources.js` | Sources panel — Metro source file browser |
|
|
31
|
+
| `init.js` | **Boot script** (loaded LAST) — IPC wiring, button handlers, memory monitor, settings apply, panel init calls |
|
|
32
|
+
|
|
33
|
+
### Other
|
|
34
|
+
| File | Role |
|
|
35
|
+
|------|------|
|
|
36
|
+
| `styles.css` | All CSS — themes, panel styles, components |
|
|
37
|
+
| `index.html` | Shell HTML — nav sidebar, panel containers, script load order |
|
|
38
|
+
| `sdk/RNDebugSDK.js` | Client SDK injected into React Native apps |
|
|
39
|
+
| `bin/setup.js` | CLI setup script — copies SDK, patches RN project |
|
|
40
|
+
|
|
41
|
+
### Script Load Order (critical)
|
|
42
|
+
```
|
|
43
|
+
1. app.js — state, $, esc, ts, clearAll, freeMemory (no panel dependencies)
|
|
44
|
+
2. panels/settings.js — TAB_CONFIG, isTabEnabled, localStorage helpers (used by all panels)
|
|
45
|
+
3. panels/console.js — createTreeNode, showContextMenu, addConsoleLog (used by other panels)
|
|
46
|
+
4. panels/network.js — depends on: console.js (showToast, showContextMenu, createTreeNode)
|
|
47
|
+
5. panels/ga4.js — depends on: console.js (showContextMenu, createTreeNode)
|
|
48
|
+
6. panels/redux.js — depends on: console.js (addConsoleLog, createTreeNode, showContextMenu)
|
|
49
|
+
7. panels/storage.js — depends on: console.js (createTreeNode, showContextMenu)
|
|
50
|
+
8. panels/performance.js — depends on: settings.js (isTabEnabled)
|
|
51
|
+
9. panels/native.js — depends on: settings.js (isTabEnabled)
|
|
52
|
+
10. panels/react.js — no dependencies
|
|
53
|
+
11. panels/sources.js — no panel dependencies
|
|
54
|
+
12. init.js — depends on ALL of the above (calls init functions, registers IPC)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Critical Rule: Do NOT Break Other Panels
|
|
60
|
+
|
|
61
|
+
**Every panel is currently in a single `app.js` file.** Changes to one panel can silently
|
|
62
|
+
break another. Before making any change, check:
|
|
63
|
+
|
|
64
|
+
1. **Shared state** — Is the variable you're touching read/written by other panels?
|
|
65
|
+
2. **Shared functions** — Is the function you're modifying called from other panels?
|
|
66
|
+
3. **IPC listeners** — The preload `on()` method calls `removeAllListeners(channel)`.
|
|
67
|
+
Registering the same channel twice **kills the first listener**.
|
|
68
|
+
4. **Array sync** — `state.redux.actions` and `state.redux.states` MUST have the same length.
|
|
69
|
+
Never empty one without emptying the other.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Panels — Ownership & State
|
|
74
|
+
|
|
75
|
+
### Console Panel
|
|
76
|
+
- **Init:** `initConsolePanel()` (app.js)
|
|
77
|
+
- **State owned:**
|
|
78
|
+
- `state.console.logs` — array of log entries
|
|
79
|
+
- `state.console.levelFilters` — `{log, info, warn, error, debug}` booleans
|
|
80
|
+
- `state.console.searchFilter` — string
|
|
81
|
+
- `state.console.showRedux` — boolean (show redux actions in console)
|
|
82
|
+
- `_consolePending` — batch queue for rAF rendering
|
|
83
|
+
- `_consoleRAF` — pending requestAnimationFrame ID
|
|
84
|
+
- `_lastLogMsg`, `_lastLogRow`, `_lastLogCount` — log grouping state
|
|
85
|
+
- **IPC channels:** `console-event` (line 921)
|
|
86
|
+
- **Key functions:** `addConsoleLog()`, `flushConsoleBatch()`, `renderConsole()`, `buildLogRow()`, `buildLogBody()`
|
|
87
|
+
- **Cross-panel dependency:** Called by Redux (`handleReduxEvent` → `addConsoleLog`)
|
|
88
|
+
- **Constants:** `MAX_CONSOLE_LOGS = 5000`
|
|
89
|
+
|
|
90
|
+
### Network Panel
|
|
91
|
+
- **Init:** `initNetworkPanel()` (app.js)
|
|
92
|
+
- **State owned:**
|
|
93
|
+
- `state.network.requests` — `{id: requestObj}` map
|
|
94
|
+
- `state.network.order` — array of request IDs
|
|
95
|
+
- `state.network.selectedId`, `statusFilter`, `typeFilter`, `searchFilter`, `throttle`, `enabled`, `sortCol`, `sortDir`
|
|
96
|
+
- `_netRAF` — pending requestAnimationFrame ID
|
|
97
|
+
- **IPC channels:** `network-event` (line 357)
|
|
98
|
+
- **Key functions:** `handleNetworkEvent()`, `renderNetwork()`, `buildNetRow()`, `selectNetRequest()`, `closeNetDetail()`
|
|
99
|
+
- **Cross-panel dependency:** Calls `addConsoleLog()` for error toasts; calls `showToast()`
|
|
100
|
+
- **Constants:** `NET_COLS` (column definitions)
|
|
101
|
+
|
|
102
|
+
### Redux Panel
|
|
103
|
+
- **Init:** `initReduxPanel()` (app.js)
|
|
104
|
+
- **State owned:**
|
|
105
|
+
- `state.redux.actions` — array of action entries
|
|
106
|
+
- `state.redux.states` — array of full state snapshots (**MUST be same length as actions**)
|
|
107
|
+
- `state.redux.selected` — selected action index (-1 = none)
|
|
108
|
+
- `state.redux.searchFilter`, `sortDir`
|
|
109
|
+
- `_reduxCatColors`, `_reduxColorIdx` — action category color cache
|
|
110
|
+
- **IPC channels:** `redux-event` (line 356)
|
|
111
|
+
- **Key functions:** `handleReduxEvent()`, `renderRedux()`, `_createHighlightedTree()`, `_deepEqual()`, `_findLeafChanges()`
|
|
112
|
+
- **Cross-panel dependency:** Calls `addConsoleLog()` to mirror actions in console
|
|
113
|
+
- **CRITICAL:** `actions` and `states` arrays must always be the same length. Never clear one without the other.
|
|
114
|
+
- **Constants:** `MAX_REDUX_HISTORY = 500`
|
|
115
|
+
|
|
116
|
+
### GA4 Events Panel
|
|
117
|
+
- **Init:** `initGA4Panel()` (app.js)
|
|
118
|
+
- **State owned:**
|
|
119
|
+
- `ga4State` — standalone object: `{events, selected, searchFilter, sortDir, _raf}`
|
|
120
|
+
- `_ga4EventColors`, `_ga4ColorIdx` — event color cache
|
|
121
|
+
- **IPC channels:** `ga4-event` (line 360)
|
|
122
|
+
- **Key functions:** `handleGA4Event()`, `renderGA4List()`, `renderGA4Detail()`, `renderGA4Summary()`
|
|
123
|
+
|
|
124
|
+
### Storage Panel
|
|
125
|
+
- **Init:** `initStoragePanel()` (app.js)
|
|
126
|
+
- **State owned:**
|
|
127
|
+
- `state.storage.entries` — `{key: value}` map
|
|
128
|
+
- `state.storage.keys` — ordered key array
|
|
129
|
+
- `state.storage.selected`, `searchFilter`
|
|
130
|
+
- `_storageRAF` — pending requestAnimationFrame ID
|
|
131
|
+
- **IPC channels:** `storage-event` (line 358)
|
|
132
|
+
- **Key functions:** `handleStorageEvent()`, `renderStorage()`, `renderStorageValue()`
|
|
133
|
+
|
|
134
|
+
### Performance Panel
|
|
135
|
+
- **Init:** `initPerformancePanel()` (app.js)
|
|
136
|
+
- **State owned:**
|
|
137
|
+
- `perfState` — standalone object: `{fps, jsThread, uiThread, recording, data}`
|
|
138
|
+
- **IPC channels:** `perf-event` (line 362, shared with Memory)
|
|
139
|
+
- **Key functions:** `handlePerfEvent()`, `drawPerfGraph()`, `clearPerfCanvas()`
|
|
140
|
+
|
|
141
|
+
### Memory Panel
|
|
142
|
+
- **Init:** `initMemoryPanel()` (app.js)
|
|
143
|
+
- **State owned:** None (displays live values from perf events)
|
|
144
|
+
- **IPC channels:** `perf-event` (line 362, shared with Performance)
|
|
145
|
+
- **Key functions:** `handleMemoryEvent()`
|
|
146
|
+
|
|
147
|
+
### Native Logs Panel
|
|
148
|
+
- **Init:** `initNativeLogsPanel()` (app.js)
|
|
149
|
+
- **State owned:**
|
|
150
|
+
- `_nativeState` — standalone object: `{logs, connected, platform, levelFilter, searchFilter}`
|
|
151
|
+
- **IPC channels:** `native-log`, `native-status` (registered inside init, lines 3429, 3440)
|
|
152
|
+
- **Key functions:** `_clearNativeLogs()`, `_appendNativeLog()`, `_renderNativeLogs()`, `_autoDetectNative()`
|
|
153
|
+
- **Constants:** `MAX_NATIVE_LOGS`
|
|
154
|
+
|
|
155
|
+
### Settings Panel
|
|
156
|
+
- **Init:** `initSettingsPanel()` (app.js)
|
|
157
|
+
- **State owned:** All localStorage accessors (theme, font, app name, metro port, tab visibility, tab order, hidden URLs)
|
|
158
|
+
- **Key functions:** `applyTheme()`, `applyFontSize()`, `applyFontFamily()`, `applyAppName()`, `applyTabVisibility()`, `_buildTabVisGrid()`, `_loadVersionHistory()`
|
|
159
|
+
- **Constants:** `TAB_CONFIG`, `FONT_FAMILIES`
|
|
160
|
+
|
|
161
|
+
### Sources Panel
|
|
162
|
+
- **Init:** `initSourcesPanel()` (app.js)
|
|
163
|
+
- **Key functions:** `fetchSourceFileList()`, `renderSourceFileList()`, `buildSourceTreeNode()`, `loadSourceFile()`
|
|
164
|
+
|
|
165
|
+
### React Tree Panel
|
|
166
|
+
- **Init:** `initReactPanel()` (app.js)
|
|
167
|
+
- **Minimal** — just a connect button for React DevTools
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Shared Utilities — DO NOT MODIFY without checking all callers
|
|
172
|
+
|
|
173
|
+
| Function | Used By |
|
|
174
|
+
|----------|---------|
|
|
175
|
+
| `$(id)` | Every panel |
|
|
176
|
+
| `esc(s)` | Every panel |
|
|
177
|
+
| `ts(ms)` | Console, Network, Redux, GA4, Native |
|
|
178
|
+
| `collectEntries(val)` | `objPreview()`, `createTreeNode()` |
|
|
179
|
+
| `objPreview(val)` | `createTreeNode()` |
|
|
180
|
+
| `createTreeNode(key, val, collapsed)` | Console, Network, Redux, Storage, GA4 |
|
|
181
|
+
| `createPrimitiveSpan(val)` | `createTreeNode()`, `renderConsoleArg()` |
|
|
182
|
+
| `showContextMenu(e, items)` | Console, Network, Redux, Storage, GA4, Native |
|
|
183
|
+
| `clearAll()` | IPC `clear-all-ui`, memory warning |
|
|
184
|
+
| `freeMemory()` | IPC `device-all-disconnected` (debounced) |
|
|
185
|
+
| `clearActiveTab()` | Keyboard shortcut Cmd+K |
|
|
186
|
+
| `isTabEnabled(tabId)` | Redux, GA4, Storage, Native, Performance |
|
|
187
|
+
| `formatSize(bytes)` | Storage, Network, Memory |
|
|
188
|
+
| `switchPanel(panel)` | Navigation, tab visibility, toasts |
|
|
189
|
+
| `updateDeviceBanner(service, on)` | IPC connection handlers |
|
|
190
|
+
| `showToast(msg, type, panel)` | Network |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## IPC Channel Registry
|
|
195
|
+
|
|
196
|
+
### Renderer listens (app.js → via preload allowlist)
|
|
197
|
+
|
|
198
|
+
| Channel | Handler | Panel |
|
|
199
|
+
|---------|---------|-------|
|
|
200
|
+
| `ports` | Sets `state.ports` | Global |
|
|
201
|
+
| `cdp-targets` | Updates CDP button | Global |
|
|
202
|
+
| `redux-event` | `handleReduxEvent` | Redux |
|
|
203
|
+
| `network-event` | `handleNetworkEvent` | Network |
|
|
204
|
+
| `storage-event` | `handleStorageEvent` | Storage |
|
|
205
|
+
| `console-event` | `addConsoleLog` | Console |
|
|
206
|
+
| `ga4-event` | `handleGA4Event` | GA4 |
|
|
207
|
+
| `perf-event` | `handlePerfEvent` + `handleMemoryEvent` | Perf + Memory |
|
|
208
|
+
| `redux-connected` | `updateDeviceBanner` + cancel disconnect timer | Global |
|
|
209
|
+
| `network-connected` | `updateDeviceBanner` + cancel disconnect timer | Global |
|
|
210
|
+
| `storage-connected` | `updateDeviceBanner` + cancel disconnect timer | Global |
|
|
211
|
+
| `react-dt-status` | `updateDeviceBanner` | Global |
|
|
212
|
+
| `clear-all-ui` | `clearAll()` | Global |
|
|
213
|
+
| `device-all-disconnected` | debounced `freeMemory()` | Global |
|
|
214
|
+
| `app-version` | Sets `state._appVersion`, `state._isPackaged` | Settings |
|
|
215
|
+
| `update-available` | `_applyUpdateBanner()` | Settings |
|
|
216
|
+
| `update-downloaded` | `_applyUpdateBanner()` | Settings |
|
|
217
|
+
| `trigger-open-cdp` | Opens CDP | Global |
|
|
218
|
+
| `theme-changed` | Applies theme | Settings |
|
|
219
|
+
| `focus-search` | Focuses active panel search | Global |
|
|
220
|
+
| `native-log` | Appends native log | Native (registered inside init) |
|
|
221
|
+
| `native-status` | Updates native connection status | Native (registered inside init) |
|
|
222
|
+
|
|
223
|
+
### Preload allowlist (preload.js)
|
|
224
|
+
|
|
225
|
+
**Every new IPC channel MUST be added to the `allowed` array in `preload.js` line 10-14.**
|
|
226
|
+
If a channel is not in this array, the listener is silently dropped — no error, no warning.
|
|
227
|
+
|
|
228
|
+
### Main process sends (main.js)
|
|
229
|
+
|
|
230
|
+
| Channel | Sent from |
|
|
231
|
+
|---------|-----------|
|
|
232
|
+
| `redux-event` | `startBridge` callback for Redux bridge (port 9090) |
|
|
233
|
+
| `network-event` | `startBridge` callback for Network bridge (port 9092) |
|
|
234
|
+
| `storage-event` | `startBridge` callback for Storage bridge (port 9091) |
|
|
235
|
+
| `console-event` | `startBridge` callback for Network bridge (type=console) |
|
|
236
|
+
| `perf-event` | `startBridge` callback for Network bridge (type=perf) |
|
|
237
|
+
| `ga4-event` | `startBridge` callback for Network bridge (type=ga4) |
|
|
238
|
+
| `*-connected` | `startBridge` on WS connect/disconnect |
|
|
239
|
+
| `device-all-disconnected` | `startBridge` when all 3 bridges have 0 clients |
|
|
240
|
+
| `clear-all-ui` | Menu Cmd+K handler |
|
|
241
|
+
| `app-version` | `createMainWindow` on `did-finish-load` |
|
|
242
|
+
| `native-log` | Native log process stdout parser |
|
|
243
|
+
| `native-status` | Native log start/stop/error |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Main Process (main.js) — Structure
|
|
248
|
+
|
|
249
|
+
### WebSocket Bridges
|
|
250
|
+
|
|
251
|
+
| Port | Name | Client Set | Events Carried |
|
|
252
|
+
|------|------|------------|----------------|
|
|
253
|
+
| 9090 | Redux | `reduxClients` | `type: 'redux'` — action + nextState |
|
|
254
|
+
| 9091 | Storage | `storageClients` | `type: 'storage'` — key/value snapshots |
|
|
255
|
+
| 9092 | Network | `networkClients` | `type: 'console'`, `'network'`, `'perf'`, `'ga4'`, `'control'` |
|
|
256
|
+
| 8097 | React DT | `reactDTClients` | React DevTools relay (pass-through) |
|
|
257
|
+
|
|
258
|
+
### before-quit cleanup order
|
|
259
|
+
1. Send `device-all-disconnected` to renderer
|
|
260
|
+
2. Destroy `devtoolsWindow`
|
|
261
|
+
3. Close `reactDTServer` + clients
|
|
262
|
+
4. Close all bridge servers + clients
|
|
263
|
+
5. Kill `_nativeLogProcess` (second `before-quit` handler inside `setupIPC`)
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Rules for Making Changes
|
|
268
|
+
|
|
269
|
+
### 1. Adding a new panel
|
|
270
|
+
- Add init function: `initXxxPanel()`
|
|
271
|
+
- Add to `TAB_CONFIG` array
|
|
272
|
+
- Add to init sequence at bottom of app.js
|
|
273
|
+
- Add state to `clearAll()` and `freeMemory()` if the panel stores data
|
|
274
|
+
- Add case to `clearActiveTab()` if the panel has clearable data
|
|
275
|
+
|
|
276
|
+
### 2. Adding a new IPC channel
|
|
277
|
+
- Add `ipcMain.on/handle` in `main.js` `setupIPC()`
|
|
278
|
+
- Add channel name to `preload.js` allowed array (line 10-14)
|
|
279
|
+
- Add `window.electronAPI.on()` in app.js or expose send method in preload
|
|
280
|
+
- **Test:** Verify the listener fires by adding a `console.log` in the handler
|
|
281
|
+
|
|
282
|
+
### 3. Modifying shared state
|
|
283
|
+
- Check all panels that read/write the field (see tables above)
|
|
284
|
+
- **Redux arrays:** `actions` and `states` must stay in sync
|
|
285
|
+
- **rAF IDs:** Cancel with `cancelAnimationFrame()` before clearing data
|
|
286
|
+
- **freeMemory():** Only trim, never wipe arrays that other panels index into
|
|
287
|
+
|
|
288
|
+
### 4. Modifying clearAll() or freeMemory()
|
|
289
|
+
- `clearAll()` wipes everything + re-renders — used for explicit user action (Cmd+K)
|
|
290
|
+
- `freeMemory()` trims heavy data without clearing UI — used on disconnect/quit
|
|
291
|
+
- After modifying either, verify ALL panels still render correctly
|
|
292
|
+
- Test: Cmd+K clears all panels, device disconnect doesn't break subsequent events
|
|
293
|
+
|
|
294
|
+
### 5. Modifying the object tree renderer
|
|
295
|
+
- `collectEntries()`, `objPreview()`, `createTreeNode()` are used by Console, Network, Redux, Storage, GA4
|
|
296
|
+
- **Any change to these functions affects ALL panels** that render object trees
|
|
297
|
+
- `_createHighlightedTree()` in Redux is a SEPARATE tree renderer — changes to `createTreeNode` do NOT automatically apply to Redux diff trees
|
|
298
|
+
|
|
299
|
+
### 6. CSS variable naming
|
|
300
|
+
- Themes define: `--bg`, `--bg2`, `--bg3`, `--bg4` (NOT `--bg1`)
|
|
301
|
+
- `--text`, `--text-mid`, `--text-dim`, `--text-bright`
|
|
302
|
+
- `--accent`, `--accent2`, `--border`, `--border2`
|
|
303
|
+
- `--green`, `--yellow`, `--red`
|
|
304
|
+
- **Never use undefined variables** — the value resolves to transparent/empty
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## SDK Integration (in user's React Native app)
|
|
309
|
+
|
|
310
|
+
The SDK (`RNDebugSDK.js`) has two parts:
|
|
311
|
+
1. **Auto-connect** — WebSocket connections to ports 9090/9091/9092 open on import
|
|
312
|
+
2. **Manual wiring** — Redux requires adding `reduxMiddleware` or `reduxEnhancer` to the store
|
|
313
|
+
|
|
314
|
+
`npx reactoradar setup` handles:
|
|
315
|
+
- Copying SDK to `src/debug/RNDebugSDK.js`
|
|
316
|
+
- Patching entry file (`index.js`) to import SDK
|
|
317
|
+
- Auto-patching RTK `configureStore` with middleware
|
|
318
|
+
- **Legacy `createStore`**: Only prints manual instructions (does NOT auto-patch)
|
|
319
|
+
|
|
320
|
+
If Redux is not working:
|
|
321
|
+
1. Check if `reduxMiddleware` or `reduxEnhancer` is wired into the store
|
|
322
|
+
2. Connection to port 9090 ("RN app connected") does NOT mean events are flowing
|
|
323
|
+
3. Events only flow when `store.dispatch()` goes through the middleware/enhancer
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Testing Checklist (after any change)
|
|
328
|
+
|
|
329
|
+
- [ ] Console: logs appear, level filters work, search works, Cmd+K clears
|
|
330
|
+
- [ ] Network: requests appear, detail panel opens, HAR export works
|
|
331
|
+
- [ ] Redux: actions appear, state diff shows, Cmd+K clears
|
|
332
|
+
- [ ] GA4: events appear, summary works, color toggle works
|
|
333
|
+
- [ ] Storage: keys appear, values render, search works
|
|
334
|
+
- [ ] Performance: FPS/JS/UI graphs render when recording
|
|
335
|
+
- [ ] Memory: heap values appear
|
|
336
|
+
- [ ] Native Logs: connect to adb/xcrun, logs stream, Cmd+K clears
|
|
337
|
+
- [ ] Settings: theme switch, font size, tab visibility, version history loads
|
|
338
|
+
- [ ] Device disconnect: `freeMemory()` fires after 3s, panels still work after reconnect
|
|
339
|
+
- [ ] Device reconnect: cancel disconnect timer, new events flow immediately
|
|
340
|
+
- [ ] Cmd+K: clears active tab (all tabs including native)
|
|
341
|
+
- [ ] App quit: `devtoolsWindow` closed, bridges closed, no crash
|
package/app.js
CHANGED
|
@@ -136,6 +136,8 @@ document.addEventListener('keydown', (e) => {
|
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
// Global filter removed — each panel has its own search input
|
|
140
|
+
|
|
139
141
|
// ─── Clear (each panel has its own clear button now) ─────────────────────────
|
|
140
142
|
|
|
141
143
|
function clearActiveTab() {
|
|
@@ -236,28 +238,28 @@ function clearAll() {
|
|
|
236
238
|
if (ga4Detail) ga4Detail.innerHTML = '';
|
|
237
239
|
// Native logs
|
|
238
240
|
_nativeState.logs = [];
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
+
const nativeList = $('nativeLogList');
|
|
242
|
+
if (nativeList) nativeList.innerHTML = '';
|
|
241
243
|
// Performance
|
|
242
244
|
perfState.fps = [];
|
|
243
245
|
perfState.jsThread = [];
|
|
244
246
|
perfState.uiThread = [];
|
|
245
247
|
perfState.data = [];
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
248
|
+
const perfFPS = $('perfFPS'); if (perfFPS) perfFPS.textContent = '—';
|
|
249
|
+
const perfJS = $('perfJS'); if (perfJS) perfJS.textContent = '—';
|
|
250
|
+
const perfUI = $('perfUI'); if (perfUI) perfUI.textContent = '—';
|
|
249
251
|
clearPerfCanvas('perfFPSCanvas');
|
|
250
252
|
clearPerfCanvas('perfJSCanvas');
|
|
251
253
|
clearPerfCanvas('perfUICanvas');
|
|
252
254
|
// Memory
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const
|
|
255
|
+
const memHU = $('memHeapUsed'); if (memHU) memHU.textContent = '—';
|
|
256
|
+
const memHT = $('memHeapTotal'); if (memHT) memHT.textContent = '—';
|
|
257
|
+
const memN = $('memNative'); if (memN) memN.textContent = '—';
|
|
256
258
|
// Badges
|
|
257
|
-
$('cBadge').textContent = '0';
|
|
258
|
-
$('nBadge').textContent = '0';
|
|
259
|
-
$('rBadge').textContent = '0';
|
|
260
|
-
$('sBadge').textContent = '0';
|
|
259
|
+
const cB = $('cBadge'); if (cB) cB.textContent = '0';
|
|
260
|
+
const nB = $('nBadge'); if (nB) nB.textContent = '0';
|
|
261
|
+
const rB = $('rBadge'); if (rB) rB.textContent = '0';
|
|
262
|
+
const sB = $('sBadge'); if (sB) sB.textContent = '0';
|
|
261
263
|
if ($('ga4Badge')) $('ga4Badge').textContent = '0';
|
|
262
264
|
if ($('nativeBadge')) $('nativeBadge').textContent = '0';
|
|
263
265
|
// Re-render all
|
|
@@ -281,13 +283,8 @@ function freeMemory() {
|
|
|
281
283
|
if (state.console.logs.length > 200) {
|
|
282
284
|
state.console.logs = state.console.logs.slice(-200);
|
|
283
285
|
}
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
state.redux.actions = state.redux.actions.slice(-50);
|
|
287
|
-
state.redux.states = state.redux.states.slice(-50);
|
|
288
|
-
state.redux.actions.forEach((a, i) => a.index = i);
|
|
289
|
-
state.redux.selected = -1;
|
|
290
|
-
}
|
|
286
|
+
// Drop full Redux state snapshots (keep action metadata)
|
|
287
|
+
state.redux.states = [];
|
|
291
288
|
// Drop storage values (keep keys for reference)
|
|
292
289
|
for (const k in state.storage.entries) {
|
|
293
290
|
state.storage.entries[k] = null;
|
|
@@ -327,11 +324,13 @@ function updateDeviceBanner(service, connected) {
|
|
|
327
324
|
}
|
|
328
325
|
}
|
|
329
326
|
|
|
327
|
+
|
|
330
328
|
function takeScreenshot() {
|
|
331
329
|
const btn = $('btnScreenshot');
|
|
332
330
|
if (!btn) return;
|
|
333
331
|
const origText = btn.innerHTML;
|
|
334
332
|
btn.innerHTML = '<span style="opacity:0.6">Saving...</span>';
|
|
333
|
+
// Use Electron's native capturePage — always works, no DOM rendering issues
|
|
335
334
|
window.electronAPI?.captureScreenshot();
|
|
336
335
|
btn.innerHTML = '<span style="color:var(--green)">Saved!</span>';
|
|
337
336
|
setTimeout(() => { btn.innerHTML = origText; }, 2000);
|
package/init.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// init.js — IPC wiring, button handlers, memory monitor, and panel initialization
|
|
3
|
+
// This file loads LAST — after app.js (state/helpers) and all panel scripts.
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
// ─── CDP Button ───────────────────────────────────────────────────────────────
|
|
7
|
+
$('btnCDP')?.addEventListener('click', () => {
|
|
8
|
+
// Tell main process to open the CDP DevTools window with the best available target
|
|
9
|
+
window.electronAPI?.openCDPTarget(null); // null = use latest known target
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// ─── Screenshot Button ────────────────────────────────────────────────────────
|
|
13
|
+
$('btnScreenshot')?.addEventListener('click', takeScreenshot);
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
// IPC from Main
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
if (window.electronAPI) {
|
|
19
|
+
window.electronAPI.on('ports', ports => { state.ports = ports; });
|
|
20
|
+
|
|
21
|
+
window.electronAPI.on('cdp-targets', targets => {
|
|
22
|
+
state.cdpTargets = targets;
|
|
23
|
+
const btn = $('btnCDP');
|
|
24
|
+
if (btn) {
|
|
25
|
+
const hasCDP = targets?.length > 0;
|
|
26
|
+
const port = state.ports?.METRO || getStoredMetroPort();
|
|
27
|
+
btn.textContent = hasCDP
|
|
28
|
+
? `JS Debugger (:${port}) [${targets.length}] ↗`
|
|
29
|
+
: `JS Debugger (:${port}) ↗`;
|
|
30
|
+
btn.style.opacity = hasCDP ? '1' : '0.5';
|
|
31
|
+
if (hasCDP) {
|
|
32
|
+
btn.onclick = () => window.electronAPI.openCDPTarget(targets[0].webSocketDebuggerUrl);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
window.electronAPI.on('redux-event', handleReduxEvent);
|
|
38
|
+
window.electronAPI.on('network-event', handleNetworkEvent);
|
|
39
|
+
window.electronAPI.on('storage-event', handleStorageEvent);
|
|
40
|
+
|
|
41
|
+
window.electronAPI.on('ga4-event', handleGA4Event);
|
|
42
|
+
|
|
43
|
+
window.electronAPI.on('perf-event', event => {
|
|
44
|
+
handlePerfEvent(event);
|
|
45
|
+
handleMemoryEvent(event);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
window.electronAPI.on('clear-all-ui', clearAll);
|
|
49
|
+
|
|
50
|
+
// When all device bridges disconnect, release heavy memory but keep logs visible.
|
|
51
|
+
// Debounced to avoid data loss during hot reloads or flaky connections.
|
|
52
|
+
let _disconnectTimer = null;
|
|
53
|
+
window.electronAPI.on('device-all-disconnected', () => {
|
|
54
|
+
clearTimeout(_disconnectTimer);
|
|
55
|
+
_disconnectTimer = setTimeout(() => {
|
|
56
|
+
console.log('[App] All devices disconnected — freeing memory');
|
|
57
|
+
freeMemory();
|
|
58
|
+
}, 3000);
|
|
59
|
+
});
|
|
60
|
+
// Cancel pending free if a device reconnects
|
|
61
|
+
const _cancelDisconnectTimer = () => { clearTimeout(_disconnectTimer); _disconnectTimer = null; };
|
|
62
|
+
window.electronAPI.on('redux-connected', on => { if (on) _cancelDisconnectTimer(); updateDeviceBanner('redux', on); });
|
|
63
|
+
window.electronAPI.on('network-connected', on => { if (on) _cancelDisconnectTimer(); updateDeviceBanner('network', on); });
|
|
64
|
+
window.electronAPI.on('storage-connected', on => { if (on) _cancelDisconnectTimer(); updateDeviceBanner('storage', on); });
|
|
65
|
+
window.electronAPI.on('react-dt-status', on => { updateDeviceBanner('reactDT', on); });
|
|
66
|
+
|
|
67
|
+
// Cmd+F — focus the search input for the active panel
|
|
68
|
+
function _handleFind() {
|
|
69
|
+
// If network detail is open, focus the detail search
|
|
70
|
+
if (state.activePanel === 'network' && state.network.selectedId) {
|
|
71
|
+
const wrap = $('detailSearchWrap');
|
|
72
|
+
const input = $('detailSearchInput');
|
|
73
|
+
if (wrap && input) {
|
|
74
|
+
wrap.style.display = 'flex';
|
|
75
|
+
input.focus();
|
|
76
|
+
input.select();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const searchMap = {
|
|
81
|
+
console: 'consoleSearch',
|
|
82
|
+
network: 'netSearchInput',
|
|
83
|
+
ga4: 'ga4Search',
|
|
84
|
+
redux: 'reduxSearch',
|
|
85
|
+
storage: 'storageSearch',
|
|
86
|
+
};
|
|
87
|
+
const inputId = searchMap[state.activePanel];
|
|
88
|
+
if (inputId) {
|
|
89
|
+
const el = $(inputId);
|
|
90
|
+
if (el) { el.focus(); el.select(); }
|
|
91
|
+
}
|
|
92
|
+
// Also show/focus Console bottom find bar
|
|
93
|
+
if (state.activePanel === 'console') {
|
|
94
|
+
const bar = $('consoleFindBar');
|
|
95
|
+
if (bar) { bar.style.display = 'flex'; $('consoleFindInput')?.focus(); }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
window.electronAPI.on('focus-search', _handleFind);
|
|
99
|
+
// Direct keyboard fallback — Electron menu accelerators can miss in some contexts
|
|
100
|
+
document.addEventListener('keydown', (e) => {
|
|
101
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'f') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
_handleFind();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
window.electronAPI.on('app-version', (version, isPackaged) => {
|
|
108
|
+
state._appVersion = version;
|
|
109
|
+
state._isPackaged = !!isPackaged;
|
|
110
|
+
// Update anywhere the version is displayed
|
|
111
|
+
document.querySelectorAll('#aboutVersion').forEach(el => el.textContent = 'v' + version);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
window.electronAPI.on('update-available', ({ current, latest, autoUpdate }) => {
|
|
115
|
+
state._updateAvailable = { current, latest, autoUpdate };
|
|
116
|
+
_applyUpdateBanner();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
window.electronAPI.on('update-downloaded', ({ version }) => {
|
|
120
|
+
state._updateDownloaded = version;
|
|
121
|
+
_applyUpdateBanner();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
window.electronAPI.on('trigger-open-cdp', () => {
|
|
125
|
+
window.electronAPI?.openCDPTarget(null);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Theme toggle from menu shortcut (Cmd+Shift+T)
|
|
129
|
+
window.electronAPI.on('theme-changed', theme => {
|
|
130
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
131
|
+
setStoredTheme(theme);
|
|
132
|
+
document.querySelectorAll('#themeSwitcher .theme-card')
|
|
133
|
+
.forEach(b => b.classList.toggle('active', b.dataset.theme === theme));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Console event IPC
|
|
137
|
+
window.electronAPI.on('console-event', addConsoleLog);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── Memory Monitor ──────────────────────────────────────────────────────────
|
|
141
|
+
// Check memory usage periodically and warn user before it causes blank screen
|
|
142
|
+
let _memoryWarningShown = false;
|
|
143
|
+
setInterval(() => {
|
|
144
|
+
if (!window.performance || !performance.memory) return;
|
|
145
|
+
const used = performance.memory.usedJSHeapSize;
|
|
146
|
+
const limit = performance.memory.jsHeapSizeLimit;
|
|
147
|
+
const pct = used / limit;
|
|
148
|
+
// Warn at 70% usage
|
|
149
|
+
if (pct > 0.7 && !_memoryWarningShown) {
|
|
150
|
+
_memoryWarningShown = true;
|
|
151
|
+
const banner = document.createElement('div');
|
|
152
|
+
banner.id = 'memoryWarning';
|
|
153
|
+
banner.className = 'memory-warning';
|
|
154
|
+
const usedMB = Math.round(used / 1024 / 1024);
|
|
155
|
+
banner.innerHTML = `<span>High memory usage (${usedMB}MB) — ReactoRadar may become unresponsive.</span>`
|
|
156
|
+
+ `<button class="memory-warn-btn" id="memWarnClear">Clear All Data</button>`
|
|
157
|
+
+ `<button class="memory-warn-btn" id="memWarnDismiss">Dismiss</button>`;
|
|
158
|
+
document.body.prepend(banner);
|
|
159
|
+
$('memWarnClear')?.addEventListener('click', () => {
|
|
160
|
+
// Clear all panel data
|
|
161
|
+
state.console.logs = []; _consolePending = [];
|
|
162
|
+
_lastLogMsg = ''; _lastLogRow = null; _lastLogCount = 1;
|
|
163
|
+
$('cBadge').textContent = '0'; renderConsole();
|
|
164
|
+
state.network.requests = {}; state.network.order = []; state.network.selectedId = null;
|
|
165
|
+
$('nBadge').textContent = '0'; renderNetwork();
|
|
166
|
+
state.redux.actions = []; state.redux.states = []; state.redux.selected = -1;
|
|
167
|
+
$('rBadge').textContent = '0'; renderRedux();
|
|
168
|
+
banner.remove(); _memoryWarningShown = false;
|
|
169
|
+
});
|
|
170
|
+
$('memWarnDismiss')?.addEventListener('click', () => { banner.remove(); });
|
|
171
|
+
}
|
|
172
|
+
// Reset flag when memory drops
|
|
173
|
+
if (pct < 0.5) _memoryWarningShown = false;
|
|
174
|
+
}, 30000); // Check every 30 seconds
|
|
175
|
+
|
|
176
|
+
// Apply saved theme + font size + font family + app name on load
|
|
177
|
+
|
|
178
|
+
applyTheme(getStoredTheme());
|
|
179
|
+
applyFontSize(getStoredFontSize());
|
|
180
|
+
applyFontFamily(getStoredFontFamily());
|
|
181
|
+
applyAppName(getStoredAppName());
|
|
182
|
+
applyTabVisibility();
|
|
183
|
+
|
|
184
|
+
// Send stored metro port to backend
|
|
185
|
+
window.electronAPI?.setMetroPort(getStoredMetroPort());
|
|
186
|
+
|
|
187
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
188
|
+
// INIT — Panel initialization (all panel scripts must be loaded before this)
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
initConsolePanel();
|
|
191
|
+
initNetworkPanel();
|
|
192
|
+
initGA4Panel();
|
|
193
|
+
initPerformancePanel();
|
|
194
|
+
initMemoryPanel();
|
|
195
|
+
initReduxPanel();
|
|
196
|
+
initStoragePanel();
|
|
197
|
+
initReactPanel();
|
|
198
|
+
initNativeLogsPanel();
|
|
199
|
+
initSettingsPanel();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.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": {
|
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
"preload.js",
|
|
37
37
|
"index.html",
|
|
38
38
|
"app.js",
|
|
39
|
+
"init.js",
|
|
40
|
+
"panels/",
|
|
39
41
|
"styles.css",
|
|
40
42
|
"sdk/",
|
|
41
43
|
"bin/",
|
|
42
44
|
"assets/",
|
|
43
|
-
"
|
|
45
|
+
"AGENTS.md"
|
|
44
46
|
],
|
|
45
47
|
"scripts": {
|
|
46
48
|
"start": "unset ELECTRON_RUN_AS_NODE && electron .",
|