@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,52 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { PROBE_INTENTS, normalizeProbeAnnotation } from './probe-intent.js';
|
|
3
|
+
|
|
4
|
+
const INTENT_LABELS = {
|
|
5
|
+
explain: 'Explain',
|
|
6
|
+
investigate: 'Investigate',
|
|
7
|
+
change: 'Change',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function ProbeAnnotationEditor({ markerId, intent, comment, onAttach }) {
|
|
11
|
+
const [draft, setDraft] = useState(() => normalizeProbeAnnotation({ intent, comment }));
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setDraft(normalizeProbeAnnotation({ intent, comment }));
|
|
15
|
+
}, [intent, comment]);
|
|
16
|
+
|
|
17
|
+
const changeIntent = (event) => {
|
|
18
|
+
const next = normalizeProbeAnnotation({ ...draft, intent: event.target.value || null });
|
|
19
|
+
setDraft(next);
|
|
20
|
+
onAttach?.(next);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const commitComment = (event) => {
|
|
24
|
+
if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent?.isComposing) return;
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
onAttach?.(normalizeProbeAnnotation({ ...draft, comment: event.currentTarget.value }));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{ display: 'grid', gap: 6, marginTop: 8 }}>
|
|
32
|
+
<select
|
|
33
|
+
aria-label={`P${markerId} intent`}
|
|
34
|
+
value={draft.intent || ''}
|
|
35
|
+
onChange={changeIntent}
|
|
36
|
+
style={{ width: '100%', padding: '5px 7px', borderRadius: 6 }}
|
|
37
|
+
>
|
|
38
|
+
<option value="">Choose intent</option>
|
|
39
|
+
{PROBE_INTENTS.map((value) => <option key={value} value={value}>{INTENT_LABELS[value]}</option>)}
|
|
40
|
+
</select>
|
|
41
|
+
<textarea
|
|
42
|
+
aria-label={`P${markerId} comment`}
|
|
43
|
+
maxLength={2000}
|
|
44
|
+
placeholder="What should Chippy do with this probe?"
|
|
45
|
+
value={draft.comment}
|
|
46
|
+
onChange={(event) => setDraft((current) => ({ ...current, comment: event.target.value }))}
|
|
47
|
+
onKeyDown={commitComment}
|
|
48
|
+
style={{ boxSizing: 'border-box', width: '100%', minHeight: 58, resize: 'vertical', padding: '6px 7px', borderRadius: 6 }}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/layout-lab/probe-intent.ts
|
|
2
|
+
var PROBE_INTENTS = ["explain", "investigate", "change"];
|
|
3
|
+
var MAX_COMMENT_LENGTH = 2e3;
|
|
4
|
+
var INTENT_SET = new Set(PROBE_INTENTS);
|
|
5
|
+
function normalizeProbeAnnotation(value) {
|
|
6
|
+
const input = value && typeof value === "object" ? value : {};
|
|
7
|
+
const rawIntent = typeof input.intent === "string" ? input.intent.trim().toLowerCase() : "";
|
|
8
|
+
return {
|
|
9
|
+
intent: INTENT_SET.has(rawIntent) ? rawIntent : null,
|
|
10
|
+
comment: typeof input.comment === "string" ? input.comment.slice(0, MAX_COMMENT_LENGTH) : ""
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function updateProbeAnnotation(current, patch) {
|
|
14
|
+
return normalizeProbeAnnotation({
|
|
15
|
+
intent: patch.intent === void 0 ? current.intent : patch.intent,
|
|
16
|
+
comment: patch.comment === void 0 ? current.comment : patch.comment
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
PROBE_INTENTS,
|
|
21
|
+
normalizeProbeAnnotation,
|
|
22
|
+
updateProbeAnnotation
|
|
23
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const OWNER_ID = 'storybook-addon';
|
|
2
|
+
const OWNER_TYPE = 'storybook-addon';
|
|
3
|
+
const HELPER_BASE_URL = 'http://127.0.0.1:7823';
|
|
4
|
+
const STALE_AFTER_MS = 15_000;
|
|
5
|
+
const INTEGRATION = Object.freeze({
|
|
6
|
+
kind: 'storybook',
|
|
7
|
+
who: 'Chippy Storybook addon',
|
|
8
|
+
source: '.storybook/chippy-addon/setup.js',
|
|
9
|
+
reinjectsOnReload: true,
|
|
10
|
+
reloadAction: 'Chippy remounts the current SUT through Storybook FORCE_REMOUNT without reloading the preview iframe.',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function lookupAvalancheArm() {
|
|
14
|
+
try {
|
|
15
|
+
const url = `${HELPER_BASE_URL.replace(/\/+$/, '')}/__chippy/api/avalanche-arm?origin=${encodeURIComponent(location.href)}`;
|
|
16
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(1000) });
|
|
17
|
+
if (!response.ok) return null;
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
return data && data.armed && data.config ? data.config : null;
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const avalancheArm = await lookupAvalancheArm();
|
|
26
|
+
|
|
27
|
+
function acquireOwner(mode = 'chippy') {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const current = globalThis.__CHIPPY_OWNER__ || null;
|
|
30
|
+
if (
|
|
31
|
+
!current ||
|
|
32
|
+
current.ownerId === OWNER_ID ||
|
|
33
|
+
(current.heartbeatAt && now - current.heartbeatAt > STALE_AFTER_MS)
|
|
34
|
+
) {
|
|
35
|
+
const owner = {
|
|
36
|
+
...(current?.ownerId === OWNER_ID ? current : {}),
|
|
37
|
+
ownerId: OWNER_ID,
|
|
38
|
+
ownerType: OWNER_TYPE,
|
|
39
|
+
transport: 'storybook-static',
|
|
40
|
+
ui: 'storybook-panel',
|
|
41
|
+
mode,
|
|
42
|
+
helperBaseUrl: HELPER_BASE_URL,
|
|
43
|
+
integration: INTEGRATION,
|
|
44
|
+
staleAfterMs: STALE_AFTER_MS,
|
|
45
|
+
startedAt: current?.ownerId === OWNER_ID ? current.startedAt || now : now,
|
|
46
|
+
heartbeatAt: now,
|
|
47
|
+
};
|
|
48
|
+
globalThis.__CHIPPY_OWNER__ = owner;
|
|
49
|
+
globalThis.__CHIPPY_LOCK__ = globalThis.__CHIPPY_LOCK__ || {};
|
|
50
|
+
globalThis.__CHIPPY_LOCK__.get = () => globalThis.__CHIPPY_OWNER__ || null;
|
|
51
|
+
globalThis.__CHIPPY_LOCK__.heartbeat = (ownerId, patch = {}) => {
|
|
52
|
+
if (globalThis.__CHIPPY_OWNER__?.ownerId !== ownerId) {
|
|
53
|
+
return { ok: false, reason: 'not-owner', owner: globalThis.__CHIPPY_OWNER__ || null };
|
|
54
|
+
}
|
|
55
|
+
globalThis.__CHIPPY_OWNER__ = { ...globalThis.__CHIPPY_OWNER__, ...patch, heartbeatAt: Date.now() };
|
|
56
|
+
return { ok: true, owner: globalThis.__CHIPPY_OWNER__ };
|
|
57
|
+
};
|
|
58
|
+
globalThis.__CHIPPY_LOCK__.release = (ownerId) => {
|
|
59
|
+
if (globalThis.__CHIPPY_OWNER__?.ownerId !== ownerId) {
|
|
60
|
+
return { ok: false, reason: 'not-owner', owner: globalThis.__CHIPPY_OWNER__ || null };
|
|
61
|
+
}
|
|
62
|
+
const owner = globalThis.__CHIPPY_OWNER__;
|
|
63
|
+
delete globalThis.__CHIPPY_OWNER__;
|
|
64
|
+
return { ok: true, owner };
|
|
65
|
+
};
|
|
66
|
+
return { ok: true, owner, reused: current?.ownerId === OWNER_ID, replacedStale: !!(current && current.ownerId !== OWNER_ID) };
|
|
67
|
+
}
|
|
68
|
+
return { ok: false, reason: 'owned-by-other-chippy', owner: current };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ownerResult = acquireOwner('chippy');
|
|
72
|
+
globalThis.__CHIPPY_OWNER_RESULT__ = ownerResult;
|
|
73
|
+
if (!ownerResult.ok) {
|
|
74
|
+
globalThis.__CHIPPY_BLOCKED__ = ownerResult;
|
|
75
|
+
} else {
|
|
76
|
+
globalThis.__CHIPPY_STARTUP_CONFIG = {
|
|
77
|
+
...(globalThis.__CHIPPY_STARTUP_CONFIG || {}),
|
|
78
|
+
helperBaseUrl: HELPER_BASE_URL,
|
|
79
|
+
activated: false,
|
|
80
|
+
ui: 'storybook-panel',
|
|
81
|
+
ownerType: OWNER_TYPE,
|
|
82
|
+
ownerId: OWNER_ID,
|
|
83
|
+
transport: 'storybook-static',
|
|
84
|
+
mode: 'chippy',
|
|
85
|
+
integration: INTEGRATION,
|
|
86
|
+
avalanche: avalancheArm,
|
|
87
|
+
staleAfterMs: STALE_AFTER_MS,
|
|
88
|
+
};
|
|
89
|
+
globalThis.__CHIPPY = globalThis.__CHIPPY || {};
|
|
90
|
+
globalThis.__CHIPPY.config = { ...(globalThis.__CHIPPY.config || {}), ...globalThis.__CHIPPY_STARTUP_CONFIG };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { HELPER_BASE_URL, INTEGRATION, OWNER_ID, OWNER_TYPE, STALE_AFTER_MS, ownerResult };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit metrics adapter: an optional per-commit counter source that sits next
|
|
3
|
+
* to the DevTools fiber walk.
|
|
4
|
+
*
|
|
5
|
+
* The detector always captures commits through the DevTools hook. Some hosts
|
|
6
|
+
* also run an instrumented react-dom that exposes render counters and calls a
|
|
7
|
+
* commit callback of its own. An adapter wraps one such host surface: it seeds
|
|
8
|
+
* and protects the surface, snapshots its counters, turns two snapshots into a
|
|
9
|
+
* per-commit delta plus a restart count, and wires the host's commit callback
|
|
10
|
+
* to the detector. Without an adapter the detector runs in `devtools` mode and
|
|
11
|
+
* reports restarts as unavailable.
|
|
12
|
+
*
|
|
13
|
+
* One adapter is active at a time. Product adapters live in `src/plugins/`.
|
|
14
|
+
*/
|
|
15
|
+
import type { Fiber } from './internals.js';
|
|
16
|
+
export type CommitMetricsRoot = {
|
|
17
|
+
current: Fiber;
|
|
18
|
+
memoizedUpdaters?: Set<Fiber>;
|
|
19
|
+
};
|
|
20
|
+
/** Raw counters read from the host surface at one point in time. */
|
|
21
|
+
export type CommitMetricsSnapshot = Record<string, unknown>;
|
|
22
|
+
/** Counter deltas for one commit, keyed by counter name. */
|
|
23
|
+
export type CommitMetricsDelta = {
|
|
24
|
+
[counter: string]: number | undefined;
|
|
25
|
+
};
|
|
26
|
+
export type CommitMetricsMeasurement = {
|
|
27
|
+
delta: CommitMetricsDelta;
|
|
28
|
+
/** Render passes React performed but did not commit. Never negative. */
|
|
29
|
+
restarts: number;
|
|
30
|
+
};
|
|
31
|
+
export interface CommitMetricsAdapter {
|
|
32
|
+
/** Stable id; the detector reports `integrationMode: 'adapter:<id>'`. */
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/**
|
|
35
|
+
* Stack frame names the host surface adds to every render. Hook ancestry
|
|
36
|
+
* hides them like other renderer internals.
|
|
37
|
+
*/
|
|
38
|
+
readonly frameFilters?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Setup that must run before React evaluates, such as installing an accessor
|
|
41
|
+
* that survives host cleanup. Called by the extension's early bootstrap
|
|
42
|
+
* through `__AVALANCHE__.runPrePageHooks(config)`.
|
|
43
|
+
*/
|
|
44
|
+
prePage?(config: unknown): void;
|
|
45
|
+
/** Seed the host surface before a detector installs, unless the host owns it. */
|
|
46
|
+
prepare?(): void;
|
|
47
|
+
/** Whether the host surface is present right now. */
|
|
48
|
+
isAvailable(): boolean;
|
|
49
|
+
snapshot(): CommitMetricsSnapshot | null;
|
|
50
|
+
measure(before: CommitMetricsSnapshot, after: CommitMetricsSnapshot, committedComponents: number, commitId: number): CommitMetricsMeasurement;
|
|
51
|
+
/**
|
|
52
|
+
* Route the host's commit callback to `onCommit`. Returns false when the
|
|
53
|
+
* surface is absent. A callback is proof that the host surface is live.
|
|
54
|
+
*/
|
|
55
|
+
attach(onCommit: (root: CommitMetricsRoot) => void): boolean;
|
|
56
|
+
/** True while the callback from attach() is still wired into the live surface. */
|
|
57
|
+
isAttached(): boolean;
|
|
58
|
+
/** Re-seed and re-wire after the host wiped or replaced its surface. */
|
|
59
|
+
reattach(): void;
|
|
60
|
+
/** Remove the callback from attach(), only if it is still ours. */
|
|
61
|
+
detach(): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Make `adapter` the active commit metrics adapter for detectors installed
|
|
65
|
+
* afterwards. Replaces any earlier adapter. Returns an unregister function.
|
|
66
|
+
*/
|
|
67
|
+
export declare function registerCommitMetricsAdapter(adapter: CommitMetricsAdapter): () => void;
|
|
68
|
+
export declare function getCommitMetricsAdapter(): CommitMetricsAdapter | null;
|
|
69
|
+
/**
|
|
70
|
+
* Run the active adapter's pre-page hook. Returns the adapter id when a hook
|
|
71
|
+
* ran, or null. A failing hook is swallowed: the detector falls back to
|
|
72
|
+
* DevTools capture.
|
|
73
|
+
*/
|
|
74
|
+
export declare function runPrePageHooks(config?: unknown): string | null;
|
|
75
|
+
export type IntegrationMode = `adapter:${string}` | 'devtools' | 'none';
|
|
76
|
+
export declare function adapterIdFromMode(mode: IntegrationMode | string | null | undefined): string | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named constants for CommitStats tuple indices.
|
|
3
|
+
*
|
|
4
|
+
* CommitStats is a fixed 10-position tuple per commit. This module provides
|
|
5
|
+
* human-readable names for each index to replace magic numbers in code.
|
|
6
|
+
*
|
|
7
|
+
* Tuple structure:
|
|
8
|
+
* [0] collateral — total updates caused by this source (collateral damage)
|
|
9
|
+
* [1] sources — number of distinct sources that fired in this commit
|
|
10
|
+
* [2] restarts — render count from ATL render-counter delta (or 0 if not available)
|
|
11
|
+
* [3] performedWork — components with PerformedWork flag set
|
|
12
|
+
* [4] effects — effect count
|
|
13
|
+
* [5] memos — memo count
|
|
14
|
+
* [6] callbacks — callback count
|
|
15
|
+
* [7] contextUpdates — context update count
|
|
16
|
+
* [8] unmountedComponents — number of unmounted components
|
|
17
|
+
* [9] unmountedSubtrees — number of unmounted subtrees
|
|
18
|
+
*
|
|
19
|
+
* This positional layout is shared by the detector, serializer, bridge, and
|
|
20
|
+
* offline readers. Changing an index is a wire-format change even though the
|
|
21
|
+
* tuple remains valid TypeScript, so keep the names and length together.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CS_COLLATERAL = 0;
|
|
24
|
+
export declare const CS_SOURCES = 1;
|
|
25
|
+
export declare const CS_RESTARTS = 2;
|
|
26
|
+
export declare const CS_PERFORMED_WORK = 3;
|
|
27
|
+
export declare const CS_EFFECTS = 4;
|
|
28
|
+
export declare const CS_MEMOS = 5;
|
|
29
|
+
export declare const CS_CALLBACKS = 6;
|
|
30
|
+
export declare const CS_CONTEXT_UPDATES = 7;
|
|
31
|
+
export declare const CS_UNMOUNTED_COMPONENTS = 8;
|
|
32
|
+
export declare const CS_UNMOUNTED_SUBTREES = 9;
|
|
33
|
+
/**
|
|
34
|
+
* Total length of the CommitStats tuple.
|
|
35
|
+
* Used for validation and bounds checking.
|
|
36
|
+
*/
|
|
37
|
+
export declare const COMMIT_STATS_LENGTH = 10;
|
|
38
|
+
/**
|
|
39
|
+
* Array of all commitStats indices for iteration/validation.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ALL_COMMIT_STATS_INDICES: readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CommitRoot {
|
|
2
|
+
current: object;
|
|
3
|
+
}
|
|
4
|
+
export declare function createCommitSurfaceOwnership<T>(): {
|
|
5
|
+
readonly adapterObserved: boolean;
|
|
6
|
+
recordDevtools(root: CommitRoot, capture: T): void;
|
|
7
|
+
observeAdapter(root: CommitRoot): T | null;
|
|
8
|
+
reset(): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless Profiler - React commit profiler (headless / no UI).
|
|
3
|
+
*
|
|
4
|
+
* This file intentionally keeps the commit traversal (`recordTree`) monolithic.
|
|
5
|
+
* It runs on every React commit, and even small function-call overhead inside the
|
|
6
|
+
* fiber-walk loops shows up in perf. We refactor everything *around* it.
|
|
7
|
+
*
|
|
8
|
+
* Detection Levels:
|
|
9
|
+
* - Level 1 (STATS): Only commitStats - <1% overhead (~0.8ms avg)
|
|
10
|
+
* - Level 2 (SOURCES): + source tracking - ~1-2% overhead (~1.3ms avg)
|
|
11
|
+
* - Level 3 (AMPLIFIERS/FULL): + amplifier detection - ~2-3% overhead (~2.4ms avg)
|
|
12
|
+
*
|
|
13
|
+
* Peer FAQ
|
|
14
|
+
* - Why is `recordTree()` huge? Performance: it's on the hot path (every commit).
|
|
15
|
+
* - Why per-hook actionability? React can leave alternate hook memoizedState
|
|
16
|
+
* undefined (Suspense/Offscreen/concurrent artifacts) causing false positives.
|
|
17
|
+
* - Why WeakMaps everywhere? To auto-GC tracked values/fibers without leaks.
|
|
18
|
+
* - Why so few null checks? Debug tool: fail fast and loud on misuse/internals drift.
|
|
19
|
+
* - Production? Use LEVEL1_STATS or LEVEL2_SOURCES for minimal overhead.
|
|
20
|
+
*/
|
|
21
|
+
import { type DetectorOptions, type Report, type ReportOptions, type SortBy } from './reporter.js';
|
|
22
|
+
import { type HookCallEvent, type StackFrame } from '../debug-render/core.js';
|
|
23
|
+
import { type IntegrationMode } from './commit-metrics-adapter.js';
|
|
24
|
+
/**
|
|
25
|
+
* Installable detector.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createAvalancheDetector(options?: DetectorOptions): {
|
|
28
|
+
/**
|
|
29
|
+
* Start profiling React commits.
|
|
30
|
+
*
|
|
31
|
+
* Integration model is OBSERVED at runtime, not declared at install time.
|
|
32
|
+
*
|
|
33
|
+
* 'devtools' — DevTools `onCommitFiberRoot` is the canonical entry
|
|
34
|
+
* point. We define the hook if absent, augment (chain)
|
|
35
|
+
* if present. On every commit we capture the fiber tree.
|
|
36
|
+
*
|
|
37
|
+
* 'adapter:<id>' — Promoted from 'devtools' the first time the commit
|
|
38
|
+
* metrics adapter's host callback actually fires for a
|
|
39
|
+
* real commit. From then on the adapter is the
|
|
40
|
+
* authoritative processor and the DevTools surface is
|
|
41
|
+
* chained-observer-only. The adapter is preferred when
|
|
42
|
+
* alive because it carries render-counter deltas
|
|
43
|
+
* (restart math, metricsByCommit).
|
|
44
|
+
*
|
|
45
|
+
* 'none' — Only when no DevTools hook surface can be created and
|
|
46
|
+
* no adapter surface is reachable. Practically never
|
|
47
|
+
* hit, since the content-script ships a
|
|
48
|
+
* devtools-hook-shim before react-dom.
|
|
49
|
+
*
|
|
50
|
+
* Double-invocation safety:
|
|
51
|
+
* Once an adapter callback has fired, DevTools remains chained for
|
|
52
|
+
* existing tooling but does not capture commits; the adapter alone calls
|
|
53
|
+
* `handleCommit`. Without a live adapter, DevTools is the capture
|
|
54
|
+
* surface. This avoids duplicate capture without relying on per-root
|
|
55
|
+
* timing heuristics.
|
|
56
|
+
*
|
|
57
|
+
* Side effects:
|
|
58
|
+
* - Defines `__REACT_DEVTOOLS_GLOBAL_HOOK__` if it does not already exist
|
|
59
|
+
* (minimal contract: `onCommitFiberRoot` only). React reads the hook at
|
|
60
|
+
* module-eval time; the content-script shim handles ordering.
|
|
61
|
+
* - Asks the adapter (when one is registered) to route its host commit
|
|
62
|
+
* callback here, *if and when* the host calls it.
|
|
63
|
+
*
|
|
64
|
+
* Safe to call multiple times (idempotent: chains skipped when our wrappers
|
|
65
|
+
* are already in place).
|
|
66
|
+
*/
|
|
67
|
+
install(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Stop profiling React commits. Does not clear accumulated state.
|
|
70
|
+
* Call reset() to clear state.
|
|
71
|
+
*
|
|
72
|
+
* Restores the DevTools hook to its prior state:
|
|
73
|
+
* - hook was created by us → delete the hook entirely
|
|
74
|
+
* - hook had a prior onCommitFiberRoot → restore it
|
|
75
|
+
* - hook had no handler → delete the property
|
|
76
|
+
*
|
|
77
|
+
* Asks the adapter to remove its host callback only if it is still ours
|
|
78
|
+
* (don't stomp a concurrent installer).
|
|
79
|
+
*/
|
|
80
|
+
uninstall(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Clear all accumulated profiling state. Useful for starting fresh measurements.
|
|
83
|
+
* Does not uninstall the profiler - call uninstall() separately if needed.
|
|
84
|
+
*/
|
|
85
|
+
reset(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Generate a report of top update sources and thrashing components.
|
|
88
|
+
* @param topN - Number of top sources to include (default: 10)
|
|
89
|
+
* @param sortBy - Sort thrashers by 'effect', 'callback', 'memo', or 'total' (default: 'effect')
|
|
90
|
+
* @param options - Report filtering options (e.g., minCollateral to hide low-impact sources)
|
|
91
|
+
* @returns Report containing sources, blast radius, and optional thrashing data
|
|
92
|
+
*/
|
|
93
|
+
report(topN?: number, sortBy?: SortBy, reportOptions?: ReportOptions): Report;
|
|
94
|
+
/**
|
|
95
|
+
* Raw passive hook events captured for a component (debug surface).
|
|
96
|
+
*/
|
|
97
|
+
_debugRawHookEvents(componentName: string): {
|
|
98
|
+
ok: boolean;
|
|
99
|
+
componentName: string;
|
|
100
|
+
status: import("../debug-render/core").CaptureEngineStatus;
|
|
101
|
+
components: string[];
|
|
102
|
+
events: HookCallEvent[];
|
|
103
|
+
};
|
|
104
|
+
_debugInjectHookEventsForTest(componentName: string, events: HookCallEvent[]): void;
|
|
105
|
+
_debugSetSourceForTest(sourceId: string, source: unknown): void;
|
|
106
|
+
debugHook(sourceId: string, hookIndex: number, windowSize?: number): {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
error: string;
|
|
109
|
+
sourceId: string;
|
|
110
|
+
requestedSourceId?: undefined;
|
|
111
|
+
componentName?: undefined;
|
|
112
|
+
resolvedComponentName?: undefined;
|
|
113
|
+
hookIndex?: undefined;
|
|
114
|
+
eventIndex?: undefined;
|
|
115
|
+
sourceHookIndices?: undefined;
|
|
116
|
+
eventCount?: undefined;
|
|
117
|
+
status?: undefined;
|
|
118
|
+
caveat?: undefined;
|
|
119
|
+
primitive?: undefined;
|
|
120
|
+
parents?: undefined;
|
|
121
|
+
neighborhood?: undefined;
|
|
122
|
+
} | {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
error: string;
|
|
125
|
+
sourceId: string;
|
|
126
|
+
requestedSourceId: string;
|
|
127
|
+
componentName: string;
|
|
128
|
+
resolvedComponentName: string;
|
|
129
|
+
hookIndex: number;
|
|
130
|
+
eventIndex: number;
|
|
131
|
+
sourceHookIndices: number[];
|
|
132
|
+
eventCount: number;
|
|
133
|
+
status: import("../debug-render/core").CaptureEngineStatus;
|
|
134
|
+
caveat: string;
|
|
135
|
+
primitive?: undefined;
|
|
136
|
+
parents?: undefined;
|
|
137
|
+
neighborhood?: undefined;
|
|
138
|
+
} | {
|
|
139
|
+
ok: boolean;
|
|
140
|
+
sourceId: string;
|
|
141
|
+
requestedSourceId: string;
|
|
142
|
+
componentName: string;
|
|
143
|
+
resolvedComponentName: string;
|
|
144
|
+
hookIndex: number;
|
|
145
|
+
eventIndex: number;
|
|
146
|
+
sourceHookIndices: number[];
|
|
147
|
+
primitive: string;
|
|
148
|
+
parents: StackFrame[];
|
|
149
|
+
neighborhood: {
|
|
150
|
+
hookIndex: number;
|
|
151
|
+
eventIndex: number;
|
|
152
|
+
primitive: string;
|
|
153
|
+
parents: StackFrame[];
|
|
154
|
+
}[];
|
|
155
|
+
eventCount: number;
|
|
156
|
+
status: import("../debug-render/core").CaptureEngineStatus;
|
|
157
|
+
caveat: string;
|
|
158
|
+
error?: undefined;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Returns which commit-hook integration model is currently observed.
|
|
162
|
+
* 'devtools' — DevTools hook is the active processor (no proven adapter callbacks).
|
|
163
|
+
* 'adapter:<id>' — The adapter's host surface has fired; it owns the math now.
|
|
164
|
+
* 'none' — Detector not installed.
|
|
165
|
+
*/
|
|
166
|
+
getIntegrationMode(): IntegrationMode;
|
|
167
|
+
/** Id of the commit metrics adapter resolved on install, or null. */
|
|
168
|
+
getMetricsAdapterId(): string | null;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Detection level constants - use these for clarity about the hierarchy.
|
|
172
|
+
* Higher levels include all features from lower levels.
|
|
173
|
+
*/
|
|
174
|
+
export declare const DETECTION_LEVEL: {
|
|
175
|
+
/** Level 1: Only commitStats (performedWork for restarts) - minimal overhead */
|
|
176
|
+
readonly LEVEL1_STATS: "stats";
|
|
177
|
+
/** Level 2: Stats + source tracking (which components caused updates) */
|
|
178
|
+
readonly LEVEL2_SOURCES: "sources";
|
|
179
|
+
/** Level 3a: Sources + aggregate amplifier counts only (meta.amplifiersMetrics) */
|
|
180
|
+
readonly LEVEL3_AMPLIFIER_METRICS: "amplifierMetrics";
|
|
181
|
+
/** Level 3b: Sources + full amplifier arrays (report.amplifiers, report.amplifierSources) */
|
|
182
|
+
readonly LEVEL3_AMPLIFIERS: "amplifiers";
|
|
183
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records user-side interaction anchors while Avalanche records commits.
|
|
3
|
+
* Capture is intentionally DOM-event based: the detector owns fiber/commit
|
|
4
|
+
* evidence, while this module owns the only signal that a human interaction
|
|
5
|
+
* preceded it. `linkRecent` joins the two later using performance timestamps;
|
|
6
|
+
* it is evidence of temporal proximity, not proof that the event caused every
|
|
7
|
+
* render in the commit.
|
|
8
|
+
*/
|
|
9
|
+
import type { CommitInteractionAction, InteractionAction, InteractionCaptureLevel } from './reporter.js';
|
|
10
|
+
export interface InteractionCaptureOptions {
|
|
11
|
+
interactionCapture?: InteractionCaptureLevel;
|
|
12
|
+
interactionEvents?: string[];
|
|
13
|
+
interactionWindowMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface InteractionCaptureState {
|
|
16
|
+
actions: InteractionAction[];
|
|
17
|
+
byCommit: Record<number, CommitInteractionAction[]>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createInteractionCapture(options?: InteractionCaptureOptions): {
|
|
20
|
+
state: InteractionCaptureState;
|
|
21
|
+
install(): void;
|
|
22
|
+
uninstall(): void;
|
|
23
|
+
reset(): void;
|
|
24
|
+
linkRecent(commitId: number, commitTime: number): void;
|
|
25
|
+
};
|