@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,114 @@
|
|
|
1
|
+
# Map Chippy investigation tasks and failure signatures to executable actions, completion signals, guardrails, and canonical deeper contracts.
|
|
2
|
+
# One complete record per line supports direct rg/grep and streaming ranked search.
|
|
3
|
+
# Format: id=... | q=... | tags=... | a=... | done=... | guard=... | refs=...
|
|
4
|
+
|
|
5
|
+
# Connection and orientation
|
|
6
|
+
id=start-helper | q=How do I start Chippy? | tags=start boot helper server unavailable connection refused curl exit 7 | a=Run `chippy.sh start`, then `chippy.sh health`. The wrapper backgrounds the helper and returns its health payload. | done=`health` returns `ok: true`. | guard=Do not append `&`; the wrapper owns background startup. | refs=SKILL.md#start-every-task,references/troubleshooting.md
|
|
7
|
+
id=stop-helper | q=How do I stop the Chippy helper? | tags=stop shutdown helper lifecycle | a=Run `chippy.sh stop`. | done=The wrapper reports that the helper stopped or was already stopped. | guard=Stopping removes the transport for every connected page; do not stop during an open investigation. | refs=references/command-surface.md#runtime-and-connection
|
|
8
|
+
id=check-health | q=How do I verify that Chippy itself is reachable? | tags=health readiness helper timestamp ping | a=Run `chippy.sh health` before page commands. | done=The response has `ok: true` and a current `now` timestamp. | guard=Health proves the helper only; it does not prove a page is connected. | refs=SKILL.md#start-every-task,references/troubleshooting.md
|
|
9
|
+
id=list-connected-pages | q=How do I find the page Chippy is connected to? | tags=pages list client id url title story integration recency select | a=Run `chippy.sh pages`, then select by URL, title, integration identity, and recency. | done=One current `clientId` is tied to the intended page. | guard=Resolve a fresh client ID each turn because navigation can replace it. | refs=SKILL.md#start-every-task,references/command-surface.md#runtime-and-connection
|
|
10
|
+
id=choose-between-similar-pages | q=What should I do when several connected pages look like the target? | tags=duplicate pages ambiguous select current client id multiple tabs | a=Compare URL, title, integration, activity reason, and snapshot age from `chippy.sh pages`; ask which page only when two candidates still match closely. | done=The chosen page is unambiguous or the user identifies it. | guard=Do not guess from `isLikelyCurrent` alone. | refs=SKILL.md#start-every-task
|
|
11
|
+
id=inspect-one-page | q=How do I read one connected page without all page details? | tags=page slice minimal standard full client summary | a=Run `chippy.sh page <clientId> minimal` for routing, `standard` for normal inspection, or `full` only when the extra state is required. | done=The selected slice contains the fields needed for the next decision. | guard=Prefer the smallest slice that closes the evidence gap. | refs=references/command-surface.md#runtime-and-connection,references/command-output-formats.md
|
|
12
|
+
id=refresh-snapshot | q=How do I get fresh probe and page state? | tags=request snapshot stale refresh markers current state | a=Run `chippy.sh request-snapshot <clientId>`, then read `snapshot <clientId> standard` or `page <clientId> standard`. | done=The latest snapshot time advances and reflects the current page. | guard=Refresh after reload, navigation, low confidence, or a null probe. | refs=SKILL.md#start-every-task,references/command-surface.md#runtime-and-connection
|
|
13
|
+
id=wait-for-probe-annotation | q=How do I keep the current task waiting for a new browser intent without installing hooks? | tags=wait loop annotation intent comment long poll no hooks cancellable | a=Run `chippy.sh wait <clientId> [timeoutSeconds]`; handle the returned annotation, then invoke it again only while the user-requested loop remains active. | done=The command returns one queued or newly attached probe annotation, or a bounded timeout result. | guard=Never install, edit, merge, discover, or depend on host lifecycle hooks; `wait` is one explicit cancellable tool call. | refs=SKILL.md#explicit-wait-mode,references/command-output-formats.md#wait-clientid-timeoutseconds
|
|
14
|
+
id=read-stored-snapshot | q=How do I inspect the latest snapshot without asking the page to emit another one? | tags=snapshot cached stored read no stimulus | a=Run `chippy.sh snapshot <clientId> [minimal|standard|full]`. | done=The helper returns its latest stored snapshot and timestamp. | guard=A stored snapshot can be stale; request a new one when freshness matters. | refs=references/command-surface.md#runtime-and-connection
|
|
15
|
+
id=diagnose-recent-disconnect | q=How do I investigate a page that disappeared after reload or navigation? | tags=recent pages disconnected reload navigation missing client reconnect | a=Run `chippy.sh recent-pages`, compare the former URL and integration, then run `pages` again for a replacement client ID. | done=The disconnect is tied to a reload/navigation event or a new client is found. | guard=Do not keep using the old client ID after reconnect. | refs=references/command-surface.md#runtime-and-connection,references/troubleshooting.md
|
|
16
|
+
id=activate-probes | q=How do I activate Chippy on one connected page? | tags=activate enable probe mode page | a=Run `chippy.sh activate <clientId>`, then request a fresh snapshot. | done=Page state reports `activated: true`. | guard=Activation is page-scoped and may not survive a document-scoped import reload. | refs=references/command-surface.md#runtime-and-connection
|
|
17
|
+
id=deactivate-probes | q=How do I turn off Chippy activation without stopping the helper? | tags=deactivate disable probes page | a=Run `chippy.sh deactivate <clientId>`. | done=Page state reports `activated: false`. | guard=This changes one page, not the helper or other clients. | refs=references/command-surface.md#runtime-and-connection
|
|
18
|
+
id=ensure-probe-markers | q=How do I install probe markers on a connected page? | tags=ensure probes markers P1 P2 overlay install | a=Run `chippy.sh ensure-probes <clientId>`, then have the user place visible markers when manual placement is required. | done=The page reports probes active and marker positions can be captured. | guard=Probe placement is a visible browser action; do not fabricate coordinates. | refs=references/command-surface.md#runtime-and-connection,references/playwright-probes.md
|
|
19
|
+
id=clear-probe-markers | q=How do I remove existing probe markers? | tags=clear probes reset markers remove | a=Run `chippy.sh clear-probes <clientId>` only after the markers are no longer evidence anchors. | done=The next snapshot has cleared marker positions. | guard=Do not clear probes while an investigation still depends on them. | refs=SKILL.md#never-substitute-an-easier-looking-signal,references/playwright-probes.md
|
|
20
|
+
id=show-page-status | q=How do I show a short status message inside the inspected page? | tags=show status page visible message communication | a=Run `chippy.sh show-status <clientId> <text>`. | done=The status text is visible on the selected page. | guard=Status text communicates progress; it is not evidence about product behavior. | refs=references/command-surface.md#runtime-and-connection
|
|
21
|
+
id=hide-page-status | q=How do I remove Chippy's page-visible status message? | tags=hide status clear message page | a=Run `chippy.sh hide-status <clientId>`. | done=The page-visible Chippy status is absent. | guard=This does not clear probes or capture state. | refs=references/command-surface.md#runtime-and-connection
|
|
22
|
+
id=identify-integration-owner | q=How do I tell whether Chippy came from the extension, Storybook, or a console import? | tags=integration identity owner extension storybook import reload behavior who source | a=Read `integration.kind`, `who`, `source`, `reinjectsOnReload`, and `reloadAction` from `chippy.sh pages`. | done=The injection owner and reload contract are explicit. | guard=Do not infer ownership from globals or the page URL. | refs=SKILL.md#connection-and-bootstrap
|
|
23
|
+
id=inject-console-module | q=How do I load Chippy into a user-controlled page without the extension? | tags=inject import esm console module user controlled page | a=Run `import('http://127.0.0.1:7823/__chippy/inject.mjs')` in the browser console, then resolve the page through `chippy.sh pages`. | done=The page reports `integration.kind: import` and the bridge is ready. | guard=The import is document-scoped and must be repeated after full navigation or reload. | refs=SKILL.md#connection-and-bootstrap
|
|
24
|
+
id=print-classic-inject-url | q=How do I obtain the classic Chippy injection URL? | tags=inject url classic inject.js fallback | a=Run `chippy.sh inject-url`; stdout contains the URL and stderr explains the reload boundary. | done=A valid helper-served `inject.js` URL is returned. | guard=Prefer the ESM import for new console workflows. | refs=references/command-surface.md#runtime-and-connection
|
|
25
|
+
id=toggle-jira-enrichment | q=How do I show or hide Jira ownership and boundary enrichment in probe cards? | tags=jira context ui ownership error boundary enrichment on off cards | a=Run `chippy.sh context-ui <clientId> on` or `chippy.sh context-ui <clientId> off`. | done=The connected page renders or hides the enrichment consistently. | guard=This changes Chippy UI only and does not change Jira product behavior. | refs=references/command-surface.md#runtime-and-connection,references/jira-taxonomy.md
|
|
26
|
+
|
|
27
|
+
# Probe, source, and component inspection
|
|
28
|
+
id=locate-visible-element-source | q=Where does this visible element or text come from? | tags=visible element value locate source component probe dom owner prop trace | a=Run `probe-dom`, then `probe-owners`, then `probe-props` or `probe-prop-value`; open the reported source file and line directly. | done=The component, exact source render block, value source, and forwarding hops are named. | guard=Do not grep before exhausting the runtime-to-source chain. | refs=SKILL.md#route-by-question,SKILL.md#probe-and-fiber-interpretation
|
|
29
|
+
id=inspect-probe-dom | q=How do I inspect the exact DOM node under a probe? | tags=probe dom text attributes styles source testid element | a=Run `chippy.sh probe-dom <clientId> <probeId> [parentLevel]`. | done=The result names the DOM node, text, attributes, source, styles, and nearest test ID. | guard=Coordinate truth may land on a wrapper or icon; walk a bounded DOM parent instead of guessing intent. | refs=references/command-surface.md#probe-inspection,references/command-output-formats.md
|
|
30
|
+
id=walk-dom-parent | q=How do I move from a probe target to a containing DOM element? | tags=probe parent wrapper container ancestor DOM parentLevel | a=Increment `parentLevel` in `chippy.sh probe-dom <clientId> <probeId> <parentLevel>` until the relevant DOM block is identified. | done=The bounded ancestor has the expected text, attributes, source, or test ID. | guard=DOM ancestors are not the same as React fiber parents or debug owners. | refs=SKILL.md#probe-and-fiber-interpretation
|
|
31
|
+
id=read-computed-style | q=How do I inspect computed CSS for a probed element? | tags=computed style css selector parent visual layout color spacing | a=Run `chippy.sh get-computed-style <clientId> <probeId> [parentLevel] [selector]`. | done=The returned style belongs to the exact resolved target or bounded descendant. | guard=Use source and DOM evidence with the style; a screenshot alone is not provenance. | refs=references/command-surface.md#probe-inspection
|
|
32
|
+
id=walk-react-parents-from-probe | q=How do I walk the React parent chain from a probe? | tags=probe parents fiber return chain production safe ancestors component stack | a=Run `chippy.sh probe-parents <clientId> <probeId> [max]`. | done=The `fiber.return` chain reports components, sources, props, hooks, and test IDs or an explicit gap. | guard=Use this instead of `_debugOwner` when production-safe parent structure is required. | refs=SKILL.md#route-by-question,references/command-surface.md#probe-inspection
|
|
33
|
+
id=walk-debug-owners-from-probe | q=How do I inspect who rendered the component under a probe? | tags=probe owners debugOwner render owner chain dev build | a=Run `chippy.sh probe-owners <clientId> <probeId>`, then `probe-owner` for one positional owner if needed. | done=The debug owner chain or its build limitation is explicit. | guard=`_debugOwner` is a development-build signal, not the production parent-chain route. | refs=references/command-surface.md#probe-inspection
|
|
34
|
+
id=inspect-one-probe-owner | q=How do I inspect one owner from a probe owner chain? | tags=probe owner ownerIndex positional immediate repeated names | a=Run `chippy.sh probe-owner <clientId> <probeId> <ownerIndex>` where zero is the immediate owner. | done=One positional owner is returned with its source and structure. | guard=Address owners by index, not a possibly repeated display name. | refs=SKILL.md#probe-and-fiber-interpretation
|
|
35
|
+
id=list-probe-props | q=How do I list props for the probed component or one owner? | tags=probe props names values references ownerIndex | a=Run `chippy.sh probe-props <clientId> <probeId> [ownerIndex]`. | done=Serializable values, prop names, and reference identities are available for the chosen fiber. | guard=Read every returned field; omitted serialization is not proof that a prop is absent. | refs=references/command-surface.md#probe-inspection,references/command-output-formats.md
|
|
36
|
+
id=read-one-prop-value | q=How do I read one prop without loading the whole props object? | tags=probe prop value single targeted ownerIndex large props | a=Run `chippy.sh probe-prop-value <clientId> <probeId> <propName> [ownerIndex]`. | done=The named prop value or explicit lookup failure is returned. | guard=Use exact prop spelling from `probe-props`; do not infer a callback contract from its name. | refs=references/command-surface.md#probe-inspection
|
|
37
|
+
id=inspect-probe-hooks | q=How do I inspect hooks beneath a probe? | tags=probe hooks detailed tree custom primitive debug value | a=Run `chippy.sh probe-hooks-summary` for orientation, then `probe-hooks` only when detailed hook structure is necessary. | done=Hook count and relevant primitive or custom-hook ownership are identified. | guard=Collapse repeated hook families unless every hook was requested. | refs=references/hooks-inspection.md
|
|
38
|
+
id=large-probe-result | q=What should I do when a probe has many hooks, parents, or matched styles? | tags=large output hook count parent count styles big block summarize map structure | a=When hooks or parents exceed 50, or styles exceed 20, map structure first with compact summaries and component explanation before drilling into one branch. | done=One bounded branch and next evidence gap are selected. | guard=Do not dump the full structure merely because it is available. | refs=SKILL.md#probe-and-fiber-interpretation
|
|
39
|
+
id=normalize-probe-id | q=Which probe identifier forms does Chippy accept? | tags=probe id P1 p1 1 normalize marker | a=Treat `1`, `P1`, and `p1` as probe one, and the corresponding forms for probe two. | done=The command targets the intended marker. | guard=P3 exists only when both markers resolve and represents their nearest shared DOM-backed fiber. | refs=SKILL.md#probe-and-fiber-interpretation
|
|
40
|
+
id=probe-resolves-null | q=What do I do when a probe resolves to null or has low confidence? | tags=probe null unresolved low confidence stale snapshot refresh marker | a=Request a fresh snapshot, verify marker position and page identity, then place or refresh the probe again. | done=The probe resolves with usable confidence or the exact runtime gap is reported. | guard=Do not guess which component lies under an unresolved coordinate. | refs=SKILL.md#ask-before-crossing-these-boundaries,references/troubleshooting.md
|
|
41
|
+
id=inspect-probe-boundary-chain | q=Which Error and Suspense boundaries contain this probed element? | tags=probe boundary boundaries error suspense chain inner outer loading fallback | a=Run `chippy.sh probe-boundaries <clientId> <probeId> [max]`. | done=Every containing boundary is ordered inner to outer with distance, source, DOM block, fiber ID, and Suspense state; `truncated: false` proves completeness. | guard=Snapshot cards show only nearest boundaries and are not the complete chain. | refs=SKILL.md#route-by-question,references/command-output-formats.md
|
|
42
|
+
id=interpret-jira-context-boundaries | q=How do I interpret owner, error, and suspense fields on a Jira marker card? | tags=jira context boundaries owner team error suspense distance marker card | a=Read generic component/source evidence first, then the `context.boundaries` owner, error, and suspense blocks with their fiber distances. | done=Semantic team ownership is distinguished from React render ownership and declarative boundaries. | guard=The Jira `owner` is not React `_debugOwner`. | refs=SKILL.md#probe-and-fiber-interpretation,references/jira-taxonomy.md
|
|
43
|
+
id=open-runtime-source | q=What should I do after a probe reports a source file and line? | tags=open source exact file line render block no grep runtime evidence | a=Open the reported file at the reported line and inspect the render block and forwarding hops directly. | done=Static code is joined to the exact runtime component instance. | guard=Search is the fallback only after runtime source locations are exhausted. | refs=SKILL.md#treat-runtime-facts-as-facts
|
|
44
|
+
id=verify-visible-edit | q=How do I verify a source edit made from a visible probe? | tags=verify edit HMR reload same signal probe before after | a=After the authorized edit, reproduce the same page state and re-run the same probe, fiber, or capture command used before the change. | done=The original structured signal changes as predicted without losing page identity. | guard=A screenshot can confirm appearance but cannot replace the original structured evidence. | refs=SKILL.md#keep-the-proof-loop-bounded
|
|
45
|
+
|
|
46
|
+
# Fiber and declarative-boundary views
|
|
47
|
+
id=find-component-fiber | q=How do I find a current fiber when I know a component name? | tags=find fibers component name regex current fiberId | a=Run `chippy.sh find-fibers <clientId> --name <regex> --max <N>`. | done=One current `fiberId` and source identify the intended component. | guard=Re-run after commits, HMR, navigation, or reload because remembered fiber tokens can go stale. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
48
|
+
id=find-fiber-by-source | q=How do I find fibers rendered from a known source file? | tags=find fibers source file regex component | a=Run `chippy.sh find-fibers <clientId> --file <regex> --max <N>`. | done=The result set is bounded to fibers with matching source evidence. | guard=Use the narrowest source regex and inspect collisions before selecting a target. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
49
|
+
id=find-fiber-by-testid | q=How do I find a fiber by data-testid? | tags=find fibers testid DOM test identifier regex | a=Run `chippy.sh find-fibers <clientId> --testid <regex> --max <N>`. | done=The matching current fiber and DOM context are returned. | guard=A test ID can be shared or inherited; confirm source and component identity. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
50
|
+
id=find-fiber-by-text | q=How do I find a fiber that renders known text? | tags=find fibers text visible label regex | a=Run `chippy.sh find-fibers <clientId> --text <regex> --max <N>`. | done=A bounded result connects the text to a current fiber. | guard=Text is not unique provenance; confirm component, source, and DOM context. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
51
|
+
id=find-fiber-by-prop | q=How do I find fibers that carry a named prop? | tags=find fibers prop property name | a=Run `chippy.sh find-fibers <clientId> --prop <name> --max <N>`. | done=Matching current fibers list the prop and component context. | guard=Prop presence does not prove that it caused the observed behavior. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
52
|
+
id=compare-rendered-states | q=How do I compare what changed after a click, feature gate, or between two pages? | tags=compare before after state page feature gate click fiber prop structural diff | a=Run `fiber-state-capture <clientId> <label>` in each state or page, then `fiber-state-compare <beforeCaptureId> <afterCaptureId> [maxChanges]`. | done=The diff names matched fibers plus added, removed, and changed props with no full state dump and reports `truncated: false`. | guard=The diff proves structural inequality, not causality; if truncated, raise the relevant capture or change limit. | refs=SKILL.md#route-by-question,references/command-output-formats.md
|
|
53
|
+
id=explain-component-compact | q=How do I get a compact explanation of a component? | tags=explain component compact semantic text probe fiber name replay complexity | a=After the replay preflight, run `chippy.sh explain-component <clientId> <probeId|fiberId|name> --text`; use `--complexity` only for its bounded complexity section. | done=The component structure, fiber ID, replay status, warnings, and relevant source are summarized. | guard=Replay can execute component and hook bodies; use fiber-only commands if that is unsafe. | refs=SKILL.md#route-by-question,references/active-stimulus.md#debug-replay-preflight
|
|
54
|
+
id=describe-component-hooks | q=How do I get the detailed hook tree for a component? | tags=describe component detailed hooks tree replay json text | a=After the replay preflight, run `chippy.sh describe-component <clientId> <fiberIdOrName> --text`; use `--json` only when transport fields are required. | done=The detailed tree and replay status are explicit. | guard=Do not interpret replay scaffolding values as application state. | refs=references/active-stimulus.md#debug-replay-preflight,references/hooks-inspection.md
|
|
55
|
+
id=inspect-fiber-hooks | q=How do I inspect hooks for a remembered fiber ID? | tags=inspect fiber hooks summary detailed fiberId | a=Run `inspect-fiber-hooks-summary` first, then `inspect-fiber-hooks` for the selected branch. | done=The hook structure belongs to the current target fiber. | guard=Refresh the fiber ID after any state-changing event. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
56
|
+
id=inspect-fiber-props | q=How do I inspect props for a remembered fiber? | tags=inspect fiber props current fiberId values references | a=Run `chippy.sh inspect-fiber-props <clientId> <fiberId>`. | done=Props and reference identities are returned for the current fiber. | guard=A stale token can describe an alternate or fail; reacquire after commits. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
57
|
+
id=walk-fiber-parents | q=How do I walk parents from a remembered fiber ID? | tags=inspect fiber parents return chain ancestors production | a=Run `chippy.sh inspect-fiber-parents <clientId> <fiberId> [max]`. | done=The current `fiber.return` chain is reported to its requested bound. | guard=This is structural parentage, not debug render ownership. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
58
|
+
id=walk-fiber-debug-owners | q=How do I walk debug owners from a remembered fiber? | tags=inspect fiber owners debugOwner render ownership dev | a=Run `chippy.sh inspect-fiber-owners <clientId> <fiberId>`. | done=The development owner chain or its unavailability is explicit. | guard=Do not use `_debugOwner` as a production parent-chain substitute. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
59
|
+
id=inspect-fiber-boundary-chain | q=How do I find all Error and Suspense boundaries above a remembered fiber? | tags=inspect fiber boundaries error suspense chain loading fallback | a=Run `chippy.sh inspect-fiber-boundaries <clientId> <fiberId> [max]`. | done=The inner-to-outer chain reports distances, sources, DOM blocks, fiber IDs, Suspense status, and `truncated: false`. | guard=Reacquire the fiber after a commit; if truncated, increase `max` or report the exact bound. | refs=SKILL.md#route-by-question,references/command-output-formats.md
|
|
60
|
+
id=page-boundary-forest | q=How do I read the whole page in large Error and Suspense boundary blocks? | tags=whole page big blocks filter tree declarative boundaries error suspense forest partition loading fallback | a=Run `chippy.sh page-boundaries <clientId> [max]`; ordinary fibers are filtered out while boundary nesting and root containment are preserved. | done=The boundary-only forest covers every root and reports `truncated: false`. | guard=If truncated, increase `max`; absence of boundaries is a valid complete result when truncation is false. | refs=SKILL.md#route-by-question,references/command-output-formats.md
|
|
61
|
+
id=boundary-result-truncated | q=What do I do when a boundary query reports truncated true? | tags=boundary truncated max incomplete chain forest limit | a=Repeat `probe-boundaries`, `inspect-fiber-boundaries`, or `page-boundaries` with a larger `max` until `truncated: false` or an operational bound must be reported. | done=Completeness is proven or the exact imposed bound is stated. | guard=Never call a truncated chain or forest complete. | refs=references/command-output-formats.md
|
|
62
|
+
id=read-suspense-state | q=How do I tell whether a Suspense boundary is showing content or fallback? | tags=suspense status state content fallback loading boundary current | a=Read the `status` field returned by a boundary command together with its DOM block and source. | done=The boundary status is `showing-content` or `showing-fallback`. | guard=Component names alone do not prove what the boundary is currently showing. | refs=references/command-output-formats.md
|
|
63
|
+
id=map-loading-state-ownership | q=How do I find which loading boundary owns a visible region? | tags=loading state suspense owner region page boundary probe containment | a=Use `probe-boundaries` for the visible leaf, then compare its chain with `page-boundaries` to see the region's containing Suspense block and siblings. | done=The loading region is tied to an exact boundary source and containment path. | guard=Do not infer ownership from a fallback component name without fiber containment evidence. | refs=SKILL.md#route-by-question
|
|
64
|
+
id=map-error-state-ownership | q=How do I find which Error Boundary owns a failing visible region? | tags=error state boundary owner region containment mending recovery | a=Use `probe-boundaries` on the failing region or nearest stable leaf, then `page-boundaries` for the page partition and open the reported Error Boundary source. | done=The error region, containing boundary, and source recovery layer are named. | guard=An error-looking component is not necessarily the declarative Error Boundary. | refs=SKILL.md#route-by-question
|
|
65
|
+
id=inspect-one-hook-neighborhood | q=How do I inspect one hook and nearby custom-hook structure? | tags=inspect fiber structure hookIndex window replay neighborhood | a=After replay preflight, run `chippy.sh inspect-fiber-structure <clientId> <fiberId> <hookIndex> [windowSize]`. | done=The selected hook and bounded surrounding structure are returned with replay warnings. | guard=Replay executes render and hook bodies; do not use when that execution is unsafe. | refs=references/active-stimulus.md#debug-replay-preflight,references/hooks-inspection.md
|
|
66
|
+
id=refresh-stale-fiber | q=What do I do when a fiber ID stops resolving after an interaction? | tags=stale fiber token alternate commit HMR reload reacquire | a=Re-run the original probe or `find-fibers` query and use the newly returned current `fiberId`. | done=The current fiber resolves and matches the intended component and source. | guard=Fiber tokens are page-realm handles, not durable component identifiers. | refs=references/command-surface.md#fiber-inspection-and-active-stimulus
|
|
67
|
+
|
|
68
|
+
# Controlled stimuli
|
|
69
|
+
id=click-exact-target | q=How do I click the exact element identified by Chippy? | tags=page click active stimulus selector probe fiber interaction | a=State the anchor contract, then run `chippy.sh page-click <clientId> <selector|probeId|fiberId>` and observe the resulting snapshot or capture. | done=One resolved target receives the click and the expected signal is measured. | guard=Clicking is an active stimulus; name what can change before executing it. | refs=references/active-stimulus.md
|
|
70
|
+
id=inspect-callback-contract | q=How do I determine whether a function prop is safe to invoke? | tags=fiber prop contract callback arguments inspect invoke preflight | a=Run `chippy.sh fiber-prop-contract <clientId> <fiberId> <propName>`, then inspect the callback source and required arguments. | done=The callback identity, ownership, and argument contract are proven. | guard=Never infer callback arguments from a prop name. | refs=SKILL.md#ask-before-crossing-these-boundaries,references/active-stimulus.md
|
|
71
|
+
id=invoke-function-prop | q=How do I invoke a proven callback prop on a fiber? | tags=fiber invoke function prop args JSON active stimulus callback | a=After `fiber-prop-contract` and source proof, state the anchor contract and run `chippy.sh fiber-invoke <clientId> <fiberId> <propName> [--args JSON]`. | done=The invocation result and resulting page or capture signal are observed. | guard=Invocation can mutate application state; use only the proven argument contract. | refs=references/active-stimulus.md
|
|
72
|
+
id=force-one-render | q=How do I force one component render without changing its hooks or props? | tags=fiber schedule update force rerender nudge hookless props only active stimulus | a=State the anchor contract, then run `chippy.sh fiber-schedule-update <clientId> <fiberId> [--ownerIndex N]`. | done=One synchronous renderer update occurs and the chosen signal records it. | guard=Render-time side effects still run and the extra commit is the stimulus. | refs=SKILL.md#snippets-and-active-stimuli,references/active-stimulus.md
|
|
73
|
+
id=grade-subtree-stability | q=How do I test whether a subtree is stable under one controlled render? | tags=stability nudge clean leak cascade trasher subtree render | a=Load the stability-render reference, arm the required evidence, then run the packaged `stability-nudge` procedure on a current composite fiber. | done=The result is classified as clean, leak, leak plus cascade, or trasher. | guard=The nudge schedules an extra render and any render-time effects execute. | refs=references/stability-render.md
|
|
74
|
+
id=no-schedule-update | q=Why does fiber-schedule-update return no-scheduleUpdate? | tags=no scheduleUpdate production react dev renderer unavailable nudge failure | a=Confirm the page uses a development React renderer exposing DevTools `scheduleUpdate`; production React builds strip this capability. | done=The build limitation is proven or a supported dev page is selected. | guard=Do not replace the missing renderer capability with hook mutation. | refs=references/troubleshooting.md,references/stability-render.md
|
|
75
|
+
|
|
76
|
+
# Performance capture and analysis
|
|
77
|
+
id=capture-slow-interaction | q=How do I investigate a slow click, typing, scroll, route change, or rerender? | tags=slow interaction performance avalanche arm reproduce capture live rerender | a=Run `avalanche-arm`, reproduce exactly one interaction, then read `avalanche-status`, hot sources, commit list, and one commit peek. | done=Trigger, hotspot, collateral, and fix layer are distinguished. | guard=Do not reload during a live-interaction capture and do not call a hotspot the trigger. | refs=SKILL.md#performance-evidence,references/avalanche-detector.md
|
|
78
|
+
id=capture-initial-load | q=How do I investigate slow initial page load or settling? | tags=initial load cold start performance reload avalanche settle first paint | a=For a reload-surviving integration, run `chippy.sh reload-with-avalanche <clientId> <origin> ...`, then inspect status and hot sources. | done=A bounded, repeatable reload window captures the load signal. | guard=Do not use this with a document-scoped console import because reload removes Chippy. | refs=SKILL.md#performance-evidence,references/avalanche-detector.md
|
|
79
|
+
id=capture-imported-page-without-reload | q=How do I capture performance on a production page where console import disappears on reload? | tags=import production no reload mid page capture avalanche schedule update | a=Import Chippy once, run `avalanche-install` or `avalanche-arm`, then trigger a controlled mid-page interaction or `fiber-schedule-update`. | done=The capture covers a named mid-page stimulus without losing the integration. | guard=Do not call `reload-with-avalanche` for a document-scoped import. | refs=SKILL.md#connection-and-bootstrap
|
|
80
|
+
id=clear-performance-capture | q=How do I clear Avalanche history before a new measurement? | tags=avalanche clear reset commits capture history | a=Run `chippy.sh avalanche-clear <clientId>` before the new bounded interaction. | done=Status reports an empty capture ready for the next stimulus. | guard=Do not clear while the current capture remains evidence for an open finding. | refs=references/command-surface.md#avalanche-capture-and-analysis
|
|
81
|
+
id=check-capture-status | q=How do I see whether Avalanche is installed and recording commits? | tags=avalanche status installed active commit count capture | a=Run `chippy.sh avalanche-status <clientId>`. | done=Installation, capture state, mode, and commit count are explicit. | guard=Status is orientation; it does not identify causality. | refs=references/avalanche-detector.md
|
|
82
|
+
id=install-avalanche | q=How do I install Avalanche on a connected page without reloading? | tags=avalanche install detector detection mode sources full stats amplifiers | a=Run `chippy.sh avalanche-install <clientId> [detectionMode]`. | done=Status reports the detector installed with the intended mode. | guard=Choose mode from the evidence needed; full mode costs more. | refs=references/command-surface.md#avalanche-capture-and-analysis
|
|
83
|
+
id=recapture-in-full-mode | q=How do I get renderedByCommit evidence after starting in sources mode? | tags=avalanche recapture full renderedByCommit per component render evidence | a=Run `chippy.sh avalanche-recapture <clientId> full`, reproduce the identical interaction, then inspect `commit-peek` with `include=rendered`. | done=The new capture contains per-component render evidence for the same interaction. | guard=Do not compare a different interaction or expect rendered data from sources or stats mode. | refs=SKILL.md#performance-evidence,references/avalanche-detector.md
|
|
84
|
+
id=arm-live-capture | q=How do I arm Avalanche for the next live interaction? | tags=avalanche arm origin max commits captureFibers detectionMode | a=Run `chippy.sh avalanche-arm <clientId> <origin> [maxCommits] [captureFibers] [detectionMode]` before the interaction. | done=The detector is reset, installed, and ready to record the named origin. | guard=Arm before the stimulus and retain fibers when hook or fiber analysis may follow. | refs=references/command-surface.md#avalanche-capture-and-analysis
|
|
85
|
+
id=disarm-live-capture | q=How do I stop automatic Avalanche arming for an origin? | tags=avalanche disarm origin stop auto start | a=Run `chippy.sh avalanche-disarm <clientId> <origin>`. | done=The helper reports auto-start disabled for that origin. | guard=Disarming does not itself erase the current capture. | refs=references/command-surface.md#avalanche-capture-and-analysis
|
|
86
|
+
id=find-hot-sources | q=How do I identify the hottest source families in a capture? | tags=avalanche hot sources rank source families performance broad narrow | a=Run `chippy.sh avalanche-hot-sources <clientId> [limit]` after confirming the capture window. | done=A bounded source ranking selects the next commit or component to inspect. | guard=Hot means expensive or frequent, not necessarily causal. | refs=SKILL.md#performance-evidence,references/avalanche-detector.md
|
|
87
|
+
id=rank-performance-commits | q=How do I rank captured commits by a specific signal? | tags=avalanche commit list rank collateral sources restarts effects memos callbacks context unmount | a=Run `chippy.sh avalanche-commit-list <clientId> [top] [by]` with the signal matching the symptom. | done=A bounded commit list identifies one commit for detailed inspection. | guard=Use only documented selector values and do not open the full export first. | refs=references/command-surface.md#commit-selectors
|
|
88
|
+
id=inspect-one-performance-commit | q=How do I inspect one captured commit without loading the full export? | tags=avalanche commit peek bounded detail stats sources timing metrics atl unmounts actions rendered | a=Run `chippy.sh avalanche-commit-peek <clientId> <commitId> [include] [depth] [maxItems]`. | done=One commit joins the selected evidence within explicit depth and item bounds. | guard=`include=rendered` requires a full-mode capture. | refs=references/command-surface.md#commit-selectors,references/command-output-formats.md
|
|
89
|
+
id=trace-performance-action | q=How do I expand an action breadcrumb captured by Avalanche? | tags=avalanche describe action actionId owner parent trigger breadcrumb | a=Run `chippy.sh avalanche-describe-action <clientId> <actionId>` and join its owner/parent context to the commit. | done=The action is tied to a source and component context or an explicit gap. | guard=`latestAction` is a clue, not proof of root cause. | refs=SKILL.md#performance-evidence,references/avalanche-detector.md
|
|
90
|
+
id=debug-captured-hook | q=How do I inspect one suspicious hook found in a performance capture? | tags=avalanche debug hook sourceId hookIndex window primitive custom ownership | a=After the capture identifies `sourceId` and `hookIndex`, run `chippy.sh avalanche-debug-hook <clientId> <sourceId> <hookIndex> [window]`. | done=Primitive type, custom-hook ownership, confidence, and remediation branch are named. | guard=Do not run debug-hook without a capture-derived source and hook index. | refs=references/debug-hook.md
|
|
91
|
+
id=count-state-dispatches | q=How do I count useState or reducer dispatch activity during an interaction? | tags=hook metrics setState count dispatch reducer state performance | a=Load the hook-metrics procedure, establish a before value, reproduce one interaction, and read the after value joined to the same Avalanche window. | done=The dispatch delta is measured for one bounded interaction. | guard=A counter increase shows activity, not which state update caused collateral renders. | refs=references/hook-metrics.md
|
|
92
|
+
id=track-memo-effect-callback-hooks | q=How do I track memo, callback, or effect recomputation by hook slot? | tags=hook tracker memo callback effect histogram slot source performance | a=Use the packaged hook tracker from the hook-metrics reference, install it before the interaction, then snapshot and uninstall it after collecting the histogram. | done=Hook-type and slot activity are joined to the same interaction and source. | guard=The tracker owns the host `onHookUpdate` callback until uninstall. | refs=references/hook-metrics.md,SKILL.md#snippets-and-active-stimuli
|
|
93
|
+
id=save-capture-offline | q=How do I save an Avalanche capture without repeatedly loading it through the helper? | tags=avalanche save file export last commits offline large capture | a=Run `chippy.sh avalanche-save <clientId> <file> [count]`; zero saves the full export and a positive count saves the last N commits. | done=The command reports file path, bytes, and commit count. | guard=Choose a bounded count when the full capture is unnecessary. | refs=references/command-surface.md#avalanche-capture-and-analysis
|
|
94
|
+
id=summarize-saved-capture | q=How do I summarize a saved Avalanche export offline? | tags=avalanche offline summary file no helper large capture | a=Run `chippy.sh avalanche-offline <file> summary`. | done=A compact summary is produced without contacting the page. | guard=Offline evidence cannot refresh or prove current runtime state. | refs=references/avalanche-detector.md
|
|
95
|
+
id=search-saved-capture-source | q=How do I search a saved Avalanche export for a source? | tags=avalanche offline source search file component large capture | a=Run `chippy.sh avalanche-offline <file> source-search <query>` and inspect only the matching source records. | done=The source query returns bounded matches from the saved capture. | guard=Join offline matches to the saved capture window, not the current page. | refs=references/avalanche-detector.md
|
|
96
|
+
id=separate-trigger-hotspot-collateral | q=How do I avoid blaming a hot component for causing a slow interaction? | tags=trigger hotspot collateral causality performance root cause | a=Join the initiating action or source update to the commit wave, then distinguish the trigger from components that merely rendered often or expensively. | done=A source-to-collateral chain or exact remaining causality gap is stated. | guard=Never promote heat, `latestAction`, or render count alone into causality. | refs=SKILL.md#never-substitute-an-easier-looking-signal,references/fixing-performance.md
|
|
97
|
+
id=choose-performance-fix-layer | q=How do I decide where to fix a proven performance problem? | tags=performance fix layer source boundary collateral memo state architecture | a=Load fixing-performance after the trigger and collateral chain are proven, then choose the narrowest source, boundary, or consumer layer that owns the unnecessary work. | done=The proposed edit layer follows from measured evidence and has a repeatable verification signal. | guard=Do not edit a collateral component merely because it is hot. | refs=references/fixing-performance.md
|
|
98
|
+
|
|
99
|
+
# Snippets, automation, recovery, and reporting
|
|
100
|
+
id=list-packaged-snippets | q=How do I discover Chippy's packaged page snippets? | tags=eval snippets list catalog procedures script | a=Run `chippy.sh eval-snippets`, then open the snippet README for its contract. | done=The selected snippet and required arguments are known. | guard=Prefer a first-class command when it already owns the capability. | refs=SKILL.md#snippets-and-active-stimuli,snippets/README.md
|
|
101
|
+
id=run-packaged-snippet | q=How do I execute a version-controlled snippet in the connected page? | tags=eval page snippet args page realm script procedure | a=Run `chippy.sh eval-page <clientId> <snippet-file-or-name> [--key value ...]`. | done=The helper returns the snippet's circular-safe JSON envelope. | guard=Read the snippet's ownership and anchor contract before any active stimulus. | refs=SKILL.md#snippets-and-active-stimuli,snippets/README.md
|
|
102
|
+
id=reload-connected-page | q=How do I ask a reload-surviving Chippy page to reload and reconnect? | tags=page reload wait reconnect navigation extension storybook force remount | a=Run `chippy.sh page-reload <clientId>`, then `chippy.sh page-wait-reload <clientId> [timeoutMs]`. On Storybook, page-reload uses `FORCE_REMOUNT` to remount the current SUT without navigating the preview iframe; its client remains current. | done=The helper observes the requested transition. | guard=Console-import integration is removed by reload and needs reinjection. | refs=references/command-surface.md#snippets-navigation-and-automation
|
|
103
|
+
id=handoff-to-playwright | q=How do I transfer a connected Chippy page into browser automation? | tags=handoff playwright browser automate probes url headless | a=Run `chippy.sh handoff-to-playwright <clientId> [url] [probeIdsCsv] [browserName] [headless]`, then follow the Playwright setup reference. | done=Automation receives the intended URL and selected probe anchors. | guard=Use Playwright when automation or replay is required, not as a substitute for existing connected-page evidence. | refs=references/playwright-cli-setup.md,references/playwright-probes.md
|
|
104
|
+
id=inject-playwright-page | q=How do I load Chippy into a Playwright-controlled page? | tags=playwright inject init script devtools hook avalanche iife bridge module | a=Follow the Playwright setup reference: install the DevTools hook shim before navigation, then inject the helper bundle and Avalanche bundle in the documented order. | done=Public Chippy and Avalanche globals are present and a bridge-backed command succeeds. | guard=Late DevTools hook injection does not validate debug replay. | refs=references/playwright-cli-setup.md,references/mode-a-injection.md
|
|
105
|
+
id=bridge-module-not-loaded | q=Why do bridge commands return bridge module not loaded? | tags=bridge module missing avalanche iife injection playwright fallback error | a=Inject `avalanche.iife.js` after `inject.js` in the controlled page, then retry a bridge-backed read. | done=The command returns real bridge data instead of its fallback shape. | guard=Do not diagnose command logic until the bridge bundle is present. | refs=references/mode-a-injection.md,references/troubleshooting.md
|
|
106
|
+
id=debug-replay-unavailable | q=What do I do when component debug replay is unavailable or warns? | tags=replay unavailable warning fiber only dispatcher devtools hook fallback | a=Read `replayStatus` and warnings, then use fiber-only summaries, props, parents, owners, and boundary commands when render-time replay is unsafe or unavailable. | done=The answer is bounded to verified fiber evidence and names the replay gap. | guard=Do not explain replay failure as an intuitive React boundary; connect exact runtime evidence first. | refs=references/active-stimulus.md#debug-replay-preflight,references/troubleshooting.md
|
|
107
|
+
id=helper-http-504 | q=What does a Chippy helper HTTP 504 mean? | tags=504 timeout helper upstream page runtime unresponsive | a=Verify lighter round trips with `pages`, `request-snapshot`, and `avalanche-status` before retrying the expensive command. | done=The failing layer is narrowed to helper transport or connected page/runtime. | guard=An HTTP 504 means the helper answered but timed out waiting upstream. | refs=references/troubleshooting.md
|
|
108
|
+
id=curl-exit-7 | q=How do I recover from curl exit 7 when running Chippy? | tags=curl exit 7 connection refused helper down start | a=Run `chippy.sh start`, then `health`, `pages`, and a fresh snapshot. | done=The helper is reachable and the intended page is selected. | guard=Do not treat a helper outage as a page or bridge bug. | refs=references/troubleshooting.md
|
|
109
|
+
id=curl-exit-28 | q=How do I recover from a Chippy request timeout with curl exit 28? | tags=curl exit 28 timeout request retry page responsiveness | a=Retry once, then check `pages`, `request-snapshot`, and a lighter command to determine whether the helper or page is slow. | done=One failing layer and recovery action are explicit. | guard=Do not repeatedly run the expensive command without narrowing the timeout. | refs=references/troubleshooting.md
|
|
110
|
+
id=production-minified-page | q=How do I inspect a production or minified React page with weak component names? | tags=production minified Jira taxonomy testid source DOM fallback evidence | a=Exhaust DOM, test ID, source, props, parents, and boundary facts first, then load the Jira taxonomy for bounded name-pattern interpretation. | done=The finding states what production evidence proves and what naming evidence cannot prove. | guard=Do not turn a minified display name into component identity by intuition. | refs=references/jira-taxonomy.md
|
|
111
|
+
id=control-local-proxy | q=How do I inspect, start, or stop Chippy's local proxy? | tags=proxy state start stop target port original url | a=Use `proxy-state`, `proxy-start <targetPort> [originalUrl]`, or `proxy-stop` from `chippy.sh`. | done=The helper reports the proxy's intended state. | guard=Proxy control is local infrastructure and does not authorize product or network changes outside the task. | refs=references/command-surface.md#snippets-navigation-and-automation
|
|
112
|
+
id=report-proven-finding | q=How should Chippy report a completed finding? | tags=report format confidence domain evidence next gap source line | a=Report the resolved component and source, value or behavior source, Cynefin domain, confidence, and exact next gap or `none`. | done=The claim is traceable to runtime and source evidence at the appropriate confidence. | guard=Chaotic, Disorder, and partial evidence cannot pass the source-edit gate. | refs=SKILL.md#report-and-stop,SKILL.md#keep-the-proof-loop-bounded
|
|
113
|
+
id=decide-whether-to-edit-source | q=When is Chippy allowed to edit product source after inspection? | tags=edit gate apply proof packet permission verify source change | a=Edit only when user intent permits it and the proof packet identifies the fix layer, expected signal change, and repeatable verification command. | done=Runtime, structure, source, and signal evidence agree before Apply. | guard=OODA Act is an evidence move and does not itself authorize source edits. | refs=SKILL.md#keep-the-proof-loop-bounded,references/operating-model.md
|
|
114
|
+
id=stop-investigation | q=When should a Chippy investigation stop? | tags=stop completion proven answer verified unavailable capability gap | a=Stop when the requested answer is proven, the requested edit is verified against the same signal, or the exact unavailable capability is reported. | done=There is no unresolved gap required by the user's request. | guard=Do not offer a menu after the evidence already selects the outcome. | refs=SKILL.md#report-and-stop
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Stability render — controlled re-render probe
|
|
2
|
+
|
|
3
|
+
A scoped, repeatable performance probe. The user does not interact. Chippy nudges a single fiber, watches what shakes loose in avalanche, and grades the subtree against a clean re-render baseline.
|
|
4
|
+
|
|
5
|
+
Lifecycle stage: **experiment and verification**. Enter when there is a target subtree/source to test, or after a fix from real-capture analysis needs a deterministic regression check.
|
|
6
|
+
|
|
7
|
+
Exit with one of: clean, leak, leak+cascade, or trasher when the required signature matches. If none matches, report a bounded result and return to real interaction capture instead of forcing a verdict. Clean → done or use as baseline. Leak → return to [fixing-performance](fixing-performance.md) for boundary/source/prune choice. Changed hook index on the same source → use [debug-hook](debug-hook.md). Different source/trasher → return to real capture or commit analysis; do not keep nudging the wrong target.
|
|
8
|
+
|
|
9
|
+
> **Routing lives in [SKILL.md](../SKILL.md#route-by-question).** Use the controlled-nudge procedure for a settled component; start with a real Avalanche capture when the question is which interaction or load is slow.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Execution model — how the nudge actually happens
|
|
14
|
+
|
|
15
|
+
Stability-render is **not a Playwright test, not a CI harness, and not a separate process.** It is one Chippy command (`chippy.sh eval-page`) that ships a JavaScript snippet over the existing helper-server WebSocket into a connected page. The extension, module import, and Storybook addon all provide that connection. The snippet runs *inside the page*, in the same JavaScript realm as React itself, and reaches the target fiber through the same React internals the rest of Chippy uses.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌──────────────┐ chippy.sh eval-page <client> stability-nudge
|
|
19
|
+
│ agent shell │───────────────────────────────────────────────┐
|
|
20
|
+
└──────────────┘ │
|
|
21
|
+
▼
|
|
22
|
+
┌──────────────────────────┐
|
|
23
|
+
│ chippy helper (HTTP+WS) │
|
|
24
|
+
└──────────────┬───────────┘
|
|
25
|
+
│ WS message
|
|
26
|
+
▼
|
|
27
|
+
┌──────────────────────────┐
|
|
28
|
+
│ Chippy page integration │
|
|
29
|
+
│ extension/import/addon │
|
|
30
|
+
└──────────────┬───────────┘
|
|
31
|
+
│ runs snippet body
|
|
32
|
+
▼
|
|
33
|
+
┌──────────────────────────┐
|
|
34
|
+
│ stability-nudge.js │
|
|
35
|
+
│ resolve target → fiber │
|
|
36
|
+
│ call scheduleUpdate(fiber) │
|
|
37
|
+
└──────────────┬───────────┘
|
|
38
|
+
│ React schedules render
|
|
39
|
+
▼
|
|
40
|
+
┌──────────────────────────┐
|
|
41
|
+
│ avalanche detector │
|
|
42
|
+
│ records commit + sources │
|
|
43
|
+
└──────────────┬───────────┘
|
|
44
|
+
│ status / commit-list / commit-peek / hot-sources
|
|
45
|
+
▼
|
|
46
|
+
agent reads
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
What this means in practice:
|
|
50
|
+
|
|
51
|
+
- **Any page connected through the extension, module import, or Storybook addon is a valid target.** The helper connection is the prerequisite, not a particular automation tool.
|
|
52
|
+
- **No Playwright action is required.** The snippet itself is the stimulus.
|
|
53
|
+
- **Reproducibility comes from the snippet, not the runner.** Same `--testId` + same React tree → same commit shape, every time. That is why the procedure is reliable for verification.
|
|
54
|
+
- **The snippet is a normal Chippy snippet.** It lives at `./snippets/stability-nudge.js` and obeys the standard `eval-page` `ctx` contract (`ctx.args`, `ctx.log`). Nothing about it is special-cased in the helper.
|
|
55
|
+
- **Playwright is optional, not required.** If Playwright is driving the page, it can navigate before the agent runs the procedure. Once the page is connected, Playwright is no longer in the loop. The nudge itself never goes through Playwright.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Concepts
|
|
60
|
+
|
|
61
|
+
| Term | Meaning |
|
|
62
|
+
|---|---|
|
|
63
|
+
| **Nudge** | One render scheduled on a current composite fiber through the React DevTools renderer. The minimal stimulus. |
|
|
64
|
+
| **Wave** | Commits scheduled by one nudge, including async follow-ups inside the settle window. |
|
|
65
|
+
| **Render factor** | `commits / 1`. Ideal is `~1` for a clean parent flip. `>1` means children are scheduling extra work. |
|
|
66
|
+
| **Blast radius** | Number of distinct components that committed because of the nudge. |
|
|
67
|
+
| **Trasher** | A component with non-zero effect/callback/memo `trasherCounts` in at least two commits from the same nudge. |
|
|
68
|
+
|
|
69
|
+
A clean subtree under a parent nudge: 1 commit, 0 collateral outside the expected path, 0 context waves, 0 restarts, render factor `~1`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Procedure
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
verify runtime
|
|
77
|
+
→ pick target fiber (parent OR branch root)
|
|
78
|
+
→ recapture avalanche in full mode
|
|
79
|
+
→ nudge the fiber
|
|
80
|
+
→ settle 1000 ms
|
|
81
|
+
→ read status + every commit via commit-list / commit-peek + hot-sources
|
|
82
|
+
→ classify: clean / leak / leak+cascade / trasher
|
|
83
|
+
→ (if dirty) walk the bounded commit detail top → bottom
|
|
84
|
+
→ build proof packet at highest stable boundary
|
|
85
|
+
→ if authorized, pass Apply gate and edit
|
|
86
|
+
→ re-run the same procedure to verify
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Step 1 — verify runtime
|
|
90
|
+
|
|
91
|
+
`health` → `pages` → `request-snapshot <clientId>`. Standard Phase 1 from SKILL.md. Never reuse a stale `clientId`.
|
|
92
|
+
|
|
93
|
+
### Step 2 — pick the target
|
|
94
|
+
|
|
95
|
+
Two cases drive different targets:
|
|
96
|
+
|
|
97
|
+
**Case A — parent update (memoization audit)**
|
|
98
|
+
- Target = the parent component the user named (or the P3 of two probes).
|
|
99
|
+
- Hypothesis: "this parent re-rendering should not move anything visible below the next memo boundary."
|
|
100
|
+
- Bad outcome: avalanche shows children re-rendering. Top → bottom, find the first child that lacks `React.memo` or whose memo is defeated by an unstable prop.
|
|
101
|
+
|
|
102
|
+
**Case B — branch update (trasher audit)**
|
|
103
|
+
- Target = a branch root that the user expects to re-render exactly once on each input change.
|
|
104
|
+
- Hypothesis: "every component in this branch renders exactly once per nudge (render factor ~1)."
|
|
105
|
+
- Bad outcome: the same component records non-zero `trasherCounts` in at least two commits. Inspect the recorded hook/dependency samples to find the persistent amplifier.
|
|
106
|
+
|
|
107
|
+
### Step 3 — start a clean full-mode capture
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
./scripts/chippy.sh avalanche-recapture <clientId> full
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
No reload. `avalanche-recapture ... full` installs a fresh detector and clears history so the nudge is the only thing in the window. Full mode is required for the `renderedByCommit` and `trasherCounts` signatures used below.
|
|
114
|
+
|
|
115
|
+
### Step 4 — nudge the fiber
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
./scripts/chippy.sh eval-page <clientId> stability-nudge \
|
|
119
|
+
--testId stability-chain # or --probeId 1
|
|
120
|
+
[--ownerIndex 0] # 0 = target itself, N = walk owner chain
|
|
121
|
+
[--label parent-nudge] # surfaces in ctx.log
|
|
122
|
+
[--dryRun true] # resolve target, do not schedule
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Pick exactly one of `--testId` or `--probeId`:
|
|
126
|
+
- `--testId <data-testid>` — fully automated. DOM → fiber via the React fiber key. Use this for fixtures and any component that already exposes a `data-testid`.
|
|
127
|
+
- `--probeId <id>` — uses a placed probe. Use when the target has no stable testid, or when you want the snippet to share targeting with the rest of the Chippy probe workflow.
|
|
128
|
+
|
|
129
|
+
The snippet returns `{ ok, fiberName, source, ownerIndex }`. If `ok: false`, read `reason` — on a dev build the common cause is the devtools-hook shim missing at page boot (`no-scheduleUpdate`). To nudge a parent instead of the target, use `--ownerIndex 1`. See **Update mechanism** below.
|
|
130
|
+
|
|
131
|
+
If a current `fiberId` has already been established with `find-fibers`, use the capability directly instead:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
./scripts/chippy.sh fiber-schedule-update <clientId> <fiberId>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Re-resolve the fiber after any intervening commit, HMR, navigation, or reload.
|
|
138
|
+
|
|
139
|
+
### Step 5 — settle
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
sleep 1
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
One second covers the nudge commit plus any synchronous follow-up commits scheduled during render. Heavier subtrees (large lists, Suspense) warrant `2` seconds; do not exceed `3` — beyond that you are catching unrelated work.
|
|
146
|
+
|
|
147
|
+
### Step 6 — read
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
./scripts/chippy.sh avalanche-status <clientId>
|
|
151
|
+
./scripts/chippy.sh avalanche-commit-list <clientId> 20 collateral
|
|
152
|
+
# Repeat for every commitId returned in this fresh nudge window:
|
|
153
|
+
./scripts/chippy.sh avalanche-commit-peek <clientId> <commitId> stats,sources,rendered 3 50
|
|
154
|
+
./scripts/chippy.sh avalanche-hot-sources <clientId> 12
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Read in this order:
|
|
158
|
+
1. `avalanche-status.commitCount` — how many waves occurred; count alone is not a verdict.
|
|
159
|
+
2. `avalanche-commit-list` — the complete set of commit IDs in this fresh bounded window.
|
|
160
|
+
3. Each `commit-peek.stats` — `componentRenders`, collateral, restarts, and `externalStoreRestarts`.
|
|
161
|
+
4. Each `commit-peek.sourcesByCommit` — whether the nudged or another source repeats.
|
|
162
|
+
5. Each `commit-peek.renderedByCommit` — exact target/collateral entries, `propChanges`, and every `trasherCounts` field.
|
|
163
|
+
6. `hot-sources` — ranking corroboration after the per-commit verdict evidence is present.
|
|
164
|
+
|
|
165
|
+
### Step 7 — classify
|
|
166
|
+
|
|
167
|
+
| Verdict | `commitCount` | `sourcesByCommit` pattern | `renderedByCommit` pattern |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| **clean** | 1 | nudged sourceId only (or empty) | nudged target only; every `trasherCounts` field is zero |
|
|
170
|
+
| **leak** | 1 | nudged sourceId only | high collateral count under target; `propChanges` empty on collateral entries |
|
|
171
|
+
| **leak+cascade** | >1 | same sourceId repeats across commits | `isSelfSource: true` on that source; `propChanges` empty |
|
|
172
|
+
| **trasher** | ≥2 | the same component source persists across commits | non-zero `trasherCounts` on that component in at least two commits |
|
|
173
|
+
|
|
174
|
+
Apply the rows as mutually exclusive classifications: persistent non-zero `trasherCounts` takes precedence. A different source or a high render count without that field is not enough for `trasher`; if no row matches, report the unmatched signature and return to real interaction capture.
|
|
175
|
+
|
|
176
|
+
### Step 8 — locate the offender
|
|
177
|
+
|
|
178
|
+
For **leak** (Case A):
|
|
179
|
+
- `avalanche-commit-list <clientId> 20 collateral`.
|
|
180
|
+
- Walk the top commit's collaterals top → bottom from the nudged fiber.
|
|
181
|
+
- The first collateral with stable inputs but no `React.memo` is the boundary fix. The first collateral with `React.memo` but unstable props (inline object, fresh callback, new JSX) is a boundary that needs prop stabilization above it.
|
|
182
|
+
|
|
183
|
+
> **Attribute the leak.** When the offending fiber is identified but its hook role is unclear, run [debug-hook](./debug-hook.md) on **the same nudged source** with the changed `hookIndex` from this commit. Do not switch sources. The primitive + `parents` chain pins down whether the cause is selector identity, dispatcher churn, or unstable deps.
|
|
184
|
+
|
|
185
|
+
For **trasher** (Case B):
|
|
186
|
+
- `avalanche-commit-list <clientId> 20 sources`.
|
|
187
|
+
- For each candidate commit, run `avalanche-commit-peek <clientId> <commitId> stats,sources,rendered` and find the same component with non-zero `trasherCounts` in at least two commits.
|
|
188
|
+
- Read its bounded `trashers[]` samples for `hookIndex`, `hookType`, and changed dependency indices, then use `avalanche-debug-hook` on that source and hook index.
|
|
189
|
+
|
|
190
|
+
### Before Apply in a Complex case
|
|
191
|
+
|
|
192
|
+
When the proof packet uses `confidence: likely` in the Complex domain, clear the capture and repeat the identical nudge, target, label, settle window, and read commands before any edit. The same verdict and causal signature must recur; the post-edit run is verification, not the repeatability evidence that authorizes Apply.
|
|
193
|
+
|
|
194
|
+
### Step 9 — verify the fix
|
|
195
|
+
|
|
196
|
+
Re-enter at Step 1 and re-resolve the current page and target before Step 3. A reload can clear probes, and any commit/HMR/reload can replace the current fiber:
|
|
197
|
+
|
|
198
|
+
- test-id target: resolve the same test-id again in the current page;
|
|
199
|
+
- probe target: confirm the probe still resolves or re-place it on the same DOM target;
|
|
200
|
+
- direct fiber target: run `find-fibers` again and use the new current token;
|
|
201
|
+
- prior `sourceId + hookIndex`: obtain them again from the fresh post-edit capture rather than carrying capture coordinates forward.
|
|
202
|
+
|
|
203
|
+
Then re-run from Step 3 with the same semantic target, `--ownerIndex`, and `--label`. Compare:
|
|
204
|
+
- `commitCount` should drop toward 1.
|
|
205
|
+
- `componentRenders` should drop.
|
|
206
|
+
- `collateral` should drop.
|
|
207
|
+
- The previously top hot-source should disappear or fall in rank.
|
|
208
|
+
|
|
209
|
+
If the numbers do not move, the fix was at the wrong layer. Re-classify before editing again.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Update mechanism — `scheduleUpdate`
|
|
214
|
+
|
|
215
|
+
The snippet uses one mechanism: the React DevTools renderer's **`scheduleUpdate(fiber)`**. This is the exact internal React DevTools itself calls to force a component to re-render (the same path behind "force re-render" and hook-value editing in the DevTools panel).
|
|
216
|
+
|
|
217
|
+
### Capability vs procedure
|
|
218
|
+
|
|
219
|
+
The mechanism is a **bundle capability**, not snippet logic. It ships in `avalanche.iife.js` (source `src/chippy-bridge/fiber-schedule-update.ts`) and is exposed two ways:
|
|
220
|
+
|
|
221
|
+
- **Bridge command** — `fiber-schedule-update <clientId> <fiberId> [--ownerIndex N]`. Use this directly when you already have a `fiberId` from `find-fibers` and just want one re-render. Returns `{ ok, scheduled, fiberName, source, ownerIndex }`.
|
|
222
|
+
- **`stability-nudge` snippet** — the **procedure** layer. It resolves a `--probeId`/`--testId` to a fiber (DOM context the bridge does not have), climbs `--ownerIndex`, then delegates the re-render to the same capability.
|
|
223
|
+
|
|
224
|
+
The split keeps version-coupled React internals in one bundled capability while the snippet owns probe/test-id resolution and procedure composition.
|
|
225
|
+
|
|
226
|
+
It is reached via the registered renderer:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.get(id).scheduleUpdate(fiber)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Internally (`react-dom` dev build) it is simply:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
scheduleUpdate = function (fiber) {
|
|
236
|
+
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
|
237
|
+
if (root !== null) scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
|
238
|
+
};
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Why this is the only strategy:
|
|
242
|
+
|
|
243
|
+
- **Universal.** Works on any composite fiber — function components (including **hookless** ones), `memo`, `forwardRef`, and class components alike. No `useState`/`useReducer` required.
|
|
244
|
+
- **Zero pollution.** It marks a lane and schedules; it does **not** mutate any hook value or prop. There is no "nudge anchor" to keep clean, no opaque slot left behind.
|
|
245
|
+
- **React-sanctioned.** It is the mechanism React ships for DevTools, not a hand-rolled lane write.
|
|
246
|
+
|
|
247
|
+
### Requirements
|
|
248
|
+
|
|
249
|
+
`scheduleUpdate` is registered **only in development React builds**, and only when the devtools-hook shim was present before `react-dom` evaluated (so the renderer registered into `__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers`). On a **production** build there is no `scheduleUpdate` and the nudge reports `no-scheduleUpdate`. For Playwright injection order, use [mode-a-injection](mode-a-injection.md).
|
|
250
|
+
|
|
251
|
+
### Why not `useState` dispatch / `forceUpdate` / direct lane writes?
|
|
252
|
+
|
|
253
|
+
- A `useState` dispatch only works if the target *has* a state hook, and it **pollutes** that hook's value (the old "anchor contract"). `scheduleUpdate` has neither limitation.
|
|
254
|
+
- `forceUpdate` only covers class components.
|
|
255
|
+
- Direct lane writes (`fiber.lanes |= 1`) are version-coupled (lane layout shifted across 18.0/18.2/18.3) and produce a marked fiber with no scheduled work. `scheduleUpdate` calls the real scheduler.
|
|
256
|
+
|
|
257
|
+
### Failure modes and recovery
|
|
258
|
+
|
|
259
|
+
| `reason` | Cause | Recovery |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `missing-target` | Neither `--probeId` nor `--testId` was supplied. | Pass one. |
|
|
262
|
+
| `testid-not-found` | The DOM does not contain that `data-testid`. | Snapshot the page state; story may not have rendered. |
|
|
263
|
+
| `no-composite-fiber-for-testid` | The element is detached or not React-owned. | Pick a different node. |
|
|
264
|
+
| `no-fiber` / `owner-out-of-range` | Probe did not resolve, or `--ownerIndex` overshot the chain. | Lower `ownerIndex` or replace the probe. |
|
|
265
|
+
| `no-scheduleUpdate` | Production React build, or the devtools-hook shim was not installed before `react-dom` evaluated. | Use a dev build; ensure the shim runs at page boot (extension `document_start` / Playwright `addInitScript`). |
|
|
266
|
+
| `scheduleUpdate-threw` | React threw synchronously while scheduling. | Capture the page console — usually a render error masking the schedule. |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Snippet contract
|
|
271
|
+
|
|
272
|
+
`stability-nudge.js` (in `snippets/`) accepts:
|
|
273
|
+
|
|
274
|
+
| arg | required | meaning |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `--probeId <id>` | one of these two | probe id (`1`, `2`, `3`) — resolves via placed probe |
|
|
277
|
+
| `--testId <string>` | one of these two | `data-testid` attribute — resolves DOM → fiber directly |
|
|
278
|
+
| `--ownerIndex <N>` | no | walk N steps up the owner chain (0 = target itself) |
|
|
279
|
+
| `--label <string>` | no | echoed into `ctx.log` for human-readable trace |
|
|
280
|
+
| `--dryRun <true\|false>` | no | resolve + report fiber, do not schedule |
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"ok": true,
|
|
286
|
+
"fiberName": "StabilityChain",
|
|
287
|
+
"source": { "fileName": "...", "lineNumber": 88 },
|
|
288
|
+
"ownerIndex": 0,
|
|
289
|
+
"label": "parent-nudge"
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
See **Update mechanism → Failure modes** for the `ok: false` reasons.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Reporting format
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
📏 stability render: <FiberName> @ <file>:<line>
|
|
301
|
+
waves: <commitCount> renders: <componentRenders> collateral: <N>
|
|
302
|
+
verdict: clean | leak | leak+cascade | trasher
|
|
303
|
+
first offender: <ComponentName> @ <file>:<line> reason: <missing memo | unstable prop | unstable dep | …>
|
|
304
|
+
confidence: confirmed | likely | partial
|
|
305
|
+
next gap: <…>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Never report `clean` without a render-factor number. Never report `trasher` without naming the offending amplifier hook and the dependency that changed.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Boundaries
|
|
313
|
+
|
|
314
|
+
- **Active stimulus, zero hook/prop mutation.** The snippet schedules one render without changing a hook or prop. The extra commit is the stimulus, and render-time side effects or effects in the subtree may run.
|
|
315
|
+
- **One nudge per capture.** Multiple nudges in the same window confound attribution. Always `avalanche-clear` between nudges.
|
|
316
|
+
- **No interaction overlap.** The user must not be clicking the page during a nudge. State the rule before running.
|
|
317
|
+
- **Do not nudge providers from above.** A nudge on a context provider's parent will look like a context wave; that is not a stability test, that is a propagation test. Pick a sibling boundary instead.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Worked example — `memo → useCallback → useEffect` chain
|
|
322
|
+
|
|
323
|
+
Fixture pattern: `StabilityChain` demonstrates the unfixed identity chain. Fixed companion pattern: `StabilityChainFixed` demonstrates the corrected chain.
|
|
324
|
+
|
|
325
|
+
The chain Chippy is supposed to recognise:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Parent
|
|
329
|
+
└─ unstable object literal in render: opts = { mode }
|
|
330
|
+
└─ Leaf gets opts as prop (new identity every render)
|
|
331
|
+
└─ useCallback(handler, [opts]) ← deps include unstable object
|
|
332
|
+
└─ useEffect(..., [handler]) ← deps include unstable callback
|
|
333
|
+
└─ effect body: setState(...) ← schedules another render
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Three separate things go wrong, but they are NOT three independent bugs. They are one upstream identity flip flowing downhill into progressively worse symptoms.
|
|
337
|
+
|
|
338
|
+
### What the capture should show
|
|
339
|
+
|
|
340
|
+
Nudge target: `Parent` (`StabilityChain`). One nudge.
|
|
341
|
+
|
|
342
|
+
| Signal | Expected reading |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `commitCount` | `>1` — the effect's `setState` schedules a follow-up commit |
|
|
345
|
+
| Top hot-source | `Leaf` — re-renders not because of its own logic, but because its effect dispatches |
|
|
346
|
+
| `hookChanges[]` on `Leaf` | a state hook flipping each commit (the effect-driven setState) |
|
|
347
|
+
| Effect re-attach count | grows linearly with commits — visible in DOM as `attach/detach: N/N-1` |
|
|
348
|
+
|
|
349
|
+
That last row is the smoking gun. **An effect re-attaching every commit is not a perf issue, it is a correctness issue.** Subscriptions get torn down and rebuilt; cleanup ordering matters; analytics double-fire; fetches race themselves.
|
|
350
|
+
|
|
351
|
+
### Recognising the chain — top → bottom
|
|
352
|
+
|
|
353
|
+
Chippy reads the chain in **damage order**, not in source order:
|
|
354
|
+
|
|
355
|
+
1. **Effect re-fire** = worst. Run `avalanche-commit-peek` on the top commit, look at `Leaf`'s hookChanges. If a `useEffect` is in the changed-hook list every commit, the effect is re-firing.
|
|
356
|
+
2. **Callback identity flip** = the cause of the effect re-fire. Inspect `Leaf`'s `useCallback` (probe-hooks shows the dep array). If the dep is a non-primitive that changes identity, that's the link.
|
|
357
|
+
3. **Object identity flip** = the cause of the callback identity flip. Walk `_debugOwner` up to `Parent`, read `probe-props`. If `opts` is a fresh object each render with no upstream `useMemo`, that's the root.
|
|
358
|
+
|
|
359
|
+
This is **top → bottom in the codebase** (Parent at the top of the file owns the root cause) but **bottom → top in the avalanche reading** (the symptom shows up at the leaf, the cause is at the parent). Do not reverse the order.
|
|
360
|
+
|
|
361
|
+
### Fix order — effect first, then memo/callback
|
|
362
|
+
|
|
363
|
+
These are remediation candidates for the worked pattern. A source edit still requires user intent plus the P-15 proof packet and P-16 Apply gate; verify any authorized edit with the same nudge.
|
|
364
|
+
|
|
365
|
+
Prioritize the effect retrigger as the correctness issue. Memo/callback fixes come afterward when they are still needed to prevent the wider avalanche.
|
|
366
|
+
|
|
367
|
+
Why this order matters:
|
|
368
|
+
|
|
369
|
+
- Fixing the memo/callback first hides the effect bug. The effect stops re-firing because its deps stop flipping — but if a future change reintroduces an unstable dep, the effect breaks again silently.
|
|
370
|
+
- Fixing the effect first forces the question "why is this effect treating an unstable identity as semantic input?" — which usually reveals the effect was wrong about what its real dependency is (`opts.mode`, not `opts`).
|
|
371
|
+
|
|
372
|
+
Three iterative steps. Each is verified with one stability-render capture before moving on.
|
|
373
|
+
|
|
374
|
+
#### Step 1 — stop the effect from re-firing (correctness fix)
|
|
375
|
+
|
|
376
|
+
The effect attaches a subscription and dispatches state. It depends on `handler`, but what it actually cares about is `opts.mode` — a primitive. The fix is one of:
|
|
377
|
+
|
|
378
|
+
- Narrow the dep to the primitive: depend on `opts.mode`, not on `handler`.
|
|
379
|
+
- Read the current `handler` via a ref inside the effect, so the effect attaches once and the body always uses the latest closure.
|
|
380
|
+
- If the subscription is the only side effect, move attach/detach to a `useLayoutEffect` keyed on the stable subscription target rather than on a callback identity.
|
|
381
|
+
|
|
382
|
+
**Pointer for the agent:**
|
|
383
|
+
- File: `src/stability/StabilityChain.jsx`, `Leaf` component, `useEffect(..., [handler])`.
|
|
384
|
+
- Question to answer before editing: *what does this effect semantically depend on — the function identity, or the data the function reads?*
|
|
385
|
+
- After the fix, re-run stability-render. Expected: `commitCount` drops to 1; `attach/detach` no longer grows per nudge.
|
|
386
|
+
|
|
387
|
+
#### Step 2 — stabilise the callback (avalanche prevention)
|
|
388
|
+
|
|
389
|
+
After Step 1 the effect is fine, but the callback still flips identity every render. That is harmless on its own — but the moment a memoised child uses it as a prop, the cascade comes back. Fix this *before* memoising any children, not *after*.
|
|
390
|
+
|
|
391
|
+
**Pointer for the agent:**
|
|
392
|
+
- File: `src/stability/StabilityChain.jsx`, `Leaf`, `useCallback(handler, [opts])`.
|
|
393
|
+
- Same dep narrowing logic as Step 1: depend on `opts.mode`, not on `opts`. The callback's body only uses `opts.mode`, so the wider dep is overcautious and breaks memoisation.
|
|
394
|
+
- After the fix, re-run stability-render. Expected: callback identity stable across nudges; `Leaf`'s render count for the nudge stays at 1.
|
|
395
|
+
|
|
396
|
+
#### Step 3 — stabilise the upstream object (defence in depth)
|
|
397
|
+
|
|
398
|
+
`Parent` builds `opts = { mode }` inline. Step 1 + Step 2 made downstream consumers immune to that, but the next consumer added to this subtree will get the same trap. Wrap the object once.
|
|
399
|
+
|
|
400
|
+
**Pointer for the agent:**
|
|
401
|
+
- File: `src/stability/StabilityChain.jsx`, `StabilityChain`, `const opts = { mode };`.
|
|
402
|
+
- Wrap with `useMemo(() => ({ mode }), [mode])` — or, if `mode` is the only field, pass `mode` directly and drop the object.
|
|
403
|
+
- After the fix, re-run stability-render. Expected: same numbers as after Step 2. This step is *prevention*, not *repair* — measurable improvement is "no regression when a new memoised consumer is added".
|
|
404
|
+
|
|
405
|
+
### What "deep explanation" means here
|
|
406
|
+
|
|
407
|
+
When Chippy reports findings on this fixture, every pointer must answer three questions:
|
|
408
|
+
|
|
409
|
+
1. **What flips?** Name the identity that changes. (`opts` object reference; `handler` function reference; `useEffect` cleanup/attach pair.)
|
|
410
|
+
2. **Why does that flip propagate?** Name the dep array or prop boundary it crosses. (Dep array of `useCallback`; dep array of `useEffect`; prop `opts` from Parent to Leaf.)
|
|
411
|
+
3. **What invariant breaks?** Name the runtime consequence, not just "re-render". (Subscription torn down each commit; effect cleanup races body; setState-in-effect creates a render loop bounded only by React's max nested-update guard.)
|
|
412
|
+
|
|
413
|
+
If a pointer omits any of those, it is not a deep enough explanation. State the gap explicitly rather than papering over it.
|
|
414
|
+
|
|
415
|
+
### What this section is NOT
|
|
416
|
+
|
|
417
|
+
- Not a fix. Chippy proposes pointers; the agent edits.
|
|
418
|
+
- Not a rewrite. Three small, independent diffs in one file.
|
|
419
|
+
- Not a "boil the ocean" memoisation pass. Only the three identities on the chain that the capture proved are involved.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Cross-links
|
|
424
|
+
|
|
425
|
+
- [avalanche-detector](./avalanche-detector.md) — capture surface, commit join, restart semantics.
|
|
426
|
+
- [fixing-performance](./fixing-performance.md) — where the fix lives (above / boundary / below).
|
|
427
|
+
- [browser-runtime-api](./browser-runtime-api.md) — `__resolveProbes`, `__inspectReact`.
|
|
428
|
+
- [hooks-inspection](./hooks-inspection.md) — hook walk shape, used to read `hookChanges` after a nudge.
|