reactoradar 1.6.8 → 1.6.10

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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # ReactoRadar — Architecture & Coding Rules
2
2
 
3
- > **Read this file before making ANY code changes.**
3
+ > **Read this file COMPLETELY before making ANY code changes.**
4
4
  > This document defines the architecture, panel ownership, state contracts, and rules
5
5
  > that must be followed to avoid breaking existing functionality.
6
+ >
7
+ > **NEVER release without passing the Pre-Release Checklist at the bottom.**
6
8
 
7
9
  ---
8
10
 
@@ -27,7 +29,7 @@
27
29
  | `panels/performance.js` | Performance + Memory panels — `perfState`, `handlePerfEvent`, `handleMemoryEvent`, `initMemoryPanel` |
28
30
  | `panels/native.js` | Native Logs panel — `_nativeState`, `initNativeLogsPanel`, `_appendNativeLog` |
29
31
  | `panels/react.js` | React Tree panel — just a connect button |
30
- | `panels/sources.js` | Sources panel — Metro source file browser |
32
+ | `panels/sources.js` | Sources panel — Metro source file browser (NOT initialized by default — no `panel-sources` in HTML) |
31
33
  | `init.js` | **Boot script** (loaded LAST) — IPC wiring, button handlers, memory monitor, settings apply, panel init calls |
32
34
 
33
35
  ### Other
@@ -40,7 +42,7 @@
40
42
 
41
43
  ### Script Load Order (critical)
42
44
  ```
43
- 1. app.js — state, $, esc, ts, clearAll, freeMemory (no panel dependencies)
45
+ 1. app.js — state, $, esc, ts, clearAll, freeMemory (no panel dependencies)
44
46
  2. panels/settings.js — TAB_CONFIG, isTabEnabled, localStorage helpers (used by all panels)
45
47
  3. panels/console.js — createTreeNode, showContextMenu, addConsoleLog (used by other panels)
46
48
  4. panels/network.js — depends on: console.js (showToast, showContextMenu, createTreeNode)
@@ -56,120 +58,119 @@
56
58
 
57
59
  ---
58
60
 
59
- ## Critical Rule: Do NOT Break Other Panels
61
+ ## Current Working State (v1.6.10)
60
62
 
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
+ > **This section documents exactly what works. Any change MUST preserve all of this.**
63
64
 
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.
65
+ ### Panel Status
66
+
67
+ | Panel | Status | Init Function | File | DOM ID |
68
+ |-------|--------|---------------|------|--------|
69
+ | Console | WORKING | `initConsolePanel()` | `panels/console.js` | `panel-console` |
70
+ | Network | WORKING | `initNetworkPanel()` | `panels/network.js` | `panel-network` |
71
+ | Redux | WORKING | `initReduxPanel()` | `panels/redux.js` | `panel-redux` |
72
+ | GA4 Events | WORKING | `initGA4Panel()` | `panels/ga4.js` | `panel-ga4` |
73
+ | AsyncStorage | WORKING | `initStoragePanel()` | `panels/storage.js` | `panel-storage` |
74
+ | Performance | WORKING | `initPerformancePanel()` | `panels/performance.js` | `panel-performance` |
75
+ | Memory | WORKING | `initMemoryPanel()` | `panels/performance.js` | `panel-memory` |
76
+ | Native Logs | WORKING | `initNativeLogsPanel()` | `panels/native.js` | `panel-native` |
77
+ | React Tree | WORKING | `initReactPanel()` | `panels/react.js` | `panel-react` |
78
+ | Settings | WORKING | `initSettingsPanel()` | `panels/settings.js` | `panel-settings` |
79
+ | Sources | NOT INITIALIZED | `initSourcesPanel()` | `panels/sources.js` | NO DOM element — `panel-sources` does NOT exist in `index.html` |
80
+
81
+ ### Init Sequence (in `init.js`)
82
+ ```
83
+ initConsolePanel();
84
+ initNetworkPanel();
85
+ initGA4Panel();
86
+ initPerformancePanel();
87
+ initMemoryPanel();
88
+ initReduxPanel();
89
+ initStoragePanel();
90
+ initReactPanel();
91
+ initNativeLogsPanel();
92
+ initSettingsPanel();
93
+ ```
94
+ **`initSourcesPanel()` is NOT called — intentional. No DOM element exists for it.**
95
+
96
+ ### IPC Channels Currently Registered (in `init.js`)
97
+ All inside `if (window.electronAPI) { }` block:
98
+ ```
99
+ ports, cdp-targets, redux-event, network-event, storage-event,
100
+ console-event, ga4-event, perf-event, clear-all-ui,
101
+ device-all-disconnected, redux-connected, network-connected,
102
+ storage-connected, react-dt-status, focus-search, app-version,
103
+ update-available, update-downloaded, trigger-open-cdp, theme-changed
104
+ ```
105
+ Native panel registers `native-log` and `native-status` inside `initNativeLogsPanel()`.
106
+
107
+ ### SDK Platform Detection
108
+ The SDK (`sdk/RNDebugSDK.js`) auto-detects the platform at runtime:
109
+ - **Android emulator** → `10.0.2.2` (requires `adb reverse` on ports 9090, 9091, 9092, 8097)
110
+ - **iOS simulator** → `127.0.0.1`
111
+ - **Real device** → Set `HOST_OVERRIDE` in SDK to Mac's LAN IP
112
+ - Detection uses `require('react-native').Platform.OS`
113
+
114
+ ### Setup Script (`bin/setup.js`) Behavior
115
+ 1. Copies `sdk/RNDebugSDK.js` → user's `src/debug/RNDebugSDK.js`
116
+ 2. Patches `index.js` to import SDK
117
+ 3. Store file detection order:
118
+ - Step 1: Common directories (`src/store/`, `src/redux/`, etc.) with filenames `store.*`, `index.*`
119
+ - Step 2: Root app files (`src/App.tsx`, `src/App.js`, `App.tsx`, `App.js`)
120
+ - Step 3: Deep recursive scan for files with `createStore(` or `configureStore(` call syntax
121
+ 4. Auto-patches RTK `configureStore` (adds middleware field)
122
+ 5. Auto-patches legacy `createStore` if `const middleware = [...]` pattern found
123
+ 6. Falls back to manual instructions if auto-patch fails
124
+ 7. Runs `adb reverse` for Android ports
125
+ 8. Only sets `HOST_OVERRIDE` for real device LAN IP; emulator/simulator uses auto-detect
126
+
127
+ ### Redux Integration Requirements
128
+ - Connection to port 9090 ("RN app connected") does NOT mean events are flowing
129
+ - `reduxMiddleware` or `reduxEnhancer` MUST be wired into the store
130
+ - Events only flow when `store.dispatch()` goes through the middleware/enhancer
131
+ - Thunk/function actions are safely serialized as `{type: "[Function: thunk]"}`
132
+ - State > 1MB is truncated to `{__truncated: true, sizeBytes, keys}`
70
133
 
71
134
  ---
72
135
 
73
- ## PanelsOwnership & State
136
+ ## Critical Rules DO NOT VIOLATE
74
137
 
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`
138
+ ### 1. Every init function MUST have a null guard
139
+ ```js
140
+ function initXxxPanel() {
141
+ const panel = $('panel-xxx');
142
+ if (!panel) return; // ← REQUIRED prevents crash if DOM element is missing
143
+ panel.innerHTML = `...`;
144
+ }
145
+ ```
89
146
 
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`
147
+ ### 2. Redux arrays MUST stay in sync
148
+ `state.redux.actions` and `state.redux.states` MUST have the same length.
149
+ Never empty one without emptying the other. In `freeMemory()`, always trim both together.
115
150
 
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
151
+ ### 3. IPC channels MUST be in preload allowlist
152
+ Every new IPC channel MUST be added to the `allowed` array in `preload.js`.
153
+ If a channel is not in this array, the listener is **silently dropped** — no error, no warning.
168
154
 
169
- ---
155
+ ### 4. IPC listeners MUST NOT be registered twice
156
+ The preload `on()` method calls `removeAllListeners(channel)` before adding.
157
+ Registering the same channel twice **kills the first listener**.
158
+ ALL IPC listeners go in `init.js` inside the `if (window.electronAPI) {}` block — nowhere else.
170
159
 
171
- ## Shared UtilitiesDO NOT MODIFY without checking all callers
160
+ ### 5. CSS variables use ONLY defined variables
161
+ Themes define: `--bg`, `--bg2`, `--bg3`, `--bg4` (NOT `--bg1`)
162
+ `--text`, `--text-mid`, `--text-dim`, `--text-bright`
163
+ `--accent`, `--accent2`, `--border`, `--border2`
164
+ `--green`, `--yellow`, `--red`
165
+ **Never use `--bg1`** — it resolves to transparent/empty.
172
166
 
167
+ ### 6. clearAll() and freeMemory() — check ALL panels
168
+ - `clearAll()` wipes everything + re-renders — used for Cmd+K
169
+ - `freeMemory()` trims heavy data without clearing UI — used on disconnect/quit
170
+ - After modifying either, verify ALL panels still render correctly
171
+ - Cancel pending `requestAnimationFrame` IDs before clearing data
172
+
173
+ ### 7. Shared functions — check ALL callers before modifying
173
174
  | Function | Used By |
174
175
  |----------|---------|
175
176
  | `$(id)` | Every panel |
@@ -178,75 +179,76 @@ break another. Before making any change, check:
178
179
  | `collectEntries(val)` | `objPreview()`, `createTreeNode()` |
179
180
  | `objPreview(val)` | `createTreeNode()` |
180
181
  | `createTreeNode(key, val, collapsed)` | Console, Network, Redux, Storage, GA4 |
181
- | `createPrimitiveSpan(val)` | `createTreeNode()`, `renderConsoleArg()` |
182
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 |
183
+ | `addConsoleLog(entry)` | Console IPC, Redux (`handleReduxEvent`), Network (error toasts) |
186
184
  | `isTabEnabled(tabId)` | Redux, GA4, Storage, Native, Performance |
187
185
  | `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 |
186
+
187
+ ### 8. package.json `files` field MUST include all runtime files
188
+ Current required entries:
189
+ ```json
190
+ "files": ["main.js", "preload.js", "index.html", "app.js", "init.js", "panels/", "styles.css", "sdk/", "bin/", "assets/", "AGENTS.md"]
191
+ ```
192
+ **If you add a new file, add it here or it won't be published to npm.**
191
193
 
192
194
  ---
193
195
 
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 |
196
+ ## Panels Ownership & State
244
197
 
245
- ---
198
+ ### Console Panel (`panels/console.js`)
199
+ - **State:** `state.console.logs`, `state.console.levelFilters`, `state.console.searchFilter`, `state.console.showRedux`
200
+ - **Private state:** `_consolePending`, `_consoleRAF`, `_lastLogMsg`, `_lastLogRow`, `_lastLogCount`
201
+ - **IPC:** `console-event` → `addConsoleLog()`
202
+ - **Cross-panel:** Called by Redux (`handleReduxEvent` → `addConsoleLog`)
203
+ - **Constants:** `MAX_CONSOLE_LOGS = 5000`
246
204
 
247
- ## Main Process (main.js) — Structure
205
+ ### Network Panel (`panels/network.js`)
206
+ - **State:** `state.network.requests`, `state.network.order`, `state.network.selectedId`, `statusFilter`, `typeFilter`, `searchFilter`, `sortCol`, `sortDir`
207
+ - **Private state:** `_netRAF`
208
+ - **IPC:** `network-event` → `handleNetworkEvent()`
209
+ - **Cross-panel:** Calls `addConsoleLog()`, `showToast()`, `showContextMenu()`
248
210
 
249
- ### WebSocket Bridges
211
+ ### Redux Panel (`panels/redux.js`)
212
+ - **State:** `state.redux.actions`, `state.redux.states` (MUST be same length), `state.redux.selected`
213
+ - **IPC:** `redux-event` → `handleReduxEvent()`
214
+ - **Cross-panel:** Calls `addConsoleLog()` to mirror actions in console
215
+ - **Constants:** `MAX_REDUX_HISTORY = 500`
216
+
217
+ ### GA4 Events Panel (`panels/ga4.js`)
218
+ - **State:** `ga4State` standalone object
219
+ - **IPC:** `ga4-event` → `handleGA4Event()`
220
+
221
+ ### AsyncStorage Panel (`panels/storage.js`)
222
+ - **State:** `state.storage.entries`, `state.storage.keys`, `state.storage.selected`
223
+ - **Private state:** `_storageRAF`
224
+ - **IPC:** `storage-event` → `handleStorageEvent()`
225
+
226
+ ### Performance Panel (`panels/performance.js`)
227
+ - **State:** `perfState` standalone object
228
+ - **IPC:** `perf-event` → `handlePerfEvent()`
229
+
230
+ ### Memory Panel (`panels/performance.js`)
231
+ - **IPC:** `perf-event` → `handleMemoryEvent()` (shared channel with Performance)
232
+
233
+ ### Native Logs Panel (`panels/native.js`)
234
+ - **State:** `_nativeState` standalone object
235
+ - **IPC:** `native-log`, `native-status` (registered inside `initNativeLogsPanel`, NOT in init.js)
236
+ - **Constants:** `MAX_NATIVE_LOGS = 2000`
237
+
238
+ ### Settings Panel (`panels/settings.js`)
239
+ - **State:** All localStorage accessors
240
+ - **Shared utilities:** `TAB_CONFIG`, `isTabEnabled()`, `applyTheme()`, `getTabVisibility()`, `applyTabVisibility()`
241
+
242
+ ### React Tree Panel (`panels/react.js`)
243
+ - Minimal — just a connect button for React DevTools
244
+
245
+ ### Sources Panel (`panels/sources.js`)
246
+ - **NOT INITIALIZED** — `panel-sources` does not exist in `index.html`
247
+ - `initSourcesPanel()` has a null guard and returns early
248
+
249
+ ---
250
+
251
+ ## WebSocket Bridges (main.js)
250
252
 
251
253
  | Port | Name | Client Set | Events Carried |
252
254
  |------|------|------------|----------------|
@@ -260,82 +262,103 @@ If a channel is not in this array, the listener is silently dropped — no error
260
262
  2. Destroy `devtoolsWindow`
261
263
  3. Close `reactDTServer` + clients
262
264
  4. Close all bridge servers + clients
263
- 5. Kill `_nativeLogProcess` (second `before-quit` handler inside `setupIPC`)
265
+ 5. Kill `_nativeLogProcess`
264
266
 
265
267
  ---
266
268
 
267
269
  ## Rules for Making Changes
268
270
 
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
271
+ ### Adding a new panel
272
+ 1. Create `panels/newpanel.js` with `function initNewPanel() { const panel = $('panel-new'); if (!panel) return; ... }`
273
+ 2. Add `<div id="panel-new" class="panel"></div>` to `index.html`
274
+ 3. Add `<script src="panels/newpanel.js"></script>` to `index.html` BEFORE `init.js`
275
+ 4. Add `initNewPanel();` to `init.js` init sequence
276
+ 5. Add to `TAB_CONFIG` array in `panels/settings.js`
277
+ 6. Add state to `clearAll()` and `freeMemory()` in `app.js`
278
+ 7. Add case to `clearActiveTab()` in `app.js`
279
+
280
+ ### Adding a new IPC channel
281
+ 1. Add `ipcMain.on/handle` in `main.js` `setupIPC()`
282
+ 2. Add channel name to `preload.js` allowed array
283
+ 3. Add `window.electronAPI.on()` in `init.js` inside the `if (window.electronAPI)` block
284
+ 4. **Test:** Verify the listener fires
285
+
286
+ ### Modifying the SDK
287
+ 1. Test on BOTH Android emulator AND iOS simulator
288
+ 2. Verify `adb reverse` works for Android
289
+ 3. Test hot reload — ensure no timer/WebSocket leaks
290
+ 4. Test with Redux Thunk actions (function dispatches)
291
+ 5. Test with large Redux state (>1MB)
292
+ 6. Test with binary network responses (images, videos)
293
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
294
+ ---
298
295
 
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
296
+ ## Pre-Release Checklist
305
297
 
306
- ---
298
+ > **MANDATORY — Do NOT publish without passing ALL checks.**
307
299
 
308
- ## SDK Integration (in user's React Native app)
300
+ ### Automated Checks (run these commands)
301
+ ```bash
302
+ # 1. Syntax check all files
303
+ for f in app.js init.js main.js preload.js panels/*.js sdk/RNDebugSDK.js bin/setup.js; do
304
+ node -c "$f" || echo "FAIL: $f"
305
+ done
309
306
 
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
307
+ # 2. No duplicate functions
308
+ grep -rhn "^function [a-zA-Z_]" app.js init.js panels/*.js | sed 's/(.*//' | sort | uniq -c | sort -rn | awk '$1 > 1'
313
309
 
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)
310
+ # 3. No duplicate top-level variables
311
+ grep -rhn "^const [a-zA-Z_]\|^let [a-zA-Z_]" app.js init.js panels/*.js | sed 's/ =.*//' | sort | uniq -c | sort -rn | awk '$1 > 1'
319
312
 
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
313
+ # 4. All critical functions exist
314
+ for fn in handleReduxEvent handleNetworkEvent handleStorageEvent handleGA4Event handlePerfEvent handleMemoryEvent clearAll freeMemory updateDeviceBanner initConsolePanel initNetworkPanel initGA4Panel initPerformancePanel initMemoryPanel initReduxPanel initStoragePanel initReactPanel initNativeLogsPanel initSettingsPanel addConsoleLog renderConsole renderNetwork renderRedux renderStorage renderGA4List closeNetDetail clearPerfCanvas showContextMenu createTreeNode isTabEnabled formatSize showToast takeScreenshot collectEntries _applyUpdateBanner _showChangelog; do
315
+ found=$(grep -rln "function $fn\b" app.js init.js panels/*.js 2>/dev/null | head -1)
316
+ [ -z "$found" ] && echo "MISSING: $fn"
317
+ done
318
+
319
+ # 5. npm pack includes all files
320
+ npm pack --dry-run 2>&1 | grep -c "panels/\|init.js\|sdk/\|bin/"
321
+ # Expected: 15+ files
322
+
323
+ # 6. IPC channels all in preload allowlist
324
+ # Every channel registered in init.js must be in preload.js allowed array
325
+ ```
326
+
327
+ ### Manual Checks (test in the running app)
328
+ - [ ] **App launches** — `npm start` opens the window, no blank screen
329
+ - [ ] **Console** — logs appear, level filters work, search works, Cmd+K clears
330
+ - [ ] **Network** — requests appear, detail panel opens/closes, search works, Cmd+K clears
331
+ - [ ] **Redux** — actions appear, state diff shows correctly, Cmd+K clears, works after disconnect/reconnect
332
+ - [ ] **GA4** — events appear, summary tab works, color toggle works
333
+ - [ ] **Storage** — keys appear, values render, search works
334
+ - [ ] **Performance** — Record button toggles, graphs render when data arrives
335
+ - [ ] **Memory** — heap values display when device connected
336
+ - [ ] **Native Logs** — Connect button works for Android/iOS, logs stream, Cmd+K clears
337
+ - [ ] **Settings** — theme switch works, font size changes, tab visibility toggles, version history loads
338
+ - [ ] **React Tree** — connect button shows
339
+ - [ ] **Device disconnect** — `freeMemory()` fires after 3s, panels still work after reconnect
340
+ - [ ] **Device reconnect** — cancel disconnect timer, new events flow immediately
341
+ - [ ] **Cmd+K** — clears active tab data (test on each tab)
342
+ - [ ] **All panels render** — switch through every tab, none show blank/white screen
343
+
344
+ ### SDK Checks (test in RN app)
345
+ - [ ] **iOS simulator** — SDK connects with `127.0.0.1`, console/network/storage data flows
346
+ - [ ] **Android emulator** — SDK connects with `10.0.2.2` (after `adb reverse`), all data flows
347
+ - [ ] **Redux** — actions appear in Redux tab AND console (if showRedux enabled)
348
+ - [ ] **Hot reload** — reload RN app, SDK reconnects, no duplicate data/timers
349
+
350
+ ### Setup Script Checks
351
+ - [ ] `npx reactoradar setup` — copies SDK, patches index.js, detects store file
352
+ - [ ] Redux auto-patch — adds `reduxMiddleware` to middleware array
353
+ - [ ] `npx reactoradar remove` — cleans up SDK and patches
354
+ - [ ] `adb reverse` — ports forwarded for Android
324
355
 
325
356
  ---
326
357
 
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
358
+ ## Known Limitations
359
+
360
+ 1. **Sources panel** — `panel-sources` div does not exist in `index.html`. `initSourcesPanel()` is not called. Panel is dormant.
361
+ 2. **`npx reactoradar setup` store detection** if store is in an unusual file (not `App.tsx`, not `store.*`), setup may not find it
362
+ 3. **iOS real device** requires manually setting `HOST_OVERRIDE` in the SDK
363
+ 4. **`adb reverse` drops** if Android emulator restarts, `adb reverse` must be re-run
364
+ 5. **Performance `jsThread` metric** currently uses `performance.now() % 16.67` which is approximate
package/bin/setup.js CHANGED
@@ -181,6 +181,7 @@ function findStoreFile(projectDir) {
181
181
  ];
182
182
  const storeNames = ['store.ts', 'store.js', 'store.tsx', 'store.jsx', 'index.ts', 'index.js', 'index.tsx'];
183
183
 
184
+ // 1. Check common store file locations
184
185
  for (const dir of searchDirs) {
185
186
  for (const name of storeNames) {
186
187
  const p = path.join(projectDir, dir, name);
@@ -193,18 +194,32 @@ function findStoreFile(projectDir) {
193
194
  }
194
195
  }
195
196
 
196
- // Deep search: recursively find any file with configureStore/createStore
197
+ // 2. Check App.tsx / App.js (common pattern: store created in root component)
198
+ const rootAppFiles = ['src/App.tsx', 'src/App.js', 'App.tsx', 'App.js', 'app/App.tsx', 'app/App.js'];
199
+ for (const f of rootAppFiles) {
200
+ const p = path.join(projectDir, f);
201
+ if (fileExists(p)) {
202
+ const content = fs.readFileSync(p, 'utf8');
203
+ if (content.includes('configureStore') || content.includes('createStore')) {
204
+ return f;
205
+ }
206
+ }
207
+ }
208
+
209
+ // 3. Deep search: recursively find files with actual createStore()/configureStore() calls
210
+ // Skip files that merely reference configureStore as a parameter/type name
197
211
  try {
198
212
  const glob = (dir, depth) => {
199
213
  if (depth > 4) return null;
200
214
  let entries;
201
215
  try { entries = fs.readdirSync(path.join(projectDir, dir), { withFileTypes: true }); } catch { return null; }
202
216
  for (const e of entries) {
203
- if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
217
+ if (e.name === 'node_modules' || e.name.startsWith('.') || e.name === 'debug') continue;
204
218
  const rel = path.join(dir, e.name);
205
- if (e.isFile() && /\.(ts|js|tsx|jsx)$/.test(e.name) && /store/i.test(e.name)) {
219
+ if (e.isFile() && /\.(ts|js|tsx|jsx)$/.test(e.name)) {
206
220
  const content = fs.readFileSync(path.join(projectDir, rel), 'utf8');
207
- if (content.includes('configureStore') || content.includes('createStore')) {
221
+ // Must contain actual call: configureStore({ or createStore( — not just the word as a type/param
222
+ if (/configureStore\s*\(/.test(content) || /createStore\s*\(/.test(content)) {
208
223
  return rel;
209
224
  }
210
225
  }
@@ -265,12 +280,20 @@ async function install(projectDir) {
265
280
  process.exit(1);
266
281
  }
267
282
 
268
- // Read SDK, patch HOST
283
+ // Read SDK, patch HOST_OVERRIDE for real device support
284
+ // The SDK auto-detects Android emulator vs iOS simulator at runtime.
285
+ // For real devices, we set HOST_OVERRIDE to the Mac's LAN IP.
269
286
  let sdkContent = fs.readFileSync(sdkSrc, 'utf8');
270
- sdkContent = sdkContent.replace(
271
- /const HOST = '[^']+';/,
272
- `const HOST = '${host}';`
273
- );
287
+ const isRealDevice = reason.includes('device') || reason.includes('LAN IP');
288
+ if (isRealDevice && host !== '127.0.0.1' && host !== '10.0.2.2') {
289
+ sdkContent = sdkContent.replace(
290
+ /const HOST_OVERRIDE = null;/,
291
+ `const HOST_OVERRIDE = '${host}';`
292
+ );
293
+ log('HOST_OVERRIDE set to', C.bold + host + C.reset, '(real device LAN IP)');
294
+ } else {
295
+ log('HOST auto-detect enabled', C.dim + '(Android: 10.0.2.2, iOS: 127.0.0.1)' + C.reset);
296
+ }
274
297
  try {
275
298
  fs.writeFileSync(sdkDest, sdkContent);
276
299
  } catch (e) {
@@ -383,12 +406,51 @@ ${SDK_MARKER_END}
383
406
  warn('Could not auto-patch', storeFile, '— wire Redux manually');
384
407
  }
385
408
  }
386
- } else if (storeContent.includes('createStore')) {
387
- warn('Legacy createStore found at', C.bold + storeFile + C.reset);
388
- console.log(C.dim + ' Add manually:' + C.reset);
389
- console.log(C.dim + ` import { reduxEnhancer } from '${relSDK}';` + C.reset);
390
- console.log(C.dim + ' const store = createStore(reducer, __DEV__ ? reduxEnhancer : undefined);' + C.reset);
391
- }
409
+ } else if (storeContent.includes('createStore')) {
410
+ // Legacy createStore try to auto-patch by adding reduxMiddleware to middleware array
411
+ let patched = storeContent;
412
+ let didPatch = false;
413
+
414
+ // Pattern 1: middleware array exists — push reduxMiddleware into it
415
+ // e.g. const middleware = []; or const middleware = [sagaMiddleware];
416
+ if (/(?:const|let|var)\s+middleware\s*=\s*\[/.test(storeContent) && !storeContent.includes('reduxMiddleware') && !storeContent.includes('RNDebugSDK')) {
417
+ // Add the require + push after the middleware array declaration
418
+ patched = patched.replace(
419
+ /((?:const|let|var)\s+middleware\s*=\s*\[[^\]]*\];?)/,
420
+ `$1\n if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); if (reduxMiddleware) middleware.push(reduxMiddleware); } catch {} }`
421
+ );
422
+ if (patched !== storeContent) {
423
+ didPatch = true;
424
+ }
425
+ }
426
+
427
+ // Pattern 2: applyMiddleware(...middleware) exists but no middleware array — inject inline
428
+ if (!didPatch && storeContent.includes('applyMiddleware') && !storeContent.includes('reduxMiddleware') && !storeContent.includes('RNDebugSDK')) {
429
+ // Find the if (__DEV__) block near middleware setup and add there
430
+ // Or add before the createStore call
431
+ const createStoreMatch = storeContent.match(/createStore\s*\(/);
432
+ if (createStoreMatch) {
433
+ const idx = storeContent.indexOf(createStoreMatch[0]);
434
+ const insertPoint = storeContent.lastIndexOf('\n', idx);
435
+ if (insertPoint > 0) {
436
+ patched = storeContent.slice(0, insertPoint) +
437
+ `\n if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); if (reduxMiddleware) middleware.push(reduxMiddleware); } catch {} }` +
438
+ storeContent.slice(insertPoint);
439
+ if (patched !== storeContent) didPatch = true;
440
+ }
441
+ }
442
+ }
443
+
444
+ if (didPatch) {
445
+ fs.writeFileSync(storePath, patched);
446
+ log('Patched', C.bold + storeFile + C.reset, '— Redux middleware wired (legacy createStore)');
447
+ } else {
448
+ warn('Legacy createStore found at', C.bold + storeFile + C.reset);
449
+ console.log(C.dim + ' Could not auto-patch. Add manually:' + C.reset);
450
+ console.log(C.dim + ` if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); middleware.push(reduxMiddleware); } catch {} }` + C.reset);
451
+ console.log(C.dim + ' Add this BEFORE the createStore() call in your middleware setup.' + C.reset);
452
+ }
453
+ }
392
454
  } else {
393
455
  warn('Redux detected but store file not found automatically');
394
456
  console.log(C.dim + ' Add to your store setup:' + C.reset);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactoradar",
3
3
  "productName": "ReactoRadar",
4
- "version": "1.6.8",
4
+ "version": "1.6.10",
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
@@ -19,8 +19,20 @@ if (typeof __DEV__ === 'undefined' || !__DEV__) {
19
19
  } else {
20
20
 
21
21
  // ─── Config ───────────────────────────────────────────────────────────────────
22
- // Android emulator → 10.0.2.2 | iOS sim → 127.0.0.1 | Device → your LAN IP
23
- const HOST = '10.0.2.2';
22
+ // Auto-detect platform: Android emulator → 10.0.2.2 | iOS sim → 127.0.0.1
23
+ // For real devices: Android uses adb reverse (so 10.0.2.2 works via port forwarding),
24
+ // iOS real device needs the Mac's LAN IP — override HOST_OVERRIDE below if needed.
25
+ const HOST_OVERRIDE = null; // Set to your Mac's LAN IP for iOS real device, e.g. '192.168.1.100'
26
+
27
+ function _detectHost() {
28
+ if (HOST_OVERRIDE) return HOST_OVERRIDE;
29
+ try {
30
+ const { Platform } = require('react-native');
31
+ if (Platform.OS === 'android') return '10.0.2.2';
32
+ return '127.0.0.1'; // iOS simulator
33
+ } catch { return '127.0.0.1'; }
34
+ }
35
+ const HOST = _detectHost();
24
36
 
25
37
  const PORTS = {
26
38
  NETWORK_AND_CONSOLE: 9092, // unified feed for network + console