@atlassian/chippy 0.0.0-stage → 0.0.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +276 -2
  3. package/bin/chippy.mjs +92 -0
  4. package/bin/pack.mjs +204 -0
  5. package/bin/skill-dir.mjs +15 -0
  6. package/lib/avalanche.js +2601 -0
  7. package/lib/avalanche.js.map +7 -0
  8. package/lib/bootstrap.d.ts +1 -0
  9. package/lib/bootstrap.js +70 -0
  10. package/lib/bootstrap.js.map +7 -0
  11. package/lib/browser.js +15 -0
  12. package/lib/browser.js.map +7 -0
  13. package/lib/extension/avalanche.es.js +5535 -0
  14. package/lib/extension/avalanche.es.js.map +1 -0
  15. package/lib/extension/chippy-page-bridge.js +1953 -0
  16. package/lib/extension/inspect-react.js +216 -0
  17. package/lib/extension/layout-lab.js +1453 -0
  18. package/lib/extension/panel-dom.js +90 -0
  19. package/lib/extension/resolve-probes.js +342 -0
  20. package/lib/mcp-skill/LICENSE +201 -0
  21. package/lib/mcp-skill/README.md +148 -0
  22. package/lib/mcp-skill/SKILL.md +335 -0
  23. package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
  24. package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
  25. package/lib/mcp-skill/extension/armed-reload-startup.js +83 -0
  26. package/lib/mcp-skill/extension/avalanche.es.js +5535 -0
  27. package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
  28. package/lib/mcp-skill/extension/avalanche.iife.js +5536 -0
  29. package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
  30. package/lib/mcp-skill/extension/background.js +325 -0
  31. package/lib/mcp-skill/extension/chippy-page-bridge.js +1953 -0
  32. package/lib/mcp-skill/extension/content-script.js +190 -0
  33. package/lib/mcp-skill/extension/devtools-hook-shim.js +85 -0
  34. package/lib/mcp-skill/extension/early-bootstrap.js +49 -0
  35. package/lib/mcp-skill/extension/icon-128.png +0 -0
  36. package/lib/mcp-skill/extension/icon-16.png +0 -0
  37. package/lib/mcp-skill/extension/icon-32.png +0 -0
  38. package/lib/mcp-skill/extension/icon-48.png +0 -0
  39. package/lib/mcp-skill/extension/inspect-react.js +216 -0
  40. package/lib/mcp-skill/extension/layout-lab.js +1453 -0
  41. package/lib/mcp-skill/extension/logo.svg +14 -0
  42. package/lib/mcp-skill/extension/manifest.json +69 -0
  43. package/lib/mcp-skill/extension/panel-dom.js +90 -0
  44. package/lib/mcp-skill/extension/panel-settings.js +173 -0
  45. package/lib/mcp-skill/extension/popup.html +41 -0
  46. package/lib/mcp-skill/extension/popup.js +80 -0
  47. package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
  48. package/lib/mcp-skill/extension/resolve-probes.js +342 -0
  49. package/lib/mcp-skill/extension/startup-handoff.js +30 -0
  50. package/lib/mcp-skill/package.json +1 -0
  51. package/lib/mcp-skill/references/README.md +56 -0
  52. package/lib/mcp-skill/references/active-stimulus.md +85 -0
  53. package/lib/mcp-skill/references/avalanche-detector.md +731 -0
  54. package/lib/mcp-skill/references/bdd-router.md +310 -0
  55. package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
  56. package/lib/mcp-skill/references/command-output-formats.md +1085 -0
  57. package/lib/mcp-skill/references/command-surface.md +183 -0
  58. package/lib/mcp-skill/references/cynefin.md +99 -0
  59. package/lib/mcp-skill/references/debug-hook.md +161 -0
  60. package/lib/mcp-skill/references/fixing-performance.md +490 -0
  61. package/lib/mcp-skill/references/hook-metrics.md +199 -0
  62. package/lib/mcp-skill/references/hooks-inspection.md +61 -0
  63. package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
  64. package/lib/mcp-skill/references/mcp.md +122 -0
  65. package/lib/mcp-skill/references/mode-a-injection.md +102 -0
  66. package/lib/mcp-skill/references/operating-model.md +128 -0
  67. package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
  68. package/lib/mcp-skill/references/playwright-probes.md +205 -0
  69. package/lib/mcp-skill/references/procedures.md +545 -0
  70. package/lib/mcp-skill/references/recipes.txt +114 -0
  71. package/lib/mcp-skill/references/stability-render.md +428 -0
  72. package/lib/mcp-skill/references/test-mode-playbook.md +194 -0
  73. package/lib/mcp-skill/references/troubleshooting.md +59 -0
  74. package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
  75. package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
  76. package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
  77. package/lib/mcp-skill/scripts/chippy-helper-server.js +1768 -0
  78. package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
  79. package/lib/mcp-skill/scripts/chippy.sh +1252 -0
  80. package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
  81. package/lib/mcp-skill/scripts/helper-access.js +158 -0
  82. package/lib/mcp-skill/scripts/helper-proxy.js +87 -0
  83. package/lib/mcp-skill/scripts/page-socket-trust.js +81 -0
  84. package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
  85. package/lib/mcp-skill/scripts/search-recipes.js +248 -0
  86. package/lib/mcp-skill/snippets/README.md +89 -0
  87. package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
  88. package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
  89. package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
  90. package/lib/mcp-skill/snippets/find-prop.js +83 -0
  91. package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
  92. package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
  93. package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
  94. package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
  95. package/lib/package.json +1 -0
  96. package/lib/plugin.js +119 -0
  97. package/lib/plugin.js.map +7 -0
  98. package/lib/storybook/avalanche-panel-model.js +79 -0
  99. package/lib/storybook/brand.jsx +6 -0
  100. package/lib/storybook/connection-fallback.jsx +47 -0
  101. package/lib/storybook/constants.js +15 -0
  102. package/lib/storybook/context-card.jsx +171 -0
  103. package/lib/storybook/manager-owner.js +42 -0
  104. package/lib/storybook/manager.jsx +425 -0
  105. package/lib/storybook/preset.js +7 -0
  106. package/lib/storybook/preview.js +298 -0
  107. package/lib/storybook/probe-annotation-editor.jsx +52 -0
  108. package/lib/storybook/probe-intent.js +23 -0
  109. package/lib/storybook/setup.js +104 -0
  110. package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
  111. package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
  112. package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
  113. package/lib/types/avalanche/detector.d.ts +183 -0
  114. package/lib/types/avalanche/interaction-capture.d.ts +25 -0
  115. package/lib/types/avalanche/internals.d.ts +189 -0
  116. package/lib/types/avalanche/report-serializer.d.ts +283 -0
  117. package/lib/types/avalanche/reporter.d.ts +610 -0
  118. package/lib/types/chippy-bridge/fiber-source.d.ts +48 -0
  119. package/lib/types/chippy-bridge/react-renderer.d.ts +74 -0
  120. package/lib/types/chippy-bridge/recorded-source.d.ts +44 -0
  121. package/lib/types/chippy-bridge/source-map.d.ts +30 -0
  122. package/lib/types/debug-render/core.d.ts +78 -0
  123. package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
  124. package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
  125. package/lib/types/layout-lab/marker-context.d.ts +94 -0
  126. package/lib/types/package/avalanche.d.ts +6 -0
  127. package/lib/types/package/browser.d.ts +10 -0
  128. package/lib/types/package/plugin.d.ts +15 -0
  129. package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
  130. package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
  131. package/lib/types/plugins/index.d.ts +49 -0
  132. package/lib/types/plugins/jira/context-provider.d.ts +36 -0
  133. package/package.json +106 -4
@@ -0,0 +1,731 @@
1
+ # Avalanche detector
2
+
3
+ Avalanche works with standard React DOM. Install the DevTools hook before React
4
+ evaluates so the renderer registers and sends commit callbacks. A commit
5
+ metrics adapter can add host-supplied counters; the shipped bundle includes
6
+ the built-in ATL adapter for Atlassian's patched `react-dom`, off unless the
7
+ page opts in through the Chippy panel's settings or `enablePlugins(['atl'])`. Its metrics slot
8
+ may be seeded on an unpatched page: DevTools remains the capture owner until an
9
+ adapter commit callback actually fires. Adapter callbacks then provide
10
+ additional counters and own capture without a duplicate DevTools report,
11
+ whether the adapter callback arrives before or after DevTools. An
12
+ empty metrics object cannot establish that a patched host is active.
13
+
14
+
15
+ Avalanche is a React commit profiler injected alongside Chippy. It records sources, collateral renders, context waves, hook churn, and restart-heavy commits.
16
+
17
+ Works on any React product. Operates in two modes; the mode is observed at runtime, not declared:
18
+
19
+ - **`adapter:<id>`** — A registered commit metrics adapter drives commits. With the built-in ATL adapter (`adapter:atl`), patched `react-dom` (Atlassian products) drives `window.__ATL_REACT_METRICS`. Full fidelity: per-commit fiber walk **plus** render-counter delta math (restart counts, adapter counters, `metricsByCommit`). `report.meta.metricsAdapter` names the adapter. Captures from earlier releases report `atl` for `adapter:atl`.
20
+ - **`devtools`** — Any other React product. The detector chains into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` (defining the hook if absent, augmenting if present). Per-commit fiber walk is fully reconstructed; render-counter delta math is **unavailable** (no patch to read counters from).
21
+
22
+ Mode promotes from `devtools` → `adapter:<id>` the first time the adapter's commit callback (for ATL, `__ATL_REACT_METRICS.onCommitFiberRoot`) is actually invoked, never the other direction. Confirmed via `report.meta.integrationMode`.
23
+
24
+ ## What's available per mode
25
+
26
+ `recordTree()` runs identically in both modes — the React fiber tree is the same. Per-commit metrics that come from the fiber walk are real measurements in both modes:
27
+
28
+ | `commitStats[N]` index | Field | `adapter:<id>` | `devtools` |
29
+ |---|---|---|---|
30
+ | 0 | `collateral` | ✅ | ✅ |
31
+ | 1 | `sources` | ✅ | ✅ |
32
+ | 2 | `restarts` | ✅ real | ⚠ always 0 — semantically "unknown" |
33
+ | 3 | `performedWork` | ✅ | ✅ |
34
+ | 4 | `effects` | ✅ | ✅ |
35
+ | 5 | `memos` | ✅ | ✅ |
36
+ | 6 | `callbacks` | ✅ | ✅ |
37
+ | 7 | `contextUpdates` | ✅ | ✅ |
38
+ | 8 | `unmountedComponents` | ✅ | ✅ |
39
+ | 9 | `unmountedSubtrees` | ✅ | ✅ |
40
+ | — | `metricsByCommit` (deprecated alias `atlByCommit`) | ✅ present | ❌ omitted |
41
+
42
+ To disambiguate "zero restarts measured" from "restarts not measurable in this mode", consult `report.meta.dataAvailability.restarts` (and `.metricsByCommit`). When `false`, treat that field as missing data, not as a real zero.
43
+
44
+ Implication for analysis: source detection, blast radius, hook ancestry, hot-source attribution, amplifier discovery, context churn — **all work in both modes**. Restart-waste attribution requires an adapter mode such as `adapter:atl`.
45
+ Does not require: React DevTools UI, Profiler tab, or manual recording.
46
+
47
+ Cross-link: use [fixing-performance](fixing-performance.md) when deciding whether the fix belongs above the source, at the boundary, or below it.
48
+
49
+ ## When to use
50
+
51
+ - lag, jank, or "everything re-rendered"
52
+ - probe shows suspicious hook count but cause is unclear
53
+ - before/after a performance fix
54
+ - commit-by-commit diff on a saved capture
55
+
56
+ ## How capture works
57
+
58
+ Avalanche is mid-flight. Start from a live page, reload or interact, then read the capture.
59
+
60
+ Basic loop:
61
+ 1. get `clientId`
62
+ 2. arm reload if you need page-load commits
63
+ 3. interact or reload
64
+ 4. read `status`, `hot-sources`, `commit-list`, `commit-peek`
65
+ 5. save export if you need repeated analysis
66
+
67
+ First captured commit has no prior snapshot. Treat commit `2+` as stronger signal.
68
+
69
+ ## Commands
70
+
71
+ ### Runtime performance happy path
72
+
73
+ For a live interaction investigation, run these as **separate commands**. Do not combine hot sources, commit list, and commit peek in one shell command on real captures; output can truncate and hide the useful slice.
74
+
75
+ ```bash
76
+ chippy.sh avalanche-arm my-client http://localhost:9000
77
+ # user performs the slow interaction
78
+ chippy.sh avalanche-status my-client
79
+ chippy.sh avalanche-commit-list my-client 15 collateral
80
+ chippy.sh avalanche-commit-peek my-client 42 stats,sources,metrics 6 40
81
+ ```
82
+
83
+ Use `reload-with-avalanche` only for initial-load profiling. Runtime performance uses `avalanche-arm`, then the user/harness interacts, then read status/list/peek.
84
+
85
+ ### `avalanche-status <clientId>`
86
+ ```bash
87
+ chippy.sh avalanche-status my-client
88
+ ```
89
+ Look for `installed`, `hasMetricsGlobal`, `commitCount`.
90
+
91
+ ### `reload-with-avalanche <clientId> <origin> [maxCommits] [timeoutMs] [settleMs]`
92
+ ```bash
93
+ chippy.sh reload-with-avalanche my-client http://localhost:9000 0 15000 5000
94
+ ```
95
+ Arms, reloads, waits for reconnect, waits settle window, then prints capture status. With the extension or proxy mode, the reloaded document installs Avalanche before React's first commit, so boot commits are included. The reload step's ack reports `earlyStartup`: `registered` (extension), `handoff-only` (no extension; proxy mode still captures boot), or `not-armed`.
96
+
97
+ ### `avalanche-hot-sources <clientId> [limit]`
98
+ ```bash
99
+ chippy.sh avalanche-hot-sources my-client 20
100
+ ```
101
+ Top sources by collateral damage. Start here for a quick overview, but use `commit-list` + `commit-peek` for commit-level reasoning.
102
+
103
+ ### `avalanche-commit-list <clientId> [top] [by]`
104
+ ```bash
105
+ chippy.sh avalanche-commit-list my-client 15 collateral
106
+ chippy.sh avalanche-commit-list my-client 15 contextUpdates
107
+ ```
108
+ Ranks commits. Rows are intentionally compact. If an event/action breadcrumb is linked to the commit, `latestAction` is clue-only: action name, delta from previous commit, DOM tag/text/test id, target fiber name, `ownerStackKind`, closest 10 owner/parent names, and full stack count. Do not treat `commit-list` as the full event dump.
109
+
110
+ Valid live sort keys:
111
+ ```text
112
+ collateral sources restarts performedWork effects memos callbacks contextUpdates unmountedComponents unmountedSubtrees
113
+ ```
114
+
115
+ Do **not** use undocumented keys like `byDuration`. The live bridge rejects unsupported keys and returns `ok:false` with `validBy`. Offline analysis has a different, richer derived-key set; do not mix live and offline sort keys.
116
+
117
+ ### `avalanche-commit-peek <clientId> <commitId> [include] [depth] [maxItems]`
118
+ ```bash
119
+ chippy.sh avalanche-commit-peek my-client 42 stats,sources,metrics 3 20
120
+ chippy.sh avalanche-commit-peek my-client 42 stats,sources,actions 3 20
121
+ chippy.sh avalanche-commit-peek my-client 42 stats,rendered 3 20
122
+ ```
123
+ Pull one commit across slices. Use `include` CSV to limit payload. `actions` returns full event breadcrumbs attached to this commit. `rendered` returns bounded structural evidence for components that rendered in the commit, including parent/source ids, shallow prop-change facts, self-source flag, and linked trasher counts.
124
+
125
+ Effective live include values:
126
+ ```text
127
+ stats,sources,timing,metrics,unmounts,actions,rendered
128
+ ```
129
+
130
+ `atl` is a deprecated alias of `metrics`; it returns the same slice under `atl`.
131
+
132
+ `unmounts` returns bounded branch-start samples for this one commit. It is a zoom-in surface, not a full export: use `commit-list ... unmountedComponents` / `unmountedSubtrees` for counts first, then `commit-peek N ... unmounts` to ask where teardown started and how large each sampled branch was.
133
+
134
+ The wrapper accepts CSV strings, but the current live in-page bridge only emits the upstream-shaped slices above. `collaterals`, `context`, and `amplifiers` are useful for offline saved-capture analysis when those arrays exist in the saved payload; they are not reliable live slices.
135
+
136
+ `all` is **not** a supported include value. The live bridge rejects unsupported includes and returns `ok:false` with `validInclude`. Omit `include` for the default live slice set, or pass an explicit CSV like `stats,sources,metrics` or `stats,unmounts`.
137
+
138
+ ### `avalanche-describe-action <clientId> <actionId>`
139
+ ```bash
140
+ chippy.sh avalanche-describe-action my-client 7
141
+ ```
142
+ Looks up one recorded event/action breadcrumb by id and returns the full payload. Use this after `commit-list` surfaces a useful `latestAction` id. Full payload includes DOM target, nearest test id, target fiber, and complete locality stack. In dev builds the stack is `ownerStackKind: "owners"`; in production bundles owners may be unavailable, so it falls back to `ownerStackKind: "parents"` using the React return chain. This is probe-like grounding for a click/key/input: DOM target + nearest meaningful fiber + locality stack.
143
+
144
+ ### `avalanche-export <clientId> [lastN]`
145
+ ```bash
146
+ chippy.sh avalanche-export my-client
147
+ ```
148
+ Full JSON export from live page. Large output is easy to truncate in the shell; prefer `avalanche-save` when the export will be inspected more than once.
149
+
150
+ ### `avalanche-save <clientId> <path> [count]`
151
+ ```bash
152
+ chippy.sh avalanche-save my-client .chippy-captures/raf-1846.json
153
+ chippy.sh avalanche-save my-client .chippy-captures/last-50.json 50
154
+ ```
155
+ Save once. Analyze offline many times. `count=0` or omitted saves the full export; `count=N` saves the last N commits.
156
+
157
+ Expected success response:
158
+ ```json
159
+ {"ok":true,"file":".chippy-captures/raf-1846.json","bytes":12345,"commits":67}
160
+ ```
161
+
162
+ Common failure:
163
+ ```text
164
+ avalanche-analyze: response has no Avalanche export (expected meta and commitStats); check avalanche-status
165
+ ```
166
+
167
+ No file is written. Re-check `avalanche-status` first: `installed`, `hasMetricsGlobal`, and `commitCount` should show a real capture. If status is empty, arm/reload correctly and capture again before saving. With `count=N`, per-commit data keeps the last N commits; aggregate `sources` totals still cover the whole capture.
168
+
169
+ ### `avalanche-offline <path> <summary|commit-list|commit-peek|hot-sources|describe-action|source-search> ...`
170
+ ```bash
171
+ chippy.sh avalanche-offline .chippy-captures/raf-1846.json summary
172
+ chippy.sh avalanche-offline .chippy-captures/raf-1846.json commit-list 10 collateral
173
+ chippy.sh avalanche-offline .chippy-captures/raf-1846.json commit-peek 42 stats,sources,rendered 20
174
+ chippy.sh avalanche-offline .chippy-captures/raf-1846.json source-search --name 'Issue(Row|List)'
175
+ ```
176
+ Use saved captures without a live page. `commit-list`, `commit-peek`, `hot-sources`, and `describe-action` run the same code as the live commands and return the same shapes, so sort keys and include values match the live tables above (`unmounts` is live-only).
177
+
178
+ `summary` returns commit count, first/last commit id, source count, and totals of the commit stats. `source-search --name <regex>` matches aggregate sources by `sourceId` and rendered components by name with per-instance render and self-source counts.
179
+
180
+ ### `avalanche-arm <clientId> <origin> [maxCommits]`
181
+ ```bash
182
+ chippy.sh avalanche-arm my-client http://localhost:9000 200
183
+ ```
184
+ Arm runtime capture on this origin without reloading.
185
+
186
+ ### `avalanche-disarm <clientId> <origin>`
187
+ ```bash
188
+ chippy.sh avalanche-disarm my-client http://localhost:9000
189
+ ```
190
+ Cancel armed reload capture.
191
+
192
+ ### `page-reload <clientId>` / `page-wait-reload <clientId> [timeoutMs]`
193
+ ```bash
194
+ chippy.sh page-reload my-client
195
+ chippy.sh page-wait-reload my-client 15000
196
+ ```
197
+ Low-level reload helpers. Use when debugging injection timing.
198
+
199
+ ## Event/action breadcrumbs
200
+
201
+ Avalanche records bounded browser event breadcrumbs while armed. Defaults are explicit user actions only: click, keydown, keyup, input, change, submit. Noisy streams such as wheel/scroll/move are opt-in via detector options (`interactionCapture: 'noisy' | 'all'`) or explicit `interactionEvents`.
202
+
203
+ Breadcrumb linking is intentionally minimal: action id, action name, last commit id, delta time, DOM target, nearest test id, target fiber, and locality stack. The agent reconstructs deeper source context on demand.
204
+
205
+ Read order:
206
+ 1. `avalanche-commit-list` — compact latestAction clues only.
207
+ 2. `avalanche-describe-action <id>` — full event breadcrumb by id.
208
+ 3. `avalanche-commit-peek <commitId> ... actions` — all actions attached to one commit.
209
+
210
+ For locality: dev builds use `ownerStackKind: "owners"`; production/minified builds fall back to `ownerStackKind: "parents"`. Compact views keep the closest 10 names from the target-local side plus the full count. Full views may be large; inspect only when needed.
211
+
212
+ ## Observability design principle
213
+
214
+ When a capture cannot answer the question, improve observability deliberately:
215
+
216
+ - add the smallest first-class, bounded, tested surface that answers the missing question
217
+ - keep broad tools broad and detail tools detailed: `commit-list` ranks counts, `commit-peek` zooms into one commit, `export` stays lean
218
+ - do not widen default payloads or dump raw trees because one investigation needed detail
219
+ - preserve tool shape: one include/query should answer one question (`stats`, `sources`, `metrics`, `unmounts`)
220
+ - write a focused test for the new observable before trusting live output
221
+
222
+ ## Report shape
223
+
224
+ Start with these fields:
225
+ - `meta` — capture metadata; `meta.commits` is total processed commits
226
+ - `sources` — aggregated hot sources across window
227
+ - `sourcesByCommit[N]` — sources for commit `N`
228
+ - `commitStats[N]` — fixed tuple for commit `N`
229
+ - `metricsByCommit[N]` — low-level adapter metrics for commit `N` (deprecated alias `atlByCommit`)
230
+ - `restartWasteByCommit[N]` — restart-heavy source attribution for commit `N`
231
+
232
+ `commitStats[N]` tuple:
233
+ ```text
234
+ [0] collateral
235
+ [1] sources
236
+ [2] restarts
237
+ [3] performedWork
238
+ [4] effects
239
+ [5] memos
240
+ [6] callbacks
241
+ [7] contextUpdates
242
+ [8] unmountedComponents
243
+ [9] unmountedSubtrees
244
+ ```
245
+
246
+ Unmount reading:
247
+ - `unmountedComponents` = total component fibers torn down in that commit
248
+ - `unmountedSubtrees` = branch starts of teardown, useful for naming where the unmount begins
249
+ - high `unmountedComponents` with low `unmountedSubtrees` = one large branch removed
250
+ - high `unmountedSubtrees` = multiple branch starts; suspect conditional replacement, key churn, route/layout swap, or broad tree replacement
251
+ - `commit-peek N stats,unmounts` returns bounded branch-start samples for commit `N`; samples are triage anchors, not an exhaustive list of removed components
252
+ - branch samples include `name`, `source`, `depth`, `count`, and a `host` summary derived from DOM-rendering fibers when present in the branch: `elements`, `text`, `tagsTop`, `testIdsTop`, `rolesTop`, `ariaLabels`, `textHints`
253
+ - For user asks like `DOM unmounts`, `real DOM teardown`, `regions`, or `where was removed`, answer DOM-first: root family count, largest branch counts, `testIdsTop`, `rolesTop`, `tagsTop`, `ariaLabels`, and `textHints` before component/provider labels.
254
+ - host summary provides spatial evidence from fibers only. Do not use MutationObserver, DOM monkeypatching, or geometry for default Avalanche unmount reads; those surfaces are noisier, easier to perturb, and belong only in explicit debug instrumentation.
255
+ - Read unmount samples as **where teardown started and how much was removed**. Component names label the branch root; they are secondary to branch position and host/testid/text hints.
256
+
257
+ Suspense reading:
258
+ - `suspenseByCommit[N]` records boundary transitions for commit `N`
259
+ - `entered=true` = boundary entered fallback
260
+ - `left=true` = boundary revealed primary content
261
+ - boundaries that render without delay are intentionally silent — no `suspenseByCommit` entry means no fallback transition recorded, not "nothing happened"
262
+ - treat the boundary as branch metadata first; trace below it before blaming placeholder wrappers
263
+
264
+ ## `renderedByCommit` — per-component render evidence
265
+
266
+ Populated in every detection mode. The mode decides which fields carry evidence: `stats` has no source tracking, so `isSelfSource` stays `false`; `trasherCounts` stay zero unless the mode is `full` or `amplifiers`. Pulled into `commit-peek` output via `include=rendered`.
267
+
268
+ Top-level shape (`?` = optional, may be absent or `null`; everything else is required):
269
+
270
+ ```text
271
+ renderedByCommit: Record<commitId: number, RenderedComponentEvidence[]>
272
+
273
+ RenderedComponentEvidence = {
274
+ sourceId: number, // joins to sourcesByCommit / hot-sources
275
+ name: string, // component display name; "Unknown" if unresolved
276
+ parentId: number | null, // sourceId of nearest rendered parent (root = null)
277
+ isSelfSource: boolean, // true if the component re-rendered itself
278
+ propChanges: PropChangeEvidence[], // [] when no diff (not absent)
279
+ trasherCounts: { effects: number; callbacks: number; memos: number },
280
+ // all three always present; 0 when no amplifier hits
281
+ trashers?: TrasherEvidence[], // bounded samples for non-zero trasherCounts
282
+ selfTime?: number, // ms; only when Profiler timing available
283
+ actualDuration?: number // ms; only when Profiler timing available
284
+ }
285
+ ```
286
+
287
+ `PropChangeEvidence`:
288
+
289
+ ```text
290
+ PropChangeEvidence = {
291
+ name: string, // prop key
292
+ kind: 'added' | 'removed' | 'scalar-changed' | 'ref-changed' | 'unchanged',
293
+ prev?: PropValueSummary, // present when kind ∈ {removed, scalar-changed, ref-changed, unchanged}
294
+ current?: PropValueSummary // present when kind ∈ {added, scalar-changed, ref-changed, unchanged}
295
+ }
296
+ ```
297
+
298
+ `PropValueSummary` (bounded — no deep clone):
299
+
300
+ ```text
301
+ PropValueSummary = {
302
+ type: 'string'|'number'|'boolean'|'null'|'undefined'|'function'|'array'|'object'|'symbol'|'bigint',
303
+ preview: string, // human-readable summary; always present
304
+ length?: number, // arrays + strings
305
+ keysTop?: string[], // objects: up to N top-level keys
306
+ sample?: unknown // arrays: bounded element sample
307
+ }
308
+ ```
309
+
310
+ `TrasherEvidence` (bounded — samples the changed hook deps, not every occurrence):
311
+
312
+ ```text
313
+ TrasherEvidence = {
314
+ hookIndex: number,
315
+ hookType: 'effect' | 'callback' | 'memo',
316
+ effectTag?: number,
317
+ changedDepIndices: number[],
318
+ changedDeps: Array<{
319
+ index: number,
320
+ prev: PropValueSummary,
321
+ current: PropValueSummary,
322
+ origin: { type: unknown; hookIndex: number; depth: number; hookType: string } | null
323
+ }>
324
+ }
325
+ ```
326
+
327
+ For effects, `changedDeps` is the first stop: it names the dependency index and bounded prev/current summaries that caused the effect to re-run.
328
+
329
+ Cross-reference: the producer is `src/avalanche/detector.tsx` (`diffProps`, `summarizePropValue`, `detectHookThrashing`); the DTO mapper is `src/avalanche/report-serializer.tsx` (`RenderedComponentEvidenceDTO`).
330
+
331
+ ### Reading `trasherCounts`
332
+
333
+ `trasherCounts` reports how many amplifier observations (`effects`, `callbacks`, `memos`) the detector attributed to this component in this commit. **Non-zero means the component fired an effect/callback/memo whose dependency identity changed.** This is the routed signal for the "trasher" verdict in P-06.
334
+
335
+ Reading rules:
336
+ - `trasherCounts.effects > 0` — at least one `useEffect` re-ran from changed deps.
337
+ - `trasherCounts.callbacks > 0` — at least one `useCallback` produced a new function identity.
338
+ - `trasherCounts.memos > 0` — at least one `useMemo` recomputed.
339
+ - All three zero in `renderedByCommit` while the component still appears in `sourcesByCommit` → render trigger is *not* amplifier-driven; look at `propChanges` for ref-changed props from above, or treat as self-update (`isSelfSource: true`).
340
+ - Same `sourceId` appears in `renderedByCommit` across many commits with persistent non-zero `trasherCounts` → confirmed trasher; trace the hook with `avalanche-debug-hook <clientId> <sourceId> <hookIndex>`.
341
+
342
+ ### Reading `propChanges`
343
+
344
+ `propChanges` lists only the props that differed between prev and current. An empty array means the parent passed the same prop identities — re-render trigger is internal (state/context/store/parent re-render of a non-memoized child).
345
+
346
+ Reading rules:
347
+ - `kind: 'ref-changed'` on a callback or object prop → likely upstream amplifier; trace the parent's `useCallback`/`useMemo` deps.
348
+ - `kind: 'scalar-changed'` → data actually changed; the render is justified if the component should react to that value.
349
+ - `kind: 'added'` / `'removed'` on optional props → parent toggled a feature; verify the toggle is intentional.
350
+ - All props `unchanged` but component re-rendered → ancestor cascade. Look up the parent chain, not at this component.
351
+
352
+ ### `renderedByCommit` prop-change taxonomy — read the simple case first
353
+
354
+ `renderedByCommit` is not a "why-did-you-render for three tricks". It is a per-commit prop-diff ledger. Read every rendered entry in this order:
355
+
356
+ | # | Signal | What it means | Typical classification | Fix layer / next step |
357
+ |---|---|---|---|---|
358
+ | 0 | `isSelfSource: true` | This component scheduled work for itself in this commit. | **self-source** | Inspect this component's changed hooks / callbacks / effects (P-07). |
359
+ | 1 | only `scalar-changed` props | Data actually changed (`number`, `string`, `boolean`, `null`). | **legitimate re-render** unless invariant says the scalar should not affect this child. | Verify invariant. If legitimate, stop; if not, move the scalar boundary up. |
360
+ | 2 | `added` / `removed` optional props | Parent toggled a feature or branch. | **conditional prop / branch change** | Verify the toggle is intentional; inspect the parent conditional if unexpected. |
361
+ | 3 | no prop changes and `isSelfSource: false` | Parent/ancestor rendered and this component came along. | **cascade collateral** | Walk to `parentId`; do not blame this component. |
362
+ | 4 | `ref-changed` function prop | Callback identity changed. | **identity churn** or legitimate callback update. | Walk to parent; inspect `useCallback` deps or inline function creation. |
363
+ | 5 | `ref-changed` object/array prop | Object identity changed. Contents may still be equal. | **identity leak** or legitimate data object change. | Walk to parent; inspect literal creation, selectors, `useMemo`, data normalization. |
364
+ | 6 | `ref-changed` `children` | JSX children identity changed. | **caller-created ReactElement churn** | Caller-only fix: hoist/memoize children at the caller. |
365
+ | 7 | any prop changes plus `trasherCounts > 0` | Prop churn and local hook churn are both present. | **mixed leak + amplifier** | Separate caller leak from local amplifier; prove both before editing. |
366
+
367
+ General rules:
368
+ - Start with the simple explanation. A `scalar-changed` prop is often correct behavior, not a performance bug.
369
+ - `ref-changed` means identity changed, not automatically "bad". It becomes a bug only when the component should not observe that identity change or when it defeats a memo boundary.
370
+ - If `trasherCounts` are all zero and the only suspicious signal is `ref-changed`, treat it as a **caller-side identity question** first. Walk to `parentId`; do not hunt for amplifier hooks in the child.
371
+ - If `propChanges` is empty and `isSelfSource` is false, this entry is collateral. Keep walking upward until the first self-source, context wave, or identity-changing parent.
372
+
373
+ #### Common identity fingerprints
374
+
375
+ These are common examples, not an exhaustive taxonomy:
376
+
377
+ | Pattern | Fingerprint | Reading | Caller-side remediation |
378
+ |---|---|---|---|
379
+ | Callback dep churn | `{ name: <callback>, kind: "ref-changed", prev.type: "function", current.type: "function" }` often with a scalar dep also `scalar-changed` in the same commit | `useCallback(fn, [dep])` is tied to a dep that changes during the same interaction. | Use a functional updater (`setX((v) => !v)`) to remove the dep, or otherwise stabilize the callback identity. |
380
+ | Array/object literal | `{ name: <prop>, kind: "ref-changed", prev.type: "object", current.type: "object" }` and source shows a literal or freshly allocated object | Parent recreates an equal-looking object each render; `React.memo` shallow compare fails. | Hoist literal to module scope, `useMemo(() => […], [])`, or pass a stable reference. |
381
+ | JSX children pass-through | `{ name: "children", kind: "ref-changed", prev.type: "object", current.type: "object" }` on a memoized parent | Inline JSX creates fresh `ReactElement` objects/arrays each parent render; the memoized parent cannot save itself. | Caller-only: hoist children into a stable variable or pass memoized element children. |
382
+
383
+ ### Reading `propChanges` + `trasherCounts` together — the source classification table
384
+
385
+ | `isSelfSource` | `propChanges` has changes | `trasherCounts > 0` | Classification | Next step |
386
+ |---|---|---|---|---|
387
+ | `true` | n/a | n/a | **self-source** (component triggered its own re-render) | P-07 hook interrogation on this component |
388
+ | `false` | yes (ref-changed) | n/a | **collateral from upstream amplifier** | Walk to `parentId`, run P-06 on that |
389
+ | `false` | yes (scalar-changed only) | n/a | **legitimate re-render** (data changed) | Verify invariant — may be correct behavior |
390
+ | `false` | empty | yes | **trasher** (this component is the amplifier) | P-07 on the changed hook |
391
+ | `false` | empty | no | **pure cascade collateral** | Walk to `parentId`, look for the real trigger |
392
+
393
+ ## Reading signals correctly
394
+
395
+ React metrics are **population statistics**. Avalanche is **commit evidence**.
396
+
397
+ Do not read raw counters as single-component causality. A counter like `componentRenderWithHooksCount: 800` means React executed 800 function-component render paths in the capture/commit window. It does not say which visible component was the root cause, whether every execution committed, or which prop/hook/context made one component render.
398
+
399
+ Use this hierarchy:
400
+
401
+ ```text
402
+ population metrics → shape and scale
403
+ commit details → what happened in one React commit
404
+ fiber/probe/source → why a specific component participated
405
+ before/after diff → whether the same waste disappeared
406
+ ```
407
+
408
+ ### Signal layers
409
+
410
+ | Layer | Examples | Answers | Does not answer |
411
+ |---|---|---|---|
412
+ | **Population metrics** | `componentRenderWithHooksCount`, `domWorkCount`, `contextUpdateCount`, `mountWorkCount` | How much React work happened overall | Why one component rendered |
413
+ | **Commit stats** | `commitStats[N]` | Shape of one commit: source count, collateral breadth, restarts, context/effect/memo/callback churn | Exact prop/hook cause |
414
+ | **Commit details** | `sourcesByCommit[N]`, `contextUpdatesByCommit[N]`, `restartWasteByCommit[N]` | Which sources/providers/restart signals were involved in that commit | Whether a downstream component could have bailed out |
415
+ | **Fiber/probe data** | owners, parents, props, hooks, source locations | How values crossed boundaries for a target component | Population scale |
416
+ | **Code** | hook deps, provider values, selectors, keys, memo boundaries | The implementation cause and fix layer | Runtime frequency without a capture |
417
+
418
+ ### Read from broad to narrow
419
+
420
+ 1. **Population shape.** Use raw React metrics to see whether the window is mostly render work, DOM work, mount work, context propagation, or discarded/restarted work.
421
+ 2. **Commit selection.** Use live `commit-list` to pick the commit that best represents the suspicious upstream stat shape (`collateral`, `sources`, `restarts`, `performedWork`, `effects`, `memos`, `callbacks`, `contextUpdates`). Use offline `commit-list` for saved-capture derived keys like `amplifiers`, `repeats`, or `restartProxy`.
422
+ 3. **Per-commit join.** Use `commit-peek` and read `commitStats[N]`, `sourcesByCommit[N]`, `metricsByCommit[N]`, and restart/context side channels together.
423
+ 4. **Role classification.** Classify a component as source, hotspot, collateral, mount/remount, context/store participant, or restart/discarded work.
424
+ 5. **Fiber trace.** Use probes to inspect owner/parent/prop/hook flow before naming a specific cause.
425
+ 6. **Source trace.** Open the exact source location and verify hook deps, setter call sites, provider values, selector identity, keys, or memo boundaries.
426
+ 7. **Diff.** Re-run the same capture window after one fix and compare the same signals.
427
+
428
+ ### Population metric reading
429
+
430
+ | Metric | Correct reading | Common mistake |
431
+ |---|---|---|
432
+ | `componentRenderWithHooksCount` | Function-component render executions, including work that may not commit | Calling it visible re-render count |
433
+ | `finishClassComponentCount` | Class component render completions | Ignoring class work in total render executions |
434
+ | `componentRenderWithHooksCount + finishClassComponentCount` | Raw component render executions in the window | Treating it as root-cause count |
435
+ | `componentWorkCount` | Component-level fiber work | Assuming every unit changed DOM |
436
+ | `commitWorkCount` | Work that reached commit phase | Treating all render executions as committed |
437
+ | `domWorkCount` | Host DOM/text fiber work | Assuming component render noise changed the DOM |
438
+ | `mountWorkCount` / `componentMountWorkCount` | New fiber/component work; high during an interaction suggests remounting | Calling remounts normal re-renders |
439
+ | `updateWorkCount` / `componentUpdateWorkCount` | Existing fiber/component update work | Assuming updates explain why they happened |
440
+ | `contextUpdateCount` | Context provider value propagation count | Assuming all consumers changed meaningful data |
441
+ | `legacyContextUpdateCount` | Legacy context propagation | Treating it like modern context without checking class paths |
442
+ | `externalStoreRestartCount` | `useSyncExternalStore` restart signal | Optimizing collateral children first |
443
+ | `priorityInterruptedWorkCount` | Work abandoned due to higher-priority preemption | Calling it component churn |
444
+ | `renderRootSyncCount` / `renderRootConcurrentCount` | Root render passes by mode | Treating pass count as component count |
445
+ | `totalTickCount` / `ticksCount` | Scheduler/render ticks | Treating ticks as commits |
446
+
447
+ ### Commit-stat reading
448
+
449
+ `commitStats[N]` is the shape of commit `N`:
450
+
451
+ | Field | Correct reading |
452
+ |---|---|
453
+ | `collateral` | Components that rendered without local source evidence; blast radius / victims |
454
+ | `sources` | Components Avalanche identified as update origins in that commit |
455
+ | `restarts` | Render executions discarded before commit; confirm with side fields |
456
+ | `performedWork` | Fibers React marked with performed work during commit |
457
+ | `effects` | Effect dependency churn/amplifier signal |
458
+ | `memos` | Memo dependency churn/amplifier signal |
459
+ | `callbacks` | Callback dependency churn/amplifier signal |
460
+ | `contextUpdates` | Context provider changes associated with the commit |
461
+
462
+ Pattern reads:
463
+
464
+ ```text
465
+ high sources + high collateral
466
+ → source started a broad wave
467
+
468
+ low sources + high collateral
469
+ → likely propagation from parent/context/store or missing boundary
470
+
471
+ high contextUpdates + high collateral
472
+ → provider wave; inspect value diff before assuming churn
473
+
474
+ high restarts with side fields
475
+ → discarded work; do not optimize random collateral first
476
+
477
+ high mount work during interaction
478
+ → remount problem: keys, conditional tree replacement, route/layout swap
479
+
480
+ high domWorkCount
481
+ → host tree work happened; not just harmless component function calls
482
+ ```
483
+
484
+ ### Reconciliation checks
485
+
486
+ Use raw React metrics to check whether Avalanche classification explains the work:
487
+
488
+ ```text
489
+ atlTotal = componentRenderWithHooksCount + finishClassComponentCount
490
+ classifiedTotal = commitStats[N].collateral + commitStats[N].sources
491
+ ```
492
+
493
+ Read the gap:
494
+
495
+ ```text
496
+ atlTotal ≈ classifiedTotal
497
+ → Avalanche classification accounts for most component render work
498
+
499
+ atlTotal >> classifiedTotal
500
+ → discarded/restarted work, unclassified work, or measurement mismatch; inspect restart side fields
501
+
502
+ componentMountWorkCount high while expecting update-only
503
+ → remount, unstable keys, conditional subtree replacement, or component type churn
504
+
505
+ domWorkCount high
506
+ → real host tree work; check DOM-changing props/list replacement/layout subtree
507
+ ```
508
+
509
+ Do not call `atlTotal` "component X rendered N times". It is population data. Single-component wording requires commit details plus fiber/source evidence.
510
+
511
+ ## Per-commit join
512
+
513
+ These slices share the same commit index `N`:
514
+ - `commitStats[N]`
515
+ - `sourcesByCommit[N]`
516
+ - `metricsByCommit[N]`
517
+ - `restartWasteByCommit[N]`
518
+ - `contextUpdatesByCommit[N]`
519
+
520
+ Use `commit-list` to pick a commit. Use `commit-peek` to inspect the same `N` across slices. Never compare `commitStats[42]` to `sourcesByCommit[43]` or a different capture window.
521
+
522
+ ## Restart semantics
523
+
524
+ `restarts` = **discarded render executions**. React called the render function but threw away the result before commit.
525
+
526
+ Not "render cycles restarted". Not "component mounted and unmounted". A render ran, produced output, and that output was discarded.
527
+
528
+ Formula:
529
+ ```text
530
+ atlTotal = componentRenderWithHooksCount + finishClassComponentCount
531
+ restarts = max(0, atlTotal - committedComponents)
532
+ ```
533
+
534
+ Example: `atlTotal = 2116`, `committedComponents = 3` → `restarts = 2113` means 2113 render executions discarded in that commit window.
535
+
536
+ Treat it as a strong proxy, not a courtroom fact. Confirm cause with side fields:
537
+ - `externalStoreRestarts` — `useSyncExternalStore` races
538
+ - `priorityInterruptions` — higher-priority work preempted this render
539
+ - `fiberInterruptions` — interrupted fiber work without a cleaner subtype
540
+
541
+ Large `restarts` with zero side fields: still real, but needs more skepticism.
542
+ Large `restarts` with large `priorityInterruptions`: concurrent-mode priority thrash. Look for what triggers the high-priority interruption.
543
+
544
+ ## Scoping a capture
545
+
546
+ Three tools:
547
+ - temporal: short reload window, short settle, low commit cap
548
+ - ranking: live `commit-list` by `collateral`, `restarts`, or `contextUpdates`; offline `commit-list` by derived keys like `restartProxy`, `amplifiers`, or `repeats`
549
+ - filtering: save export, then use `source-search` or offline `commit-peek`
550
+
551
+ Do not scope avalanche by selector. Scope by time window, commit, or component name (`source-search --name`). Exports carry no source file paths, so file-based scoping is not available.
552
+
553
+ ## Commit inspection discipline — rebuild the crime scene
554
+
555
+ Do not chase the largest commit by default. A large commit can be inevitable: route content appears, a list legitimately receives new data, or a modal mounts real UI. Large means expensive; it does **not** mean causal or fixable.
556
+
557
+ For interactive Avalanche, rebuild the order of events:
558
+
559
+ ```text
560
+ 1. Pick a suspicious commit.
561
+ 2. Inspect the commits before it: what changed that made this wave possible?
562
+ 3. Inspect the suspicious commit: what work happened here, and which parts were necessary?
563
+ 4. Inspect the commits after it: what continued, repeated, or became avoidable noise?
564
+ 5. Decide whether the fix belongs in the past, the commit itself, or the future cleanup.
565
+ ```
566
+
567
+ ### Before / during / after read
568
+
569
+ | Window | Question | Tool read |
570
+ |---|---|---|
571
+ | **Before** | What earlier source/provider/store/key/identity change set up this wave? | `commit-list` around the target, `commit-peek` on N-1/N-2/N-3, sort by `sources` or `contextUpdates` |
572
+ | **During** | Which work in this commit was necessary vs collateral/noisy? | `commit-peek N stats,sources,metrics`, add `unmounts` when teardown counts are high, then probe/source for the top source, provider, or branch root |
573
+ | **After** | Did this wave stop, or did it leave repeating noise? | `commit-list` after N, sort by `collateral`, `restarts`, `effects`, `memos`, `callbacks`, or offline `repeats` |
574
+
575
+ ### Two-direction 5 whys
576
+
577
+ Run both directions when a capture is messy.
578
+
579
+ **Step backward — why did this happen?**
580
+
581
+ ```text
582
+ Why did this commit render?
583
+ → because this source/provider/store changed
584
+ → why did that source change?
585
+ → why did the parent/store/effect produce that value?
586
+ → why did no boundary prune it?
587
+ → what earlier commit or setup made this inevitable?
588
+ ```
589
+
590
+ **Step forward — why did this continue?**
591
+
592
+ ```text
593
+ Why were there commits after the main wave?
594
+ → because an effect/store/subscription/context update fired
595
+ → why did it fire after the main commit?
596
+ → why did the result still look new/noisy?
597
+ → why did consumers fail to bail out?
598
+ → which small repeated source would reduce noise and reveal the real signal?
599
+ ```
600
+
601
+ ### Reading rule
602
+
603
+ A useful Avalanche answer is not "commit 42 is largest". It is:
604
+
605
+ ```text
606
+ commit 39 set up <source/provider/identity>
607
+ commit 42 performed necessary <mount/update> plus noisy <collateral/restarts>
608
+ commits 43-47 continued because <effect/store/context/identity>
609
+ therefore fix <earlier setup | boundary | later noise source>
610
+ ```
611
+
612
+ ## Thrash and memo reading
613
+
614
+ Look for:
615
+ - repeated source across many commits
616
+ - high collateral with low source count
617
+ - amplifiers: unstable `memo` / `callback` / `effect` deps
618
+ - context waves: repeated provider identity churn
619
+
620
+ Hook churn means the source changed. It does not mean the fix is inside that component.
621
+
622
+ ## Confirming a fix
623
+
624
+ After a change:
625
+ - same interaction, same window
626
+ - lower `collateral`
627
+ - fewer repeated commits from same source
628
+ - fewer amplifiers or context waves
629
+ - hot source drops or remains but with lower blast radius
630
+
631
+ ## Troubleshooting
632
+
633
+ - Manifest or extension script change: reload the extension.
634
+ - Bundle change: reload the page.
635
+ - Helper-server change: `chippy.sh stop` then `chippy.sh start`.
636
+ - `commitCount: 0` with installed detector: verify patched `react-dom` and reload path.
637
+ - Duplicate `__AVALANCHE__ already defined`: same bundle injected twice. Fix startup order before trusting results.
638
+ - On Jira-like pages, metrics may be reset during bootstrap/hydration. Chippy must preserve or restore the live metrics object before judging capture health.
639
+
640
+ ## Working with subagents — paginated Avalanche analysis
641
+
642
+ Large captures can exceed one agent's useful working memory. Do not limit analysis to the main context when the capture naturally splits into waves, ranges, or questions. Use subagents as page workers over reduced slices.
643
+
644
+ ### When to paginate
645
+
646
+ Use paginated subagent analysis when:
647
+ - the capture has 100+ commits or shell output truncates useful details
648
+ - the capture spans multiple interaction waves, async loading phases, or post-interaction cleanup
649
+ - one large commit is not enough to explain the page behavior
650
+ - before/during/after reasoning needs separate setup, main-wave, and noise analysis
651
+
652
+ Do **not** paginate when one `commit-peek` answers the question, or when adjacent commits are tightly coupled and splitting would hide causality. In that case, keep the before/during/after chain in the main context.
653
+
654
+ ### Core rule
655
+
656
+ Partition by **question**, not by equal commit count.
657
+
658
+ Good slices:
659
+ ```text
660
+ commits 0-30: what setup made the later wave inevitable?
661
+ commits 31-60: was the main wave necessary work or avoidable collateral?
662
+ commits 61-100: what repeated after the UI should have settled?
663
+ ```
664
+
665
+ Bad slice:
666
+ ```text
667
+ commits 0-50: analyze this
668
+ ```
669
+
670
+ ### Workflow
671
+
672
+ 1. **Save once.**
673
+ ```bash
674
+ chippy.sh avalanche-save my-client .chippy-captures/run.json
675
+ ```
676
+ 2. **Build a top-level timeline in the main context.**
677
+ ```bash
678
+ chippy.sh avalanche-offline .chippy-captures/run.json summary
679
+ chippy.sh avalanche-offline .chippy-captures/run.json commit-list 30 collateral
680
+ chippy.sh avalanche-offline .chippy-captures/run.json commit-list 30 repeats
681
+ chippy.sh avalanche-offline .chippy-captures/run.json commit-list 30 contextUpdates
682
+ ```
683
+ 3. **Choose ranges by wave/question.** Setup, main wave, continuation/noise, or first-principles invariant. `commit-list` ranks globally; use the ranked rows plus timestamps/commit ids to define ranges.
684
+ 4. **Reduce each range before delegation.** Use `commit-peek` on selected commit ids and `source-search --name` for known components. Give the subagent only the top suspicious commits and relevant stats/sources/context/metrics slices. No raw exports.
685
+ 5. **Ask one concrete question per subagent.**
686
+ 6. **Collect compact findings.** Each subagent returns commit range, role, evidence, likely fix layer, confidence, and next gap.
687
+ 7. **Join findings in the main context.** Rebuild the crime scene across slices: setup → main wave → continued noise. Ask whether slices share one earlier root cause before accepting fragmented fixes.
688
+
689
+ ### Subagent prompt shape
690
+
691
+ ```text
692
+ Analyze reduced Avalanche slice <commit range> from <saved capture path or pasted compact slice>.
693
+ Question: <one concrete question>.
694
+ Use population-vs-commit reading rules: raw React metrics are population stats; do not claim single-component causality without source/fiber evidence.
695
+ Return only:
696
+ - range and commits inspected
697
+ - role: setup | main wave | continuation | noise | unknown
698
+ - top source/provider/store/key/identity signal
699
+ - evidence from stats/sources/context/metrics
700
+ - likely fix layer: earlier setup | boundary | later noise source | no fix here
701
+ - confidence: confirmed | likely | partial
702
+ - next gap
703
+ Do not return raw payload.
704
+ ```
705
+
706
+ ### Example questions
707
+
708
+ ```text
709
+ In commits 0-30, what earlier provider/store/key/identity setup made the later wave inevitable?
710
+ ```
711
+
712
+ ```text
713
+ In commits 31-60, is the largest commit mostly necessary mount/data work or avoidable collateral?
714
+ ```
715
+
716
+ ```text
717
+ In commits 61-100, which repeated source/provider/effect keeps firing after the interaction should have settled?
718
+ ```
719
+
720
+ ```text
721
+ Across this slice, do the repeated commits share one source, or are they independent noisy birds?
722
+ ```
723
+
724
+ ### Guardrails
725
+
726
+ - No concrete question, no subagent.
727
+ - No raw exports; reduce payload first.
728
+ - No "analyze this slice" prompts.
729
+ - Each slice must be joinable back to the global timeline.
730
+ - If a subagent finds local symptoms only, the main agent still asks: "what earlier commit made this possible?"
731
+ - If multiple slices point to the same provider/store/key/identity source, prefer one upstream fix over many local patches.