@atlassian/chippy 0.0.0-stage → 0.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +188 -2
- package/bin/chippy.mjs +134 -0
- package/bin/pack.mjs +184 -0
- package/lib/avalanche.js +2570 -0
- package/lib/avalanche.js.map +7 -0
- package/lib/bootstrap.d.ts +1 -0
- package/lib/bootstrap.js +51 -0
- package/lib/bootstrap.js.map +7 -0
- package/lib/browser.js +15 -0
- package/lib/browser.js.map +7 -0
- package/lib/extension/avalanche.es.js +5396 -0
- package/lib/extension/avalanche.es.js.map +1 -0
- package/lib/extension/chippy-page-bridge.js +1833 -0
- package/lib/extension/inspect-react.js +216 -0
- package/lib/extension/layout-lab.js +1453 -0
- package/lib/extension/panel-dom.js +90 -0
- package/lib/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/LICENSE +201 -0
- package/lib/mcp-skill/README.md +150 -0
- package/lib/mcp-skill/SKILL.md +330 -0
- package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
- package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
- package/lib/mcp-skill/extension/avalanche.es.js +5396 -0
- package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
- package/lib/mcp-skill/extension/avalanche.iife.js +5397 -0
- package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
- package/lib/mcp-skill/extension/background.js +323 -0
- package/lib/mcp-skill/extension/chippy-page-bridge.js +1833 -0
- package/lib/mcp-skill/extension/content-script.js +167 -0
- package/lib/mcp-skill/extension/devtools-hook-shim.js +68 -0
- package/lib/mcp-skill/extension/early-bootstrap.js +47 -0
- package/lib/mcp-skill/extension/icon-128.png +0 -0
- package/lib/mcp-skill/extension/icon-16.png +0 -0
- package/lib/mcp-skill/extension/icon-32.png +0 -0
- package/lib/mcp-skill/extension/icon-48.png +0 -0
- package/lib/mcp-skill/extension/inspect-react.js +216 -0
- package/lib/mcp-skill/extension/layout-lab.js +1453 -0
- package/lib/mcp-skill/extension/logo.svg +14 -0
- package/lib/mcp-skill/extension/manifest.json +69 -0
- package/lib/mcp-skill/extension/panel-dom.js +90 -0
- package/lib/mcp-skill/extension/panel-settings.js +191 -0
- package/lib/mcp-skill/extension/popup.html +41 -0
- package/lib/mcp-skill/extension/popup.js +80 -0
- package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
- package/lib/mcp-skill/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/package.json +1 -0
- package/lib/mcp-skill/references/README.md +56 -0
- package/lib/mcp-skill/references/active-stimulus.md +85 -0
- package/lib/mcp-skill/references/avalanche-detector.md +731 -0
- package/lib/mcp-skill/references/bdd-router.md +310 -0
- package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
- package/lib/mcp-skill/references/command-output-formats.md +1075 -0
- package/lib/mcp-skill/references/command-surface.md +183 -0
- package/lib/mcp-skill/references/cynefin.md +99 -0
- package/lib/mcp-skill/references/debug-hook.md +161 -0
- package/lib/mcp-skill/references/fixing-performance.md +490 -0
- package/lib/mcp-skill/references/hook-metrics.md +199 -0
- package/lib/mcp-skill/references/hooks-inspection.md +61 -0
- package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
- package/lib/mcp-skill/references/mcp.md +122 -0
- package/lib/mcp-skill/references/mode-a-injection.md +68 -0
- package/lib/mcp-skill/references/operating-model.md +128 -0
- package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
- package/lib/mcp-skill/references/playwright-probes.md +205 -0
- package/lib/mcp-skill/references/procedures.md +545 -0
- package/lib/mcp-skill/references/recipes.txt +114 -0
- package/lib/mcp-skill/references/stability-render.md +428 -0
- package/lib/mcp-skill/references/test-mode-playbook.md +193 -0
- package/lib/mcp-skill/references/troubleshooting.md +54 -0
- package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
- package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
- package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
- package/lib/mcp-skill/scripts/chippy-helper-server.js +1710 -0
- package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
- package/lib/mcp-skill/scripts/chippy.sh +1218 -0
- package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
- package/lib/mcp-skill/scripts/helper-access.js +144 -0
- package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
- package/lib/mcp-skill/scripts/search-recipes.js +248 -0
- package/lib/mcp-skill/snippets/README.md +89 -0
- package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
- package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
- package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
- package/lib/mcp-skill/snippets/find-prop.js +83 -0
- package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
- package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
- package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
- package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
- package/lib/package.json +1 -0
- package/lib/plugin.js +119 -0
- package/lib/plugin.js.map +7 -0
- package/lib/storybook/avalanche-panel-model.js +79 -0
- package/lib/storybook/brand.jsx +6 -0
- package/lib/storybook/connection-fallback.jsx +47 -0
- package/lib/storybook/constants.js +15 -0
- package/lib/storybook/context-card.jsx +171 -0
- package/lib/storybook/manager.jsx +403 -0
- package/lib/storybook/preset.js +7 -0
- package/lib/storybook/preview.js +263 -0
- package/lib/storybook/probe-annotation-editor.jsx +52 -0
- package/lib/storybook/probe-intent.js +23 -0
- package/lib/storybook/setup.js +93 -0
- package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
- package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
- package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
- package/lib/types/avalanche/detector.d.ts +183 -0
- package/lib/types/avalanche/interaction-capture.d.ts +25 -0
- package/lib/types/avalanche/internals.d.ts +189 -0
- package/lib/types/avalanche/report-serializer.d.ts +283 -0
- package/lib/types/avalanche/reporter.d.ts +610 -0
- package/lib/types/chippy-bridge/fiber-source.d.ts +37 -0
- package/lib/types/chippy-bridge/react-renderer.d.ts +49 -0
- package/lib/types/chippy-bridge/source-map.d.ts +30 -0
- package/lib/types/debug-render/core.d.ts +72 -0
- package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
- package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
- package/lib/types/layout-lab/marker-context.d.ts +94 -0
- package/lib/types/package/avalanche.d.ts +6 -0
- package/lib/types/package/browser.d.ts +10 -0
- package/lib/types/package/plugin.d.ts +15 -0
- package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
- package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
- package/lib/types/plugins/index.d.ts +49 -0
- package/lib/types/plugins/jira/context-provider.d.ts +36 -0
- package/package.json +104 -4
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Hook metrics — `setStateCount` + the hook-update tracker
|
|
2
|
+
|
|
3
|
+
Reference for the two snippets that consume the react-dom patch's hook metrics:
|
|
4
|
+
`hook-metrics` (read-only) and `hook-tracker` (drives the `onHookUpdate` callback).
|
|
5
|
+
They belong to the ATL plugin: they read the ATL adapter's `__ATL_REACT_METRICS`
|
|
6
|
+
slot and need Atlassian's patched `react-dom` to report anything.
|
|
7
|
+
|
|
8
|
+
These are the **why** layer under Avalanche. Avalanche tells you *what* re-rendered and
|
|
9
|
+
*how much collateral* a commit caused. Hook metrics tell you *why a component's work
|
|
10
|
+
repeated*: how many state dispatches were scheduled, and which memo/callback/effect slots
|
|
11
|
+
recomputed because their deps actually changed. ~99% of the time you reach for these
|
|
12
|
+
**after** an Avalanche capture has already named a hot component.
|
|
13
|
+
|
|
14
|
+
## What the patch emits
|
|
15
|
+
|
|
16
|
+
The react-dom patch adds exactly two hook-related surfaces to the host metrics object
|
|
17
|
+
`__ATL_REACT_METRICS`:
|
|
18
|
+
|
|
19
|
+
| Surface | Kind | Meaning | Where it fires |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `setStateCount` | plain counter | `useState`/`useReducer` **dispatches scheduled** | `dispatchSetState` / reducer dispatch, bumped unconditionally (`m.setStateCount++`) |
|
|
22
|
+
| `onHookUpdate(fiber, kind, slot)` | callback slot | one memo/callback/effect **recomputed because its deps changed** | update path only, *after* the deps-equality check — never on mount, never when deps were equal |
|
|
23
|
+
|
|
24
|
+
Hook kinds are numeric on the hot path: **`1 = memo`, `2 = callback`, `3 = effect`**.
|
|
25
|
+
|
|
26
|
+
**The metrics object is host-owned.** The patch never creates it and never stores hook
|
|
27
|
+
history — it only *bumps a counter* and *calls a callback if one is present*. This is the
|
|
28
|
+
core distinction from Avalanche:
|
|
29
|
+
|
|
30
|
+
- **Avalanche** installs everything for itself (`window.__AVALANCHE__`, its own detector).
|
|
31
|
+
- **Hook metrics** are emitted by the patched runtime into a slot the *app* owns. Chippy
|
|
32
|
+
**reads** `setStateCount`, and **installs one thing** — the `onHookUpdate` callback — the
|
|
33
|
+
same way the patch's own `hookTracker.ts` is meant to be installed independently.
|
|
34
|
+
|
|
35
|
+
### `setStateCount` caveats
|
|
36
|
+
|
|
37
|
+
- The host may seed `setStateCount` as `null`. The patch then does `null++` → `NaN`. Once
|
|
38
|
+
numeric it is monotonic-per-run. `hook-metrics` reports `setStateCountValid:false` and a
|
|
39
|
+
note when it sees a non-finite value instead of pretending it is a clean counter.
|
|
40
|
+
- Some hosts **wipe** `__ATL_REACT_METRICS` to
|
|
41
|
+
`undefined` after hydration. `hook-metrics` reports `present:false, wiped:true`. The
|
|
42
|
+
`hook-tracker install` action **seeds a minimal object** in that case so the tracker can
|
|
43
|
+
still own `onHookUpdate` — it never overwrites an existing host object.
|
|
44
|
+
|
|
45
|
+
## `hook-metrics` — read-only
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
./scripts/chippy.sh eval-page <clientId> hook-metrics
|
|
49
|
+
./scripts/chippy.sh eval-page <clientId> hook-metrics --keys setStateCount,contextUpdateCount,totalCommitCount
|
|
50
|
+
./scripts/chippy.sh eval-page <clientId> hook-metrics --raw # every own key, functions tagged
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Returns `{ present, wiped, setStateCount, setStateCountValid, onHookUpdateInstalled, counters:{…} }`.
|
|
54
|
+
Use it to read the current dispatch volume without installing anything. If `present:false`,
|
|
55
|
+
the host hasn't created the object yet (or wiped it) — use `hook-tracker install` to own the
|
|
56
|
+
slot regardless.
|
|
57
|
+
|
|
58
|
+
## `hook-tracker` — drive the hook-update tracker
|
|
59
|
+
|
|
60
|
+
State persists across calls on `window.__CHIPPY_HOOK_TRACKER`, because install / reproduce /
|
|
61
|
+
read / uninstall are separate `eval-page` invocations.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
CID=<clientId>
|
|
65
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action install # own onHookUpdate, mode=counts
|
|
66
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action mode --mode debug # carry fiber refs for source locations
|
|
67
|
+
# ... reproduce the interaction (user gesture, or Playwright clicks) ...
|
|
68
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action counts # source of truth, compact
|
|
69
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action histogram --buckets 3,6,11
|
|
70
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action offenders --threshold 10
|
|
71
|
+
./scripts/chippy.sh eval-page $CID hook-tracker --action uninstall # restore slot (only if still owned)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Actions
|
|
75
|
+
|
|
76
|
+
| Action | Effect |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `install` | seed metrics if absent, set `onHookUpdate = record`, mode → `counts` |
|
|
79
|
+
| `mode --mode off\|counts\|debug` | `off` no-ops; `counts` aggregates; `debug` additionally keeps the last fiber per type so `offenders` can report source file:line |
|
|
80
|
+
| `status` | installed?, mode, whether we still own the slot, entry count, current `setStateCount` |
|
|
81
|
+
| `counts` | per component-type: `{ totalUpdates, activeSlots, slots:[{slot,count,kind}] }`, sorted |
|
|
82
|
+
| `histogram --buckets a,b,c` | DB-safe: for each kind, count of `type:slot` identities whose total reached each bucket |
|
|
83
|
+
| `offenders --threshold N [--top M]` | dev-only: slots with `count > N`, carrying component name + source (needs `debug` mode) |
|
|
84
|
+
| `reset` | clear counts, stay installed |
|
|
85
|
+
| `uninstall` | restore `onHookUpdate` **only if we still own it**; retains last counts for a final read |
|
|
86
|
+
|
|
87
|
+
### Reading the numbers
|
|
88
|
+
|
|
89
|
+
- Counts aggregate by **component type**, not per instance — 100 rows of the same list item
|
|
90
|
+
collapse into one entry. This is intentional (type-level hotspots), matching `hookTracker.ts`.
|
|
91
|
+
- `counts[slot]` is how many times that hook slot recomputed. `type[slot]` is the numeric kind.
|
|
92
|
+
- **Use `counts`/`histogram` in prod-like runs; `offenders` only in dev**, since minified
|
|
93
|
+
bundles have no readable hook ids or source.
|
|
94
|
+
- A slot with a high count = a memo/callback/effect whose **deps keep changing**. That is
|
|
95
|
+
dependency churn, and it is usually an unstable identity upstream (a new object/array/
|
|
96
|
+
function created every render), not a bug in the memo itself.
|
|
97
|
+
|
|
98
|
+
## Lighthouse numbers vs Avalanche — the cost argument
|
|
99
|
+
|
|
100
|
+
The most useful framing for these metrics is **lighthouse numbers**: cheap integer
|
|
101
|
+
counters you could sample on every hydration and every route transition and forward to a
|
|
102
|
+
dashboard, for *real users in realtime*.
|
|
103
|
+
|
|
104
|
+
- **Avalanche** hooks React at a low level (`onCommitFiberRoot` + full fiber walk per
|
|
105
|
+
commit). It sees the whole picture — collateral, penetration depth, context waves,
|
|
106
|
+
unmount branches — but that instrumentation is far too expensive to leave on for every
|
|
107
|
+
user. It is an *on-demand debugger*.
|
|
108
|
+
- **The patch's metrics** are just `count++` inline in react-dom plus one optional
|
|
109
|
+
callback. Reading `setStateCount` is reading an integer. That is cheap enough to ship.
|
|
110
|
+
|
|
111
|
+
So the two are not competitors — they sit at opposite ends of a cost/detail axis:
|
|
112
|
+
|
|
113
|
+
| | `setStateCount` / hook-tracker | Avalanche |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| Cost | integer read / one callback | full fiber walk per commit |
|
|
116
|
+
| Detail | counts only (no tree, no collateral) | complete commit graph |
|
|
117
|
+
| Realtime / all-users | yes (lighthouse) | no (on-demand only) |
|
|
118
|
+
| Answers | "how much settling happened" | "what re-rendered and how far it spread" |
|
|
119
|
+
|
|
120
|
+
## `hook-metrics-sample` — labeled before/after delta in one call
|
|
121
|
+
|
|
122
|
+
The "lighthouse number" as a first-class read. Sample the counter, optionally click a
|
|
123
|
+
`data-testid`, sample again, return the delta:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
./scripts/chippy.sh eval-page <clientId> hook-metrics-sample --label hydration --settleMs 500
|
|
127
|
+
./scripts/chippy.sh eval-page <clientId> hook-metrics-sample --label tick --click tick --clicks 4
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Returns `{ available, label, before, after, delta, clicked, settleMs }`. **Metric-optional:**
|
|
131
|
+
if the patch is absent / wiped / `NaN` it returns `{ available:false, reason }` and never
|
|
132
|
+
throws.
|
|
133
|
+
|
|
134
|
+
## Avalanche integration — `setStateCount` per commit
|
|
135
|
+
|
|
136
|
+
When the react-dom patch is present, Avalanche now records `setStateCount` **per commit** as
|
|
137
|
+
an optional field on the ATL commit delta (`ATLCommitDelta.setStateCount`,
|
|
138
|
+
`ATLCommitDeltaDTO.setStateCount`). Read it with:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
./scripts/chippy.sh avalanche-commit-peek <clientId> <commitId> atl
|
|
142
|
+
# → "atl": { ..., "setStateCount": 12 }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Optional and self-degrading.** It is computed only when `__ATL_REACT_METRICS` is live at
|
|
146
|
+
commit time (`atlDeltaAvailable`), and the delta line is omitted entirely when neither
|
|
147
|
+
snapshot carries a numeric `setStateCount` — so on an unpatched host the field is simply
|
|
148
|
+
absent, never a bogus `0`. Every other ATL delta field can be `null` while `setStateCount`
|
|
149
|
+
is a real number; they are independent.
|
|
150
|
+
|
|
151
|
+
A sequence such as **1, 12, 1** distinguishes one top-level dispatch, a batched effect or
|
|
152
|
+
store wave, and another single dispatch. Treat the per-commit value as a *commit-shape*
|
|
153
|
+
signal: it distinguishes a burst of state dispatches from a single top-level update, but it
|
|
154
|
+
does not locate their source.
|
|
155
|
+
|
|
156
|
+
**Does it aid source detection?** Modestly, and only as a *disambiguator*, not a locator.
|
|
157
|
+
Avalanche already names the source component and `nearestTestId`. `setStateCount` per commit
|
|
158
|
+
adds one axis: a commit with `performedWork` high but `setStateCount` low = re-render driven
|
|
159
|
+
from *above* (props/context), not local state; high `setStateCount` = the work originated
|
|
160
|
+
from state dispatches in that commit window. It narrows *which kind* of trigger to chase; it
|
|
161
|
+
does not by itself point at a file. Treat it as a tie-breaker on top of the owner chain,
|
|
162
|
+
never a replacement for it. **And because the patch is optional, never build a source-tracing
|
|
163
|
+
procedure that requires it** — it is an enrichment when present, nothing when absent.
|
|
164
|
+
|
|
165
|
+
## What this diagnoses (and how it pairs with Avalanche)
|
|
166
|
+
|
|
167
|
+
| Symptom | Metric read | Interpretation | Next |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| Component renders far more than its parent | `setStateCount` climbing on one interaction | high state-dispatch volume — often a store/effect firing setState in a loop | trace the dispatcher; check for setState-in-render or effect cascades |
|
|
170
|
+
| Avalanche shows a hot source, unclear why | `hook-tracker counts` on that type | one slot thrashing vs many slots each once | one slot → chase that dep's identity; many slots → the whole component re-runs (parent/props identity) |
|
|
171
|
+
| Suspected unstable memoization | `offenders` (debug) | a `memo`/`callback` slot with count ≈ render count | the memo's deps are unstable — fix identity **above** the boundary (see [fixing-performance](fixing-performance.md)) |
|
|
172
|
+
| Effect running too often | `histogram` effect column | count of effect identities reaching each bucket | effect deps churn → stabilize deps or split the effect |
|
|
173
|
+
| Type-level hotspot across many instances | `counts` aggregation | one component type dominates total updates | fix once at the type, not per instance |
|
|
174
|
+
|
|
175
|
+
**Lifecycle placement** (see [evidence and edit gates](../SKILL.md#evidence-and-edit-gates)):
|
|
176
|
+
|
|
177
|
+
1. **Triage** with Avalanche (`avalanche-hot-sources`, `avalanche-commit-list`) → candidate hot component.
|
|
178
|
+
2. **Attribution**: `hook-tracker install` → reproduce → `counts` on that component type →
|
|
179
|
+
is it *one slot thrashing* (dep churn) or *the whole component re-running* (identity/props)?
|
|
180
|
+
3. **Experiment**: after a fix, `hook-tracker reset` → reproduce → `counts` again, or use
|
|
181
|
+
[stability-render](stability-render.md) to verify without a user gesture. Same signal,
|
|
182
|
+
before/after.
|
|
183
|
+
4. **`uninstall`** to hand the slot back.
|
|
184
|
+
|
|
185
|
+
## Anchor contract
|
|
186
|
+
|
|
187
|
+
`hook-tracker` does **not** mutate product state, hooks, or props. It only:
|
|
188
|
+
|
|
189
|
+
- reads `setStateCount` (a counter the patch already writes), and
|
|
190
|
+
- **owns `onHookUpdate`** — a slot the host leaves for exactly this purpose.
|
|
191
|
+
|
|
192
|
+
What becomes opaque while installed: any *other* consumer of `onHookUpdate` is displaced
|
|
193
|
+
until `uninstall`. `uninstall` restores the slot **only if Chippy still owns it** (it checks
|
|
194
|
+
`onHookUpdate === record`), so a host that re-seeded the object after install is not clobbered.
|
|
195
|
+
If the host wiped the object and `install` had to seed one, that seeded object persists after
|
|
196
|
+
uninstall with `onHookUpdate = undefined` — harmless, and re-wiped on the next host reset.
|
|
197
|
+
|
|
198
|
+
Always `uninstall` when the investigation is done, and `reset` between reproduction runs so
|
|
199
|
+
counts from an earlier gesture don't bleed into the next measurement.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Hooks inspection
|
|
2
|
+
|
|
3
|
+
**To inspect hooks** — `probe-hooks <clientId> <probeId>`:
|
|
4
|
+
- Returns an array of hook nodes walked from `fiber.memoizedState`.
|
|
5
|
+
- Tags: `State`, `Reducer`, `Ref`, `Effect`, `Memo`, `Callback`, `Context?`, `Deferred`, `Unknown`.
|
|
6
|
+
- `State` → `value` is the current state value. `Reducer` → `value` is the last rendered state.
|
|
7
|
+
- `Ref` → `current` is the ref value.
|
|
8
|
+
- `Effect` → `deps` count + `hasCleanup`.
|
|
9
|
+
- `Memo` / `Callback` → `value` + `deps` count.
|
|
10
|
+
- Walks the full hook list up to a high safety cap (`maxHooks: 500`). If the response stops at that cap, `truncated: true` tells you.
|
|
11
|
+
- POD values inlined; objects/arrays/functions safe-serialized.
|
|
12
|
+
- If the hook list is malformed or cyclic, Chippy stops deterministically and marks the break in the output.
|
|
13
|
+
- Hook index is exact within the returned list.
|
|
14
|
+
- No hidden mini-cap for Jira-scale components.
|
|
15
|
+
- Hook output is still for inspection, not for counting business data.
|
|
16
|
+
- Re-run after code changes if you need fresh positions.
|
|
17
|
+
- Use `hookCount`, `truncated`, and `maxHooks` together when judging completeness.
|
|
18
|
+
- If `truncated` is false, the full visible hook chain was walked.
|
|
19
|
+
- If `truncated` is true on a real component, the component is unusually deep and worth noting explicitly.
|
|
20
|
+
- The cap is safety, not guidance.
|
|
21
|
+
- Deep Jira fibers are expected to work.
|
|
22
|
+
- Large outputs may still be verbose; inspect around known anchors.
|
|
23
|
+
- Prefer a nearby `State`/`Ref`/`useDebugValue` landmark when scanning hundreds of hooks.
|
|
24
|
+
- Counts can shift between renders.
|
|
25
|
+
- The list reflects one render snapshot only.
|
|
26
|
+
- This is deterministic from the exact probed fiber.
|
|
27
|
+
- If the page rerenders, probe again.
|
|
28
|
+
- The returned `hookCount` is the number of entries emitted in this result.
|
|
29
|
+
- For capped responses, `hookCount` equals `maxHooks`.
|
|
30
|
+
- For cycle breaks, one final synthetic `Unknown` entry may be appended with a note.
|
|
31
|
+
- This keeps failures inspectable instead of silent.
|
|
32
|
+
- The cap exists only to guard against broken or pathological hook lists.
|
|
33
|
+
- Jira with 300 hooks is fine.
|
|
34
|
+
- Very large components are noisy, but now observable.
|
|
35
|
+
- Use the index numbers as exact anchors.
|
|
36
|
+
- Do not infer missing hooks unless `truncated` says they are missing.
|
|
37
|
+
- When in doubt, call `probe-hooks` again after the next render settles.
|
|
38
|
+
|
|
39
|
+
Note: if you hit `truncated: true` at 500 in practice, that is a signal to improve the command surface further, not to guess past the boundary.
|
|
40
|
+
|
|
41
|
+
**Result shape adds**:
|
|
42
|
+
- `hookCount` — number of returned hook entries
|
|
43
|
+
- `truncated` — whether Chippy stopped at the safety cap
|
|
44
|
+
- `maxHooks` — current safety cap used for this walk
|
|
45
|
+
- `hooks[N].note` — optional note on synthetic break entries such as cycle detection
|
|
46
|
+
|
|
47
|
+
**Reading very deep results**:
|
|
48
|
+
- Default reading is **collapsed by repetition**: group consecutive hooks belonging to the same custom-hook family (e.g. `useSweetState [12..16]`) instead of listing each leaf. Expand a family fully when it is the only instance, or when the user explicitly asks for the raw list (`dump`, `expanded`, `every hook`).
|
|
49
|
+
- For "where exactly is complexity" asks, produce a concentration view first: hook ranges, top-level custom-hook/subtree label, primitive counts, cleanup counts, and landmarks such as `State`, `Ref`, or `useDebugValue`.
|
|
50
|
+
- find a stable hook anchor first (`State` value, `Ref.current`, `useDebugValue`)
|
|
51
|
+
- then use surrounding indexes to orient
|
|
52
|
+
- do not treat raw length alone as meaningful
|
|
53
|
+
- the useful signal is where a known value sits in the deterministic sequence
|
|
54
|
+
|
|
55
|
+
**Safety rule**:
|
|
56
|
+
- the hook walker should prefer explicit truncation over hanging, looping, or pretending the list ended naturally
|
|
57
|
+
- deterministic failure beats silent loss
|
|
58
|
+
|
|
59
|
+
**To locate a specific hook**, first use an existing `useDebugValue`, `State`, or `Ref` landmark and its neighboring indexes. If multiple source call sites still fit, report the ambiguity. Adding a new `useDebugValue` is a source edit and requires user intent plus the normal P-15/P-16 gate before changing the code.
|
|
60
|
+
|
|
61
|
+
**Hook index is stable within a render** but shifts if hooks are added/removed. Always re-run `probe-hooks` after code changes.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Jira-specific component taxonomy
|
|
2
|
+
|
|
3
|
+
Signals to keep and use when probing Jira-like production pages.
|
|
4
|
+
|
|
5
|
+
The `owner` boundary and Jira ownership fields appear only when the built-in Jira plugin is enabled. The wrapper names below apply to Jira pages whether or not it is.
|
|
6
|
+
|
|
7
|
+
## Probe Jira context
|
|
8
|
+
|
|
9
|
+
Chippy enriches a selected fiber with three collateral-scope boundaries. The extension/import panel renders them in-page; the Storybook addon renders the same three inside the matching marker card (feature parity — same data, host-specific rendering). It scans a bounded current/return/debug-owner path.
|
|
10
|
+
|
|
11
|
+
The three boundaries (`markers[id].context.boundaries = { owner, error, suspense }`) each carry a **`distance`** — the number of fiber hops from the probed fiber. Distance is the primary signal: it says how far each collateral boundary is without resolving it in full. Each block is **clickable**; a click reports the selected boundary (`{ markerId, boundaryKind, boundary, distance }`) to the backend as an `inspect-boundary` signal for later investigation (readable as `lastInspectedBoundary` on the client summary).
|
|
12
|
+
|
|
13
|
+
- **error** and **suspense** are **fiber-native React primitives**, detected generically on *any* React page (not just Jira). Error boundaries are found by React lifecycle (`getDerivedStateFromError` / `componentDidCatch`) with a name-heuristic fallback (`detectedBy: 'lifecycle' | 'name'`); Suspense by fiber tag 13. The error block shows the detailed fields; on Jira it is enriched from `JSErrorBoundary` props (`id`, `packageName`, `teamName`, `fallback`).
|
|
14
|
+
- **owner** is the Jira-specific semantic/team owner (`OwnerMark___<type>___<name>___<package>___<variant>`) — *not* the React render owner. It renders as a **short clickable lozenge** (name + distance only); its fields are intentionally not dumped because the OwnerMark commonly wraps the error boundary and would duplicate it.
|
|
15
|
+
- The nearest Suspense boundary in deep trees (e.g. Jira) can sit ~170+ fiber-hops above a leaf; the ancestry walk reaches it (ceiling 400) but early-exits once all three are found.
|
|
16
|
+
- This is live runtime metadata, not a repository or organizational lookup. Treat it as a production source hint and retain the existing confidence caveat when neither `_debugSource` nor a usable React 19 `_debugStack` source is available.
|
|
17
|
+
- Use `context-ui <clientId> off` to hide the boundary cards for the current page, and `context-ui <clientId> on` to show them again. This applies to both in-page and Storybook marker cards; generic probe data remains available in either state.
|
|
18
|
+
|
|
19
|
+
## `OwnerMark___<type>___<name>___<package>___<variant>`
|
|
20
|
+
Declarative boundary marker near error/lazy boundaries. Often the closest thing to a source name in production.
|
|
21
|
+
|
|
22
|
+
## `Connect(X)`
|
|
23
|
+
Redux wrapper. The wrapped name is the real component.
|
|
24
|
+
|
|
25
|
+
When reading avalanche traces:
|
|
26
|
+
- `Connect(X)` is usually transport, not ownership.
|
|
27
|
+
- Promote `X` or the nearest Jira-owned child/self-updater before ranking it as a source.
|
|
28
|
+
- If the trace only names `Connect(X)`, use its wrapped name as the business component label.
|
|
29
|
+
|
|
30
|
+
## `ComponentWithCondition[]`
|
|
31
|
+
Feature-gate branch marker.
|
|
32
|
+
|
|
33
|
+
When reading avalanche traces:
|
|
34
|
+
- Ignore as a primary source.
|
|
35
|
+
- Treat as path metadata only: it explains which gated branch is active.
|
|
36
|
+
- Promote the nearest Jira-owned parent or child with concrete `whatChanged` / `hookChanges` evidence.
|
|
37
|
+
- Broad collateral under this node does not mean the gate owns the update.
|
|
38
|
+
|
|
39
|
+
## `WithAnalyticsEvents(X)` / `WithAnalyticsContext(X)` / `WithAnalytics(X)`
|
|
40
|
+
Analytics wrappers. Usually structural noise.
|
|
41
|
+
|
|
42
|
+
## `JiraUFOSegmentWrapper` → `SsrRenderProfiler` → `UFOSegment`
|
|
43
|
+
Performance instrumentation seam. Useful as a feature boundary marker.
|
|
44
|
+
|
|
45
|
+
## `SuspensePlaceholder` → `SuspenseWrapper` → `OwnerMark___placeholder___X`
|
|
46
|
+
Lazy-loaded section marker.
|
|
47
|
+
|
|
48
|
+
When reading avalanche traces:
|
|
49
|
+
- `SuspensePlaceholder` is not a real business source.
|
|
50
|
+
- It artificially holds or releases child propagation during lazy/suspense transitions.
|
|
51
|
+
- Treat as environment/path metadata unless no Jira-owned source exists above or below it.
|
|
52
|
+
- Large collateral here usually means the subtree beneath it woke up; inspect the nearest Jira-owned child/self-updater next.
|
|
53
|
+
|
|
54
|
+
## `Safe(X)`
|
|
55
|
+
Crash-safe wrapper.
|
|
56
|
+
|
|
57
|
+
## `Container(X)`
|
|
58
|
+
SweetState/local shared state boundary. Strong hint for data origin.
|
|
59
|
+
|
|
60
|
+
When reading avalanche traces:
|
|
61
|
+
- Higher-value source candidate than wrappers/placeholders.
|
|
62
|
+
- If `Container(X)` is a self-updater with concrete field changes, keep it in the suspect list.
|
|
63
|
+
|
|
64
|
+
## `ConnectedXxxRelayWithSuspense`
|
|
65
|
+
Relay/GraphQL data boundary.
|
|
66
|
+
|
|
67
|
+
When reading avalanche traces:
|
|
68
|
+
- Often a real data boundary, not just noise.
|
|
69
|
+
- Keep when export shows concrete field/store changes or repeated self-updates.
|
|
70
|
+
- If only suspense transport is visible, keep drilling to the Jira-owned child.
|
|
71
|
+
|
|
72
|
+
## Avalanche ranking rules for Jira
|
|
73
|
+
- Filter helper nodes first: feature gates, placeholders, analytics wrappers, profiler seams.
|
|
74
|
+
- Do not equate blast radius with ownership. Broad hotspot can still be transport.
|
|
75
|
+
- Prefer the nearest Jira-owned self-updater with concrete `whatChanged` / `hookChanges` evidence.
|
|
76
|
+
- Repeated commits beat one broad placeholder event when picking real work.
|
|
77
|
+
- Parent/child environment still matters: keep helper nodes in notes to justify the branch shape, but not as the fix target.
|
|
78
|
+
- Before writing work items, normalize wrapper names to the business component that owns the update.
|
|
79
|
+
|
|
80
|
+
## Noise to skip
|
|
81
|
+
`AnalyticsContext`, `AnalyticsListener`, `GenericAnalyticsListener`, `SsrRenderProfiler`, `ExperienceTrackingProvider`, single-character minified HOCs, and similar non-structural wrappers.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Chippy over MCP
|
|
2
|
+
|
|
3
|
+
Use the local stdio MCP adapter when your agent host supports MCP tools. It starts
|
|
4
|
+
or reuses the helper automatically, so the agent does not need a shell command to
|
|
5
|
+
start a background server. The browser still needs the extension, module import,
|
|
6
|
+
or Storybook integration.
|
|
7
|
+
|
|
8
|
+
## Connect your agent
|
|
9
|
+
|
|
10
|
+
Follow these steps when the user asks you to connect Chippy over MCP. They apply
|
|
11
|
+
to any agent host that supports stdio MCP servers.
|
|
12
|
+
|
|
13
|
+
1. Resolve the absolute path of the directory that contains this skill's
|
|
14
|
+
`SKILL.md`. It may be a user-level or a project-local install; do not assume
|
|
15
|
+
`~/.agents/skills/chippy`. Confirm that `scripts/chippy-mcp.cjs` exists there. Check that `node --version` reports 22.12 or later.
|
|
16
|
+
2. Register a stdio MCP server named `chippy` that runs `node` with that
|
|
17
|
+
absolute `scripts/chippy-mcp.cjs` path as its only argument. Use your host's
|
|
18
|
+
documented way to add an MCP server: its own command if it has one, otherwise
|
|
19
|
+
its MCP configuration file. Register it for the user, not one project, unless
|
|
20
|
+
the user asks otherwise.
|
|
21
|
+
3. Merge the entry into the existing configuration. Keep the other servers, and
|
|
22
|
+
update an existing `chippy` entry instead of adding a second one.
|
|
23
|
+
4. If your host loads MCP servers only at startup, tell the user to restart or
|
|
24
|
+
reload it.
|
|
25
|
+
5. Verify the connection with `chippy_status`. If the Chippy tools do not
|
|
26
|
+
appear, report what you changed and use `scripts/chippy.sh` until they do.
|
|
27
|
+
|
|
28
|
+
If your host has no MCP support, skip these steps and use `scripts/chippy.sh`.
|
|
29
|
+
|
|
30
|
+
## Server configuration
|
|
31
|
+
|
|
32
|
+
The server entry, in the common `mcpServers` JSON shape. Replace the example path
|
|
33
|
+
with the **absolute installed skill path**; JSON arguments do not expand `~`.
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"chippy": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["/absolute/path/to/chippy/scripts/chippy-mcp.cjs"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Node.js 22.12+ is required. The executable bundles its MCP dependencies and their
|
|
47
|
+
licenses; the installed skill needs no `npm install`. The Chippy npm library also
|
|
48
|
+
includes the `chippy-mcp` executable.
|
|
49
|
+
Host configuration syntax varies; the example shows the command and arguments
|
|
50
|
+
that the host must launch, not a file Chippy installs into your host.
|
|
51
|
+
|
|
52
|
+
Start with `chippy_status`, then `chippy_pages`. Select a page by URL and title,
|
|
53
|
+
read `chippy_snapshot`, and inspect the relevant probe or find components directly.
|
|
54
|
+
A ready helper with `browserConnected: false` means enable Chippy on a browser page,
|
|
55
|
+
not restart the server. The skill's replay, stimulus, evidence, and edit rules apply
|
|
56
|
+
to MCP calls exactly as they do to CLI calls.
|
|
57
|
+
|
|
58
|
+
## Helper lifecycle and sandbox behavior
|
|
59
|
+
|
|
60
|
+
- New helpers bind to `127.0.0.1:7823`. Set `CHIPPY_HELPER_PORT` in the MCP process
|
|
61
|
+
environment to use another port (integer 1–65535); configure the browser's helper
|
|
62
|
+
URL to match. Set `CHIPPY_HELPER_HOST` (for example `0.0.0.0`) only when the
|
|
63
|
+
browser must reach the helper over the network. The adapter always connects to
|
|
64
|
+
loopback on that port.
|
|
65
|
+
- A matching helper version is reused. An incompatible response or occupied port
|
|
66
|
+
produces a tool error; Chippy does not replace that process. Stop or upgrade it
|
|
67
|
+
explicitly, or choose another port.
|
|
68
|
+
- The helper is a persistent shared service. Closing the MCP host/session closes
|
|
69
|
+
the stdio adapter but leaves the helper, probes, and other sessions available.
|
|
70
|
+
Default-port helpers can be stopped with the existing `chippy.sh stop` when no
|
|
71
|
+
session needs them. Chippy does not kill other helpers on shutdown.
|
|
72
|
+
- `chippy_status` returns `helperUrl`, `startedPid` (the child started by this MCP
|
|
73
|
+
process, otherwise null), `lifetime: "persistent"`, `browserConnected`, `pages`,
|
|
74
|
+
`hint`, and `ok`. An empty page list is normal setup state.
|
|
75
|
+
- The MCP process needs host permission to launch Node and connect to the helper.
|
|
76
|
+
MCP avoids reliance on the agent's shell sandbox only when the host launches
|
|
77
|
+
MCP servers with suitable access. It cannot bridge an isolated container to a
|
|
78
|
+
browser without networking/forwarding supplied by the environment.
|
|
79
|
+
- Logs use stderr; stdout contains only MCP messages. Requests are cancellable.
|
|
80
|
+
Helper errors are returned as MCP tool errors, not as successful empty evidence.
|
|
81
|
+
|
|
82
|
+
The adapter reads the helper's access token from `~/.chippy/helper-<port>.token`
|
|
83
|
+
(or `$CHIPPY_STATE_DIR`) on each request, so it keeps working after a helper
|
|
84
|
+
restart. `CHIPPY_HELPER_TOKEN` overrides the file. Reusing an existing helper does
|
|
85
|
+
not change its listening address.
|
|
86
|
+
|
|
87
|
+
## Tools
|
|
88
|
+
|
|
89
|
+
All page tools require `clientId`. Resolve it afresh after navigation. Optional
|
|
90
|
+
`timeoutMs` defaults to 5000 and accepts integers 1–60000, except annotation waits,
|
|
91
|
+
which default to 30000. Structured results preserve the existing helper response
|
|
92
|
+
and are also included as JSON text.
|
|
93
|
+
|
|
94
|
+
| Tool | Arguments and behavior |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `chippy_status` | No arguments; starts/reuses helper and reports browser readiness. |
|
|
97
|
+
| `chippy_pages` | No arguments; lists connected pages. |
|
|
98
|
+
| `chippy_snapshot` | `clientId`, `refresh` (default false). Reads stored evidence; true requests an asynchronous refresh, so read again after the page responds. |
|
|
99
|
+
| `chippy_probe` | `clientId`, `probeId`, `view`, optional `ownerIndex`, `prop`, `parentLevel`, `max`, `timeoutMs`. Views: `dom`, `parents`, `boundaries`, `owners`, `props`, `prop-value`, `hooks`, `hooks-summary`. `prop-value` requires `prop`. `owners` uses the existing live-owner route and cached-snapshot fallback. |
|
|
100
|
+
| `chippy_find_fibers` | `clientId`, `filters`, optional `timeoutMs`. Filters: `name`, `file`, `prop`, `testid`, `text`, `max`. Returns current fibers; refresh ids after commits or HMR. |
|
|
101
|
+
| `chippy_inspect_fiber` | `clientId`, `fiberId`, `view`, optional `hookIndex`, `windowSize`, `max`, `timeoutMs`. Views: `props`, `owners`, `parents`, `boundaries`, `hooks`, `hooks-summary`, `structure`, `describe`, `explain`. `structure` requires `hookIndex`. Replay-capable views can execute component/hook bodies. |
|
|
102
|
+
| `chippy_avalanche` | `clientId`, `action`, optional fields described below. Uses the existing Avalanche command builder. |
|
|
103
|
+
| `chippy_interact` | `clientId`, `action`, `target`, optional `targetKind` (default `selector`; alternatives `probeId`, `fiberId`), `propName`, `args`, `ownerIndex`, `timeoutMs`. Actions: `click`, `prop-contract`, `invoke`, `schedule-update`. `targetKind` selects click resolution; other actions take a fiber id as `target`. `prop-contract` and `invoke` require `propName`; `invoke` forwards a JSON argument array. Name the stimulus before execution and prove callback contracts before invoking them. |
|
|
104
|
+
| `chippy_wait_annotation` | `clientId`, optional `timeoutMs` (default 30000, maximum 60000). Consumes one UI intent/comment, waits once, or returns normal `status: "timeout"`. Repeat only while the user-requested loop remains active; cancellation closes the HTTP long poll. |
|
|
105
|
+
|
|
106
|
+
Avalanche actions: `status`, `install`, `uninstall`, `clear`, `export`,
|
|
107
|
+
`export-last`, `hot-sources`, `commit-list`, `commit-peek`, `debug-hook`,
|
|
108
|
+
`describe-action`. `install` starts capture; `clear` resets evidence;
|
|
109
|
+
`uninstall` stops capture. Optional `options` contains `detectionMode`
|
|
110
|
+
(`stats`, `sources`, `amplifiers`, `full`), `historySize`, and `captureFibers`.
|
|
111
|
+
Analysis fields are `limit`, `count`, `top`, `by`, `commitId`, `include` (array),
|
|
112
|
+
`depth`, `maxItems`, `sourceId`, `hookIndex`, `window`, `actionId`, and `timeoutMs`.
|
|
113
|
+
Use captured ids for commit, hook, and action analysis. See the
|
|
114
|
+
[Avalanche reference](avalanche-detector.md) for result semantics.
|
|
115
|
+
|
|
116
|
+
Indices are nonnegative integers; limits, counts, maxima, and window sizes are
|
|
117
|
+
positive integers, except Avalanche's `window`, which permits zero. Fields apply
|
|
118
|
+
to their selected view/action; for example, owner selection does not apply to the
|
|
119
|
+
owner-chain list. These nine tools cover the main inspection, capture, and UI-intent
|
|
120
|
+
loop. The CLI remains available for recipes, snippets, reload/arm workflows,
|
|
121
|
+
state comparisons, and other commands not exposed here. MCP does not change those
|
|
122
|
+
contracts or modify agent-host lifecycle hooks.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Mode A injection contract — Playwright-controlled browser without the extension
|
|
2
|
+
|
|
3
|
+
Mode A is the Chippy/Avalanche injection path used when there is **no browser extension** — Playwright opens a browser, navigates a page, and the test runner is responsible for everything the extension would normally do in Mode B. Skipping a step silently degrades capture quality — usually shows up as `commitCount: 0`, `integrationMode: "none"`, or empty `commitStats`.
|
|
4
|
+
|
|
5
|
+
## When this applies
|
|
6
|
+
|
|
7
|
+
- Headless verification under Vitest/Playwright/CI
|
|
8
|
+
- Agent-side automation that opens its own Chromium
|
|
9
|
+
- Any environment where the Chippy extension is **not** installed in the browser
|
|
10
|
+
|
|
11
|
+
If the user is using their own browser with the extension, this contract does NOT apply — the extension's `webNavigation.onCommitted` + `chrome.scripting.executeScript` path handles everything below.
|
|
12
|
+
|
|
13
|
+
## Required bootstrap and install order
|
|
14
|
+
|
|
15
|
+
Mode A has two supported runtime-loading variants. Both end with the same explicit detector install.
|
|
16
|
+
|
|
17
|
+
1. **Install the DevTools hook shim before navigation.** Use `page.addInitScript({ content: DEVTOOLS_HOOK_SHIM })` before `page.goto(...)`.
|
|
18
|
+
React-DOM checks `__REACT_DEVTOOLS_GLOBAL_HOOK__` at module-evaluation time. Installing the shim after navigation = no `currentDispatcherRef` = `replayComponentHooks` collapses to fiber-only fallback. The shim file is shipped with the Chippy skill at `extension/devtools-hook-shim.js`; the helper serves it at `/__chippy/devtools-hook-shim.js` (load that contents into the init script).
|
|
19
|
+
2. **Load the Chippy and Avalanche runtimes using one variant:**
|
|
20
|
+
- **Packaged session loader:** call `installHelpers(page, { init: true })` before `page.goto(...)`. It registers the shipped helper scripts as init scripts, so they return on every navigation.
|
|
21
|
+
- **Ad-hoc / playwright-cli:** navigate first, then import `http://127.0.0.1:7823/__chippy/inject.mjs` (preferred), or add `inject.js` followed by `avalanche.iife.js`. The classic `inject.js` is intentionally split; bridge commands return `bridge module not loaded` until the IIFE is present.
|
|
22
|
+
3. **Wait for the page client in `chippy.sh pages`.** A loaded global is not connection proof.
|
|
23
|
+
4. **Run `chippy.sh avalanche-install <clientId>`.** Loading either runtime does not auto-install the detector in Mode A.
|
|
24
|
+
Auto-install is gated on `__CHIPPY_STARTUP_CONFIG.avalanche.autoStart`, which is written by the extension's MAIN-world script in Mode B. Neither Mode A loader writes that startup configuration, so both must trigger install explicitly.
|
|
25
|
+
|
|
26
|
+
## On reload (`page.reload(...)`)
|
|
27
|
+
|
|
28
|
+
- Init scripts survive reload. The packaged loader restores its runtimes automatically; the ad-hoc variant must load them again.
|
|
29
|
+
- Both variants receive a new client and detector instance. Re-resolve the client, then repeat the explicit install.
|
|
30
|
+
|
|
31
|
+
## Boot-time commit loss — known limitation
|
|
32
|
+
|
|
33
|
+
The ad-hoc `addScriptTag` variant loads the runtimes after page code. The packaged init-script variant loads them before page code, but its detector still remains uninstalled until the helper observes the new client and sends the explicit install.
|
|
34
|
+
|
|
35
|
+
Any React commit that fires between page mount and your `avalanche-install` call is **invisible to Avalanche**. This includes:
|
|
36
|
+
- initial mount
|
|
37
|
+
- `useEffect` cleanup teardowns
|
|
38
|
+
- Suspense fallback → content swaps
|
|
39
|
+
- route transitions on first paint
|
|
40
|
+
|
|
41
|
+
**Workaround:** stories under test must expose a manual trigger (e.g. `window.__triggerSwap()`) that you call AFTER `avalanche-install`. Document this trigger in the test fixture so the test reads end-to-end.
|
|
42
|
+
|
|
43
|
+
## Failure signatures
|
|
44
|
+
|
|
45
|
+
| Symptom | Likely cause |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `commitCount: 0`, `integrationMode: "none"` | explicit detector install was skipped, or no commit occurred afterward |
|
|
48
|
+
| `commitCount > 0` but `unmountedComponents: 0` despite expected unmounts | unmounts happened before detector install; trigger them post-install |
|
|
49
|
+
| `bridge module not loaded` from `find-fibers` / `describe-component` | classic bootstrap omitted `avalanche.iife.js` |
|
|
50
|
+
| `replayComponentHooks` returns flat fiber-only data, no custom-hook folders | DevTools hook shim was skipped or installed after navigation |
|
|
51
|
+
| `coverage > 100%` in `meta.memoizedUpdatersSupport` | early commits missed (this is the boot-time gap, not a wiring bug) |
|
|
52
|
+
|
|
53
|
+
## Verifying that capture worked
|
|
54
|
+
|
|
55
|
+
- `chippy.sh avalanche-status <clientId>` → expect `installed: true`, `commitCount > 0`, and `integrationMode` matching the host (`adapter:atl` for a patched Atlassian host, `devtools` otherwise; earlier releases report `atl`), never `none` after a captured commit.
|
|
56
|
+
- In `avalanche-export`, compare `meta.commits` vs `meta.memoizedUpdatersSupport.commitsWithData` — they should match. If `commitsWithData > meta.commits`, early commits were missed.
|
|
57
|
+
- For end-to-end behavior, build a CONTROL test (trigger after install — must capture) AND a GAP test (trigger before install — documents the limitation) per fixture.
|
|
58
|
+
|
|
59
|
+
## Mode B reference (for contrast)
|
|
60
|
+
|
|
61
|
+
In Mode B, the extension does the following automatically and silently for every navigation:
|
|
62
|
+
1. `webNavigation.onCommitted` fires
|
|
63
|
+
2. Background fetches `armedAvalanche` config from helper
|
|
64
|
+
3. `chrome.scripting.executeScript({ injectImmediately: true, world: 'MAIN' })` runs the early scripts (`devtools-hook-shim.js`, `avalanche.iife.js`, `early-bootstrap.js`) before any page script evaluates
|
|
65
|
+
4. iife.ts reads `__CHIPPY_STARTUP_CONFIG.avalanche.autoStart` and self-installs
|
|
66
|
+
5. `chrome.tabs.onUpdated` (`status === 'complete'`) injects late bundle scripts (bridge + probes)
|
|
67
|
+
|
|
68
|
+
The supported Mode A loaders do not provide the extension's pre-page `autoStart` configuration. They install only after client connection; that post-connection detector start is the structural reason for the boot-time gap.
|