Commit 0713f3a
apps/box: fix Diagnostics listActivePlugins infinite loop
The Diagnostics screen kept the device + go-fula busy in a recursive
listActivePlugins → store-update → useEffect-refire → listActivePlugins
loop, visible as 2x "listActivePlugins in react-native started" per
tick on the app console and eventual crash.
Root cause is a two-layer trap:
1. Diagnostics.screen.tsx had a single useEffect that both CALLED
listActivePlugins() AND depended on activePlugins. Each call updates
the store, which re-triggers the effect. The lint suppression and
"stable enough" comment masked the bug.
2. usePluginsStore.listActivePlugins did set({ activePlugins: [] })
for both the empty-list case AND the null-msg case (the latter
happens whenever go-fula's active-plugins.txt is empty: its
readActivePlugins returns [""], the listActivePluginsImpl filters
out empty strings leaving nil, which JSON-marshals to null). Every
call created a fresh [] reference, and zustand's shallow equality
flagged that as a change. So even consumers without the latent
effect bug would re-render unnecessarily.
Why this surfaced now: the prior commit (cbca913, Loyal Agent tab
swap) promoted DiagnosticsScreen from a Settings stack child to a
top-level tab. The bug existed before but only fired when a user
manually navigated to Settings then Diagnostics. As a top-level tab
the screen mounts on app open, and the loop kicks off immediately.
Fix:
Diagnostics.screen.tsx — split into two effects so the fetcher does
not depend on what it sets:
- Effect A: listActivePlugins() once on mount, flips hasFetched true
in finally().
- Effect B: derives pluginPresence from activePlugins whenever it
changes, gated on hasFetched so the initial empty-store state does
not flash a one-frame "Blox AI not installed" before the response
lands.
usePluginsStore.ts — defense-in-depth reference-stable update. Only
set if the new contents differ from the old. Protects any future
consumer that depends on activePlugins by reference, not just
Diagnostics.
Audited other activePlugins consumers (Plugin.screen.tsx,
GlobalBottomSheet.tsx) — neither has a useEffect that both calls
listActivePlugins AND depends on activePlugins, so neither had this
specific bug. Plugin.screen.tsx's effects depend on listActivePlugins
the setter (referentially stable from zustand), not on activePlugins
the state. Store dedup still helps them avoid spurious re-renders.
Tests: 23/23 Diagnostics jest tests pass. Pre-existing
ApprovalModal.test.tsx module-resolve failure is unrelated (jest
moduleNameMapper infra; ApprovalModal not touched).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>1 parent cbca913 commit 0713f3a
2 files changed
Lines changed: 64 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
97 | 112 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
| 113 | + | |
| 114 | + | |
103 | 115 | | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
122 | 138 | | |
123 | 139 | | |
124 | 140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
85 | 100 | | |
86 | 101 | | |
87 | 102 | | |
88 | 103 | | |
89 | 104 | | |
90 | | - | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
91 | 114 | | |
92 | 115 | | |
93 | 116 | | |
| |||
0 commit comments