@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,490 @@
1
+ # Fixing performance — avalanche-driven workflow
2
+
3
+ Use a capture to classify render behavior and choose a fix for the three most common avalanche patterns. Investigate one Provider or one source at a time.
4
+
5
+ Lifecycle stage: **commit analysis → attribution → fix-layer decision**. Enter after Avalanche has captured the real load/interaction and you have candidate commits/sources from `avalanche-commit-list` or `avalanche-hot-sources`.
6
+
7
+ Exit when you can name the source/boundary/prune layer and the evidence-backed edit. If the hypothesis needs a reproducible subtree check, switch to [stability-render](stability-render.md). If a known source has a changed `hookIndex` but the hook role is unclear, switch to [debug-hook](debug-hook.md). After an edit, verify by repeating the same capture or the same stability-render nudge.
8
+
9
+ The remediation patterns below are candidates, not edit authorization. Apply one only when user intent permits and the P-15 proof packet passes the P-16 Apply gate; otherwise report the proven layer and verification command.
10
+
11
+ ## Capture the right window, then read context first
12
+
13
+ Enter this reference with a real capture. Match the capture to the reported symptom:
14
+
15
+ ```bash
16
+ # Runtime or interaction performance: no reload.
17
+ chippy.sh avalanche-arm <clientId> <origin>
18
+ # Reproduce the interaction, then read:
19
+ chippy.sh avalanche-status <clientId>
20
+ chippy.sh avalanche-hot-sources <clientId> 12
21
+
22
+ # Initial-load performance: arm, reload, reconnect, and settle.
23
+ chippy.sh reload-with-avalanche <clientId> <origin> 0 30000 5000
24
+ ```
25
+
26
+ For initial load with many context updates, rank the bounded commits before opening detail:
27
+
28
+ ```bash
29
+ chippy.sh avalanche-commit-list <clientId> 20 contextUpdates
30
+ chippy.sh avalanche-commit-peek <clientId> <commitId> stats,sources,atl 3 30
31
+ ```
32
+
33
+ Do not pipe the full export through an ad-hoc reducer. Use prepared Chippy views. If they cannot expose the required Provider detail, report that command gap or add a first-class view before making a causal claim.
34
+
35
+ Why the settle window matters: stopping on reconnect is too early for heavy pages. Post-load React work can continue for seconds after the page is back, so a short window undercounts the load.
36
+
37
+ Context evidence identifies a Provider and source location. Open that file and line directly, then determine whether the value carries real data changes or only a new reference.
38
+
39
+ After context: use hot sources and one bounded commit peek for source and thrash signals.
40
+
41
+ ### Applying the filter in practice
42
+
43
+ Filter rule for low-hanging fruit: **focus on shallow-equal churn only**. If the context payload carries genuinely new UI registrations, placements, targets, or other transport data, that is not a one-line churn fix.
44
+
45
+ Shallow-equal / identity-only candidates usually look like this:
46
+
47
+ - `identity changed` on every commit for the same Provider
48
+ - `object → object` with stable semantics and no meaningful field delta
49
+ - Provider `value={...}` built inline in render
50
+ - memoized wrapper exists, but the payload inside it still changes identity for no product reason
51
+
52
+ Not shallow-equal / skip for the first pass:
53
+
54
+ - registration-style fields such as `targets`, `placements`, mounted item registries, popup registries
55
+ - transport-layer payloads that legitimately reflect new items appearing on screen
56
+ - content field changes such as images, labels, IDs, flags, or fetched data
57
+
58
+ ### Capture lesson
59
+
60
+ Low commit counts on reload are often caused by **stopping too early**, not only by injection timing. `reload-with-avalanche` should include a settle window after reconnect.
61
+
62
+ Rule: use `reload-with-avalanche <clientId> <origin> 0 30000 5000` as an example starting point for heavy pages. Treat the timeout and settle values as local tuning knobs. Only blame injection timing after the stop timing is correct.
63
+
64
+ ---
65
+
66
+ ## Classification — triage before fixing
67
+
68
+ Every component avalanche sees is one of three roles. Never confuse them.
69
+
70
+ | Role | Avalanche field | Meaning |
71
+ |---|---|---|
72
+ | **Trigger** | `sources[]` with `isSelfUpdater: true` | Hook state changed here. This started the cascade. |
73
+ | **Hotspot** | Appears in `sources[]` frequently across commits | Renders often — may or may not be a trigger. |
74
+ | **Collateral** | `collaterals[]` | Re-rendered with no local change. Downstream blast victim. |
75
+
76
+ A hotspot is not necessarily a trigger. A collateral is not a problem by itself — its parent is. Fix the trigger, not the collateral.
77
+
78
+ > **Confirm hook kind and origin.** Once you have a source and a changed `hookIndex`, classification by primitive (`SyncExternalStore`, `State`, `Memo`, `Effect`, …) and custom-hook owner is the next decision. See [debug-hook](./debug-hook.md). Use it before picking a fix layer; the primitive narrows the remediation table to one row.
79
+
80
+ ### Cross-reference — per-commit metrics and restart attribution
81
+
82
+ Triage uses two facts that live in [avalanche-detector → Per-commit join](./avalanche-detector.md#per-commit-join):
83
+
84
+ - **Per-commit join.** `commitStats[N]`, `sourcesByCommit[N]`, and `atlByCommit[N]` share the same commit index `N`. To go from "this commit was hot" to "who fired it", join by `N`. Without this, source ranking is guesswork.
85
+ - **`restartWaste` per source.** Top sources are sorted by `collateral + restartWaste`, not just `collateral`. A low-collateral source can still be the worst offender if its commits get heavily restarted (Suspense throw, priority preemption, useSES mid-render). Always check `restartWaste` and `restartWasteByCommit[N]` before declaring a winner.
86
+
87
+ Practical use:
88
+ - "Above the source" check → look at `commitStats[N]` for the suspect commit. If `restarts > 0` and `sources` is empty, the wave is a **ghost restart** — the trigger is upstream/external, not in the source list.
89
+ - "Boundary" check → join `commitStats[N]` (high `collateral`, high `contextUpdates`) with `commits[N].sources[]` to see which source's identity churn caused the blast.
90
+ - "Below" check → if `commitStats[N].collateral` is high but `restarts` and `contextUpdates` are low, children failed to prune; fix is downstream.
91
+
92
+ > **Caveat — `restartProxy` ≠ upstream `restarts`.** The in-page chippy detector does not track `committedComponents`, so the per-commit `stats.restartProxy` field surfaced by `avalanche-commit-list` / `avalanche-commit-peek` is a **noisy upper bound**, not the precise upstream `commitStats[N][2]`. For exact attribution use the side fields:
93
+ > - `externalStoreRestarts` — `useSyncExternalStore` mutation mid-render
94
+ > - `priorityInterruptions` — concurrent priority preemption
95
+ > - `fiberInterruptions` — Suspense throw
96
+ >
97
+ > A high `restartProxy` with all three side fields at zero usually means under-counted `committedComponents`, not real restarts. See [avalanche-detector → Per-commit join](./avalanche-detector.md#per-commit-join).
98
+
99
+
100
+ ## Propagation model — source, boundary, prune
101
+
102
+ Three relations matter:
103
+ - **owner** = who created the fiber in React terms; the render site, prop source, or slot producer
104
+ - **parent** = who wraps it in the rendered tree; the structural container around it
105
+ - **slot** = owner appears inside a different parent chain because a child was passed in and rendered elsewhere; composition / dependency-injection shape
106
+
107
+ For performance work:
108
+ - use **owners** to find who is responsible for creating unstable children, callbacks, or provider values
109
+ - use **parents** to find where propagation spreads through wrappers, lists, layout shells, and context regions
110
+ - look for **own parents in own children** patterns to detect slots: the same owner contributes a child that later lives under a different parent region
111
+
112
+ A source component may need to update. That alone does not make it the fix target.
113
+
114
+ Three separate questions:
115
+
116
+ 1. **Why did this source update?**
117
+ 2. **Why did that update propagate into children?**
118
+ 3. **Why did those children fail to prune / bail out?**
119
+
120
+ Same avalanche can be fixed in three different places:
121
+
122
+ | Layer | Question | Typical fix location |
123
+ |---|---|---|
124
+ | **Above the source** | Did a parent/context/store force this source to update? | Parent effect, provider value, store selector, wrapper transport |
125
+ | **At the boundary** | Did the source recreate values that force child work? | Prop boundary, JSX identity, inline object/array/function, context value |
126
+ | **Below the source** | Did children fail to prune when inputs were stable enough? | `React.memo`, stable callbacks, stable derived props, subtree split |
127
+
128
+ Rule: a source may be legitimate. The bug can still be that it drags children with it, or that children fail to stop the wave.
129
+
130
+ ### Decision tree — where the fix lives
131
+
132
+ 1. **Confirm the trigger**
133
+ - `sources[]` with `isSelfUpdater: true` = local update happened here.
134
+ - If no local updater exists, you may be looking at propagation from above.
135
+
136
+ 2. **Check above the source**
137
+ - Did a parent/provider/store update force this source to run?
138
+ - Avalanche clues: repeated parent-owned sources, context/provider churn, route/data boundary churn, wrapper transport.
139
+ - Fiber clues: owner chain shows the value entering from above.
140
+
141
+ 3. **Check the boundary at the source**
142
+ - Did this render create new `children`, JSX, objects, arrays, callbacks, or context values?
143
+ - Avalanche clues: source updates are legitimate, but child collateral is broad and stable.
144
+ - Fiber clues: owner/prop chain shows identity-only values crossing into children.
145
+
146
+ 4. **Check below the source**
147
+ - Did children have enough stable inputs to bail out?
148
+ - Avalanche clues: large repeated collateral with no local hook change in children.
149
+ - Fiber clues: child subtree does not depend on the changed field, but still renders.
150
+
151
+ 5. **Pick the highest fix that removes the most waste**
152
+ - If the source should not update, fix above.
153
+ - If the source must update but children should not, fix boundary or children.
154
+ - Prefer the highest stable boundary that stops propagation cleanly.
155
+
156
+ ### What to inspect above vs boundary vs below
157
+
158
+ **Above the source**
159
+ - Parent effects that dispatch on every render or on unstable deps.
160
+ - Provider values recreated every render.
161
+ - Store selectors returning new references for equal data.
162
+ - Wrapper layers transporting new identities into the source.
163
+
164
+ **At the boundary**
165
+ - Inline object/array literals.
166
+ - Inline JSX nodes or mapped children rebuilt every render.
167
+ - Fresh callbacks passed into memoized children.
168
+ - Context `value={...}` rebuilt in render.
169
+
170
+ **Below the source**
171
+ - Missing `React.memo` at subtree root.
172
+ - Memo boundary exists but props are unstable.
173
+ - Child derives large props every render instead of memoizing.
174
+ - Child consumes broad context/store state when it only needs a narrow field.
175
+
176
+ ### Fiber + avalanche together
177
+
178
+ Use avalanche to locate **where the wave starts and how far it travels**.
179
+ Use fiber/probes to locate **why a value crosses a boundary and who could stop it**.
180
+
181
+ - Avalanche answers: trigger, repeat count, collateral breadth, penetration depth.
182
+ - Fiber answers: owner chain, prop names, prop values, hook location, boundary composition.
183
+
184
+ Never stop at: "this component updated".
185
+ Next questions are always:
186
+ - why did its children receive work?
187
+ - which boundary failed to prune?
188
+ - would the cleanest fix be above, at, or below this component?
189
+
190
+ ### Single-component render causality
191
+
192
+ Avalanche can support single-component render reasoning, but only after the component is joined to a specific commit. Raw React metrics are population statistics; they do not name the cause for one component.
193
+
194
+ Ask the precise question:
195
+
196
+ ```text
197
+ Why did <Component> participate in commit N?
198
+ ```
199
+
200
+ Not:
201
+
202
+ ```text
203
+ Why does <Component> render in general?
204
+ ```
205
+
206
+ The same component may participate for different reasons across different commits: mount, parent propagation, local state, context, external store, remount, or discarded/restarted work.
207
+
208
+ #### Required evidence before naming a cause
209
+
210
+ For a target component, collect:
211
+
212
+ 1. **Identity** — component name, file, line, key if repeated.
213
+ 2. **Commit** — the specific `N` where it participated.
214
+ 3. **Role** — source, collateral, hotspot, mount/remount, context/store participant, or restart/discarded work.
215
+ 4. **Commit join** — `commitStats[N]`, `sourcesByCommit[N]`, `atlByCommit[N]`, `contextUpdatesByCommit[N]`, and restart side fields for the same `N`.
216
+ 5. **Fiber flow** — owner chain, parent chain, props, hook count, and specific hook/prop values when needed.
217
+ 6. **Source proof** — hook deps, setter call site, provider value construction, selector identity, keys, or memo boundary in code.
218
+
219
+ No role, no cause. No commit, no cause. No source/fiber proof, only partial confidence.
220
+
221
+ #### Role → reasoning table
222
+
223
+ | Target role in commit `N` | Correct conclusion | Next proof |
224
+ |---|---|---|
225
+ | **Source** | The component has local update evidence in this commit | Map changed hook/source to hook call, setter, effect, store callback, or reducer path |
226
+ | **Collateral** | The component rendered because work propagated into it; it is probably a victim | Find causing source/parent/provider and inspect prop/context/memo boundary |
227
+ | **Hotspot** | The component appears often, but may be source or victim | Inspect per-commit role; do not fix by frequency alone |
228
+ | **Context participant** | A provider changed in the same commit and may have forced consumers | Verify the target consumes that context and inspect value diff for real data vs identity churn |
229
+ | **External store participant** | Store snapshot/subscription may have changed or restarted render | Inspect `useSyncExternalStore`, selector identity, snapshot equality, and restart fields |
230
+ | **Mount/remount** | This is not just a re-render; React created new fiber/component work | Inspect keys, conditional branches, route/layout swaps, component types created inside render |
231
+ | **Restart/discarded work** | React executed render work that may not have committed | Inspect `externalStoreRestartCount`, `priorityInterruptedWorkCount`, fiber interruptions, Suspense/store timing |
232
+
233
+ #### Cause categories
234
+
235
+ Use these cause statements only when their evidence is present:
236
+
237
+ | Cause | Evidence | Safe wording |
238
+ |---|---|---|
239
+ | Local state/reducer update | Target is source; changed hook maps to `useState`/`useReducer` | "Rendered because its own state/reducer changed." |
240
+ | Effect cascade | Source hook/effect dispatches repeatedly or deps are unstable | "Rendered because an effect scheduled an update from unstable deps." |
241
+ | Parent propagation | Target is collateral under a source/parent | "Rendered as collateral from parent/source work." |
242
+ | Prop identity change | Memoized target received changed prop/ref/children identity | "Could not bail out because `<prop>` changed identity." |
243
+ | Context propagation | Provider update in same commit; target consumes context | "Rendered because context value changed above it." |
244
+ | External store update | Store hook/source/restart evidence | "Rendered because its external-store snapshot changed or restarted." |
245
+ | Remount | Mount counters/key/tree replacement evidence | "Remounted due to key/tree/type change, not normal re-render." |
246
+ | Restart/preemption | Raw render executions exceed classified committed work with side fields | "React executed render work that was discarded/interrupted." |
247
+
248
+ #### Wording discipline
249
+
250
+ Bad:
251
+
252
+ ```text
253
+ Component X rendered 800 times.
254
+ ```
255
+
256
+ Good:
257
+
258
+ ```text
259
+ React executed 800 component render paths in this commit window.
260
+ Component X appeared as collateral in commit 42 from source Y.
261
+ The target had no local source evidence, so X is a victim unless prop/context proof says otherwise.
262
+ ```
263
+
264
+ Bad:
265
+
266
+ ```text
267
+ X is slow because it appears in hot sources.
268
+ ```
269
+
270
+ Good:
271
+
272
+ ```text
273
+ X is a hotspot across 12 commits. In the representative commit, X is collateral from Y.
274
+ Fix belongs above X or at the Y→X boundary, not inside X yet.
275
+ ```
276
+
277
+ #### Single-component report format
278
+
279
+ ```text
280
+ 📏 target: <Component> @ <file>:<line>
281
+ commit: <N>
282
+ role: source | collateral | hotspot | context | store | mount/remount | restart/discarded
283
+ commit shape: collateral=<n>, sources=<n>, restarts=<n>, contextUpdates=<n>
284
+ raw work: component renders=<hooks+classes>, DOM=<domWorkCount>, mounts=<componentMountWorkCount>
285
+ value/source path: <hook/prop/context/store> from <Owner/Parent/Provider> @ <file>:<line>
286
+ cause: <one sentence using the evidence-backed category>
287
+ fix layer: above source | at boundary | below source | no fix yet
288
+ confidence: confirmed | likely | partial
289
+ next gap: <unknown field, or "none">
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Pattern 1 — Context Provider emitting new value identity with equal data
295
+
296
+ **Signal:** `contextChanges[]` shows high `count` for one Provider. `changeSummary` says `"fields: ..."` or `"identity changed"` but values look the same.
297
+
298
+ **Why it happens:** The Provider's parent re-renders and constructs a new object/array literal inline:
299
+ ```jsx
300
+ // Bad — new object every render
301
+ <MyContext.Provider value={{ user, settings }}>
302
+ ```
303
+
304
+ **Fix: `useMemo` the context value.**
305
+ ```jsx
306
+ const ctxValue = useMemo(() => ({ user, settings }), [user, settings]);
307
+ <MyContext.Provider value={ctxValue}>
308
+ ```
309
+
310
+ **How to find the Provider:**
311
+ - `contextChanges[N].source.fileName` + `lineNumber` — open that file at that line.
312
+ - Look at the JSX returned by that component. Find the `<X.Provider value={...}>` call.
313
+ - If `value` is an inline literal, that is the bug.
314
+
315
+ **Verify:** Re-arm, reload, re-read `contextChanges[]`. Count for that Provider should drop to 0 or 1 (initial mount only).
316
+
317
+ ---
318
+
319
+ ## Pattern 2 — setState cascade (source → collateral blast)
320
+
321
+ **Signal:** One component appears in `sources[]` across many commits. Its `hookChanges[]` shows state flipping (often back and forth). Many `collaterals[]` with `causedBySourceId` pointing at it.
322
+
323
+ **Why it happens:** A `setState` fires on every render or in an effect with unstable deps, causing the component to re-render, which propagates to all its children.
324
+
325
+ **Sub-pattern A — unstable JSX passed as prop.**
326
+ Parent re-renders → creates new JSX node → child sees new `children` prop → child re-renders.
327
+ ```jsx
328
+ // Bad — new JSX identity every render
329
+ <Child items={data.map(d => <Row key={d.id} {...d} />)} />
330
+ ```
331
+ Fix: extract to a memoized variable or component:
332
+ ```jsx
333
+ const rows = useMemo(() => data.map(d => <Row key={d.id} {...d} />), [data]);
334
+ <Child items={rows} />
335
+ ```
336
+
337
+ **Sub-pattern B — effect with missing or unstable deps.**
338
+ ```jsx
339
+ // Bad — runs every render because `config` is new each render
340
+ useEffect(() => { setState(derive(config)); }, [config]);
341
+ ```
342
+ Fix: stabilize the dep (memoize `config`) or derive inside render without an effect.
343
+
344
+ **How to find the source:**
345
+ - Open `sources[N].source.fileName` + `lineNumber`.
346
+ - Read `hookChanges[N]` — which hook index, what changed.
347
+ - Match `hookIndex` to the component's hook chain (use `probe-hooks` if needed).
348
+ - Trace why that hook fires: look at its deps or the dispatch call site.
349
+
350
+ ---
351
+
352
+ ## Pattern 3 — Collateral blast with no data dependency (missing memo boundary)
353
+
354
+ **Signal:** A subtree appears in `collaterals[]` on every commit. The components have no local hook changes and are not data-dependent on what changed. The nearest common ancestor (`P3`) is the blast source.
355
+
356
+ **Why it happens:** A parent re-renders (for a legitimate reason) and pulls its entire subtree with it — even parts that don't care about the change. No `memo` boundary exists to stop the propagation.
357
+
358
+ **Fix: `React.memo` on the subtree root.**
359
+ ```jsx
360
+ // Wrap the expensive, stable subtree
361
+ const StablePanel = React.memo(function StablePanel({ config }) {
362
+ return <ExpensiveTree config={config} />;
363
+ });
364
+ ```
365
+
366
+ **Rules for memo boundaries:**
367
+ - Wrap at the highest stable point in the subtree, not deep inside it.
368
+ - Props passed to the memoized component must themselves be stable (primitives or memoized objects). Passing an inline `{}` or `[]` defeats the memo.
369
+ - If the memoized component receives callbacks, wrap those in `useCallback`.
370
+
371
+ **How to find where to place the boundary:**
372
+ - Place P1 on a collateral component. Run `probe-owners` — walk the owner chain upward.
373
+ - The memo boundary goes on the highest owner that does **not** need to re-render when the trigger fires.
374
+ - Verify: after adding `memo`, re-capture. The component should disappear from `collaterals[]`.
375
+
376
+ ---
377
+
378
+ ## Fiber reading — what you need before fixing anything
379
+
380
+ For any of the above patterns, you need to understand the fiber composition before touching code. The sequence:
381
+
382
+ 1. **`probe-dom <clientId> <probeId>`** — exact element and text. Orient.
383
+ 2. **`probe-owners <clientId> <probeId>`** — full owner chain with `propNames` at each level. Find where the value enters.
384
+ 3. **`probe-prop-value <clientId> <probeId> <propName> [ownerIndex]`** — confirm identity of the suspect value.
385
+ 4. **Open file at `source.fileName` + `lineNumber`** — never grep when probe has a location.
386
+ 5. **`probe-hooks <clientId> <probeId>`** — only if hook index from avalanche needs mapping to actual hook call.
387
+
388
+ `propNames` at each owner tells you what can flow in. `hookCount` warns you if spelunking local state will be expensive. `owners[N]` is coordinates into the owner chain — `ownerIndex 0` = immediate owner.
389
+
390
+ ---
391
+
392
+ ## Analysis modes — when the obvious commit is not enough
393
+
394
+ Use these modes when a full-page interactive capture is noisy or the largest commit looks inevitable. They are lenses over the same Avalanche data, not new commands. If one capture spans too many waves to hold in context, use paginated subagent analysis from [avalanche-detector](avalanche-detector.md) and join findings before choosing a fix.
395
+
396
+ ### Mode: noise reduction
397
+
398
+ Goal: reduce the small repeated birds so the real signal is easier to see.
399
+
400
+ Use when:
401
+ - the main commit is large but mostly legitimate work
402
+ - many later commits repeat small sources, effects, callbacks, memos, or context updates
403
+ - the page feels busy after the obvious interaction should be done
404
+
405
+ Process:
406
+
407
+ ```text
408
+ 1. Accept that the big commit may be necessary.
409
+ 2. Inspect commits after the main wave.
410
+ 3. Rank by repeated sources, `effects`, `memos`, `callbacks`, `contextUpdates`, or offline `repeats`.
411
+ 4. Pick one small repeated source/provider.
412
+ 5. Ask whether it represents real data movement or identity/noise.
413
+ 6. Fix one noisy bird, then re-capture to see what signal remains.
414
+ ```
415
+
416
+ Typical fixes:
417
+ - memoize provider values that recreate equal data
418
+ - stabilize callbacks/objects passed across memo boundaries
419
+ - narrow store/context selectors
420
+ - stop effects that dispatch because deps changed identity
421
+
422
+ Report as:
423
+
424
+ ```text
425
+ The large commit is not the first fix target. It contains necessary <mount/data/UI> work.
426
+ The reducible noise is commits <N..M>: repeated <source/provider/effect> caused by <identity/store/effect>.
427
+ Fixing that should make future captures cleaner and expose remaining real work.
428
+ ```
429
+
430
+ ### Mode: first principles
431
+
432
+ Goal: ask what should have been different earlier so the future wave was smaller.
433
+
434
+ Use when:
435
+ - a later commit is large but downstream of an earlier setup choice
436
+ - a provider/store/list/key/selector decision made the later wave inevitable
437
+ - you need an architectural fix, not a local patch inside the hot component
438
+
439
+ Process:
440
+
441
+ ```text
442
+ 1. Pick the later visible wave.
443
+ 2. Walk backward until the first source/provider/store/key/identity setup that made it happen.
444
+ 3. Ask what invariant should have held before the wave.
445
+ 4. Choose the earliest boundary that could have preserved that invariant.
446
+ 5. Verify by re-running the same interaction and checking later commits shrink or disappear.
447
+ ```
448
+
449
+ Examples:
450
+ - If commit 42 remounts a list because keys changed, the fix is the earlier key/data identity setup, not the row render body.
451
+ - If commit 42 blasts consumers because provider value changed identity, the fix is the provider value construction before the blast.
452
+ - If commits 43-47 continue because an effect sees fresh deps, the fix is the dependency source that existed before those effects fired.
453
+
454
+ Report as:
455
+
456
+ ```text
457
+ The future wave was caused by an earlier broken invariant: <invariant>.
458
+ Fix belongs before the hot commit at <source/boundary/provider/store setup>.
459
+ Expected verification: later commits lose <collateral/context/restart/repeat> signal.
460
+ ```
461
+
462
+ ---
463
+
464
+ ## PR discipline — one Provider / one source at a time
465
+
466
+ 400 context updates is a list of independent problems. Each fix is:
467
+
468
+ 1. Pick the highest-count Provider from `contextChanges` grouping.
469
+ 2. Classify (Pattern 1, 2, or 3).
470
+ 3. Fix exactly that one Provider or source. No bundling.
471
+ 4. Re-arm, reload, verify count drops.
472
+ 5. PR. Then next.
473
+
474
+ This keeps diffs reviewable and regressions attributable.
475
+
476
+ ---
477
+
478
+ ## Distinguishing shallow-equal churn from real data movement
479
+
480
+ Look at `changeSummary` and `prevValue` / `currValue` in `contextChanges[]`:
481
+
482
+ | Pattern | Signal | Verdict |
483
+ |---|---|---|
484
+ | Value changes on every commit, content identical | `"identity changed"` or `object → object` with stable semantics | Pure churn — fix with memoization or cached object identity |
485
+ | Value changes in registration-style fields | `"fields: placements"`, `"fields: targets"`, similar append/replace transport fields | Real data movement — skip for low-hanging pass |
486
+ | Value changes in content fields | `"fields: avatarImage"`, text, IDs, flags, payload data | Real data movement — skip unless proven shallow-equal after inspection |
487
+ | Value changes once, then stable | Single entry in `contextChanges` | Mount-time init — probably fine |
488
+ | Value changes on every keystroke/click | Many entries, content actually changes | Intentional reactivity — needs deeper refactor |
489
+
490
+ Rule: during the first perf pass, take only the shallow-equal / identity-only rows. Defer registration and transport churn to a separate refactor track.