@generative-a11y/devtools 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bhavesh Chowdhury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # `@generative-a11y/devtools`
2
+
3
+ Opt-in, development-only diagnostics for generative-a11y runtimes. The default
4
+ headless store is framework-neutral, side-effect-free on import, and retains a
5
+ bounded redacted trace: it stores categories, timing, outcomes, stable runtime
6
+ IDs, queue/entity snapshots, and browser delivery metadata. It never retains
7
+ assistant text, labels, error messages, tool data, stacks, or DOM content.
8
+
9
+ ```sh
10
+ npm install --save-dev @generative-a11y/devtools
11
+ ```
12
+
13
+ ```ts
14
+ import { createDevtoolsStore } from "@generative-a11y/devtools";
15
+ import { THREAD_ADAPTER_METADATA } from "@generative-a11y/assistant-ui";
16
+
17
+ const store = createDevtoolsStore({ maxEntries: 250 });
18
+ const detach = store.attachRuntime({
19
+ id: "support",
20
+ runtime,
21
+ source: {
22
+ adapter: THREAD_ADAPTER_METADATA.name,
23
+ fidelity: THREAD_ADAPTER_METADATA.fidelity,
24
+ evidence: THREAD_ADAPTER_METADATA.observedRuntimeMethods,
25
+ },
26
+ });
27
+ const unsubscribe = store.subscribe(renderDiagnostics);
28
+
29
+ store.pauseCapture(); // Does not pause or alter the runtime.
30
+ store.resumeCapture();
31
+ store.refreshSnapshots();
32
+ const trace = store.exportTrace(); // schema-versioned and redacted
33
+ store.clear();
34
+
35
+ unsubscribe();
36
+ detach();
37
+ store.dispose();
38
+ ```
39
+
40
+ ## Store API
41
+
42
+ - `createDevtoolsStore({ maxEntries })` creates an isolated store. `maxEntries`
43
+ defaults to `250`, must be a positive safe integer, and bounds the retained
44
+ ring buffer. `droppedCount` reports records evicted since the last `clear()`.
45
+ - `attachRuntime({ id, runtime })` validates a non-empty ID, subscribes only to
46
+ public diagnostics, captures an initial safe snapshot, and returns an
47
+ idempotent detach function. Attaching the same ID replaces its subscription
48
+ only after the replacement subscribes successfully.
49
+ - `subscribe(listener)` observes store snapshot changes and returns an
50
+ idempotent unsubscribe function. Listener failures do not alter capture.
51
+ - `getSnapshot()` returns a cached, immutable, content-free view until captured
52
+ state changes. `refreshSnapshots()` explicitly requests fresh runtime
53
+ snapshots without changing the observed runtimes.
54
+ - `pauseCapture()` and `resumeCapture()` change devtools capture only. They do
55
+ not pause scheduling, delivery, or any host runtime behavior.
56
+ - `recordDelivery(input)` adds validated, content-free DOM delivery evidence. It
57
+ records nothing while capture is paused or after disposal, but malformed IDs
58
+ and timestamps still throw so integration defects remain visible.
59
+ - `exportTrace()` refreshes runtime snapshots and returns an immutable,
60
+ schema-versioned, redacted trace. `clear()` removes retained records and
61
+ resets `droppedCount` without detaching runtimes.
62
+ - `dispose()` is idempotent. It detaches all runtimes, clears captured state,
63
+ publishes one final empty snapshot to current subscribers, and then removes
64
+ those subscribers. Later attachment or subscription attempts throw; other
65
+ control methods are no-ops.
66
+
67
+ The store only subscribes through `subscribeDiagnosticEvents()` and does not
68
+ monkey-patch dispatch, access browser globals, create UI, retain a core history,
69
+ or alter accessibility policy. A captured DOM/API delivery remains evidence of
70
+ an action, not proof that assistive technology spoke it.
71
+
72
+ ## Declared adapter evidence
73
+
74
+ Pass `source` when attaching a runtime driven by an adapter. This is an
75
+ explicit, serializable declaration from the integration, not framework detection
76
+ by devtools. It lets an inspector show the adapter name, documented public
77
+ evidence and declared fidelity without filling gaps in the lifecycle trace.
78
+ `interruption` and `retries` accept `exact`, `action-wrapper`, or `unavailable`.
79
+ `connection` accepts those values plus `inferred`, because some integrations can
80
+ only derive connection state from another documented public signal. The store
81
+ freezes a copy of this metadata and includes it in its redacted export. Each
82
+ captured record references an opaque `runtimeSourceId`. Source revisions remain
83
+ immutable and exportable for as long as a retained record references them, even
84
+ after a runtime detaches or the same runtime ID is reattached with different
85
+ metadata. Unreferenced revisions are removed with ring-buffer eviction so
86
+ repeated attachment cannot create unbounded source history.
87
+
88
+ Use an adapter package's exported metadata where it fits the integration. For
89
+ custom adapters, provide only public signals that justify normalized events. Do
90
+ not put user content, internal URLs, or private framework state in `evidence`.
91
+ The store accepts at most 12 public evidence strings, each at most 120
92
+ characters, and rejects unsupported fidelity or optional-event values before it
93
+ subscribes to a runtime.
94
+
95
+ ## Browser delivery correlation
96
+
97
+ The store intentionally does not import `@generative-a11y/dom`. Connect the
98
+ announcer's public diagnostic callback yourself to capture a content-free
99
+ delivery record alongside runtime decisions:
100
+
101
+ ```ts
102
+ const announcer = createDOMAnnouncer({
103
+ onDiagnostic(result) {
104
+ store.recordDelivery({ runtimeId: "support", result });
105
+ },
106
+ });
107
+ ```
108
+
109
+ This exposes the browser-level method and status (`aria-notify`, fallback live
110
+ region, unavailable, or disposed) and safe correlation IDs. It still cannot
111
+ establish what a screen reader announced.
112
+
113
+ ## Explicit overlay
114
+
115
+ `@generative-a11y/devtools/overlay` is an optional browser-only mounting helper.
116
+ It creates one open Shadow DOM host only when called and starts collapsed. The
117
+ mounted workbench is built from package-local shadcn/Radix components, with its
118
+ styles contained inside that Shadow DOM. It provides one searchable, filterable
119
+ Accessibility Trace Explorer with a keyboard-managed trace list, selected causal
120
+ evidence, plain-language policy and delivery context, collapsed raw metadata,
121
+ capture pause/resume/clear/refresh actions, and explicit trace copy. These
122
+ actions affect only devtools capture, never runtime policy, queueing, focus, or
123
+ host UI.
124
+
125
+ Opening the overlay moves focus into the workbench; closing it restores the
126
+ element focused before the launcher was activated. Streaming records never move
127
+ focus. The overlay does not trap focus, create a live region, modify host
128
+ layout, or install global shortcuts.
129
+
130
+ ```ts
131
+ import { mountDevtoolsOverlay } from "@generative-a11y/devtools/overlay";
132
+
133
+ const overlay = mountDevtoolsOverlay({ store });
134
+ overlay.dispose();
135
+ ```
136
+
137
+ ## Documentation
138
+
139
+ - [Devtools guide](https://generativea11y.com/docs/devtools)
140
+ - [API reference](https://generativea11y.com/api/devtools)
141
+ - [Repository](https://github.com/bhaveshchow20/generative-a11y)
142
+
143
+ ## Related packages
144
+
145
+ - [`@generative-a11y/core`](https://www.npmjs.com/package/@generative-a11y/core)
146
+ exposes the versioned diagnostic stream and content-free snapshots.
147
+ - [`@generative-a11y/dom`](https://www.npmjs.com/package/@generative-a11y/dom)
148
+ exposes browser delivery results for safe correlation.
149
+ - [`@generative-a11y/core/testing`](https://generativea11y.com/api/core/testing)
150
+ provides deterministic replay and semantic test assertions without another
151
+ package installation.
package/dist/index.cjs ADDED
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createDevtoolsStore: () => createDevtoolsStore
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ function asRecord(runtimeId, event, captureSequence, runtimeSourceId) {
27
+ if (event.kind === "event-observed") {
28
+ return Object.freeze({
29
+ runtimeId,
30
+ ...runtimeSourceId ? { runtimeSourceId } : {},
31
+ sequence: event.sequence,
32
+ captureSequence,
33
+ at: event.at,
34
+ kind: event.kind,
35
+ sourceType: event.event.type,
36
+ ...event.event.eventId ? { sourceEventId: event.event.eventId } : {},
37
+ ..."responseId" in event.event ? { responseId: event.event.responseId } : {},
38
+ ..."responseInstanceId" in event.event && event.event.responseInstanceId ? { responseInstanceId: event.event.responseInstanceId } : {},
39
+ ..."nextResponseInstanceId" in event.event && event.event.nextResponseInstanceId ? { nextResponseInstanceId: event.event.nextResponseInstanceId } : {},
40
+ ..."attempt" in event.event && event.event.attempt !== void 0 ? { attempt: event.event.attempt } : {},
41
+ ..."toolId" in event.event ? { toolId: event.event.toolId } : {},
42
+ ..."toolInstanceId" in event.event && event.event.toolInstanceId ? { toolInstanceId: event.event.toolInstanceId } : {},
43
+ ..."interactionId" in event.event ? { interactionId: event.event.interactionId } : {},
44
+ ..."approvalId" in event.event ? { approvalId: event.event.approvalId } : {},
45
+ ..."progress" in event.event && event.event.progress !== void 0 ? { progress: event.event.progress } : {},
46
+ ..."outcome" in event.event ? { outcome: event.event.outcome } : {},
47
+ ..."count" in event.event ? { count: event.event.count } : {}
48
+ });
49
+ }
50
+ return Object.freeze({
51
+ runtimeId,
52
+ ...runtimeSourceId ? { runtimeSourceId } : {},
53
+ sequence: event.sequence,
54
+ captureSequence,
55
+ at: event.at,
56
+ kind: event.kind,
57
+ ...event.decision.sourceType ? { sourceType: event.decision.sourceType } : {},
58
+ ...event.decision.sourceEventId ? { sourceEventId: event.decision.sourceEventId } : {},
59
+ disposition: event.decision.disposition,
60
+ reason: event.decision.reason,
61
+ ...event.decision.announcement ? { announcementId: event.decision.announcement.id } : {},
62
+ ...event.decision.announcement ? { channel: event.decision.announcement.channel } : {},
63
+ ...event.decision.responseId ? { responseId: event.decision.responseId } : {},
64
+ ...event.decision.toolId ? { toolId: event.decision.toolId } : {},
65
+ ...event.decision.interactionId ? { interactionId: event.decision.interactionId } : {},
66
+ ...event.decision.scheduledAt !== void 0 ? { scheduledAt: event.decision.scheduledAt } : {},
67
+ ...event.decision.dueAt !== void 0 ? { dueAt: event.decision.dueAt } : {},
68
+ ...event.decision.delayMs !== void 0 ? { delayMs: event.decision.delayMs } : {},
69
+ ...event.decision.queueSequence !== void 0 ? { queueSequence: event.decision.queueSequence } : {}
70
+ });
71
+ }
72
+ function copyRuntimeSource(source) {
73
+ if (source === void 0) return void 0;
74
+ if (typeof source.adapter !== "string" || !source.adapter.trim() || source.adapter.length > 80)
75
+ throw new TypeError("source adapter must be a non-empty string");
76
+ if (!Array.isArray(source.evidence))
77
+ throw new TypeError("source evidence must be an array");
78
+ if (source.evidence.length > 12 || source.evidence.some(
79
+ (item) => typeof item !== "string" || !item.trim() || item.length > 120
80
+ ))
81
+ throw new TypeError(
82
+ "source evidence entries must be public strings up to 120 characters"
83
+ );
84
+ const { fidelity } = source;
85
+ if (typeof fidelity !== "object" || fidelity === null)
86
+ throw new TypeError("source fidelity must be an object");
87
+ const lifecycleFidelity = /* @__PURE__ */ new Set(["exact", "action-wrapper", "unavailable"]);
88
+ const connectionFidelity = /* @__PURE__ */ new Set([...lifecycleFidelity, "inferred"]);
89
+ if (!lifecycleFidelity.has(fidelity.interruption))
90
+ throw new TypeError(
91
+ "source interruption fidelity contains an unsupported value"
92
+ );
93
+ if (!lifecycleFidelity.has(fidelity.retries))
94
+ throw new TypeError("source retry fidelity contains an unsupported value");
95
+ if (!connectionFidelity.has(fidelity.connection))
96
+ throw new TypeError(
97
+ "source connection fidelity contains an unsupported value"
98
+ );
99
+ const optionalEvents = fidelity.optionalEvents;
100
+ const validOptionalEvents = /* @__PURE__ */ new Set([
101
+ "tool.progress",
102
+ "tool.failed",
103
+ "citation.available",
104
+ "interaction.requested"
105
+ ]);
106
+ if (optionalEvents !== void 0 && (!Array.isArray(optionalEvents) || optionalEvents.some((event) => !validOptionalEvents.has(event))))
107
+ throw new TypeError("source optional events contain an unsupported value");
108
+ return Object.freeze({
109
+ adapter: source.adapter.trim(),
110
+ evidence: Object.freeze(source.evidence.map((item) => item.trim())),
111
+ fidelity: Object.freeze({
112
+ interruption: fidelity.interruption,
113
+ retries: fidelity.retries,
114
+ connection: fidelity.connection,
115
+ ...optionalEvents ? { optionalEvents: Object.freeze([...optionalEvents]) } : {}
116
+ })
117
+ });
118
+ }
119
+ function asDeliveryRecord(input, captureSequence, runtimeSourceId) {
120
+ const { result } = input;
121
+ return Object.freeze({
122
+ runtimeId: input.runtimeId,
123
+ ...runtimeSourceId ? { runtimeSourceId } : {},
124
+ captureSequence,
125
+ at: result.at,
126
+ kind: "dom-delivery",
127
+ sourceType: result.sourceType,
128
+ announcementId: result.announcementId,
129
+ channel: result.channel,
130
+ deliveryStatus: result.status,
131
+ deliveryMethod: result.method,
132
+ ...result.sourceEventId ? { sourceEventId: result.sourceEventId } : {},
133
+ ...result.responseId ? { responseId: result.responseId } : {},
134
+ ...result.toolId ? { toolId: result.toolId } : {},
135
+ ...result.interactionId ? { interactionId: result.interactionId } : {},
136
+ ...result.error?.name ? { errorName: result.error.name } : {}
137
+ });
138
+ }
139
+ function copyRuntimeSnapshot(source) {
140
+ return Object.freeze({
141
+ schemaVersion: 1,
142
+ at: source.at,
143
+ policy: Object.freeze({
144
+ ...source.policy,
145
+ text: Object.freeze({ ...source.policy.text }),
146
+ tools: Object.freeze({ ...source.policy.tools })
147
+ }),
148
+ pending: Object.freeze({
149
+ announcements: Object.freeze(
150
+ source.pending.announcements.map((item) => Object.freeze({ ...item }))
151
+ ),
152
+ flushes: Object.freeze(
153
+ source.pending.flushes.map((item) => Object.freeze({ ...item }))
154
+ )
155
+ }),
156
+ responses: Object.freeze(
157
+ source.responses.map((item) => Object.freeze({ ...item }))
158
+ ),
159
+ tools: Object.freeze(
160
+ source.tools.map((item) => Object.freeze({ ...item }))
161
+ ),
162
+ pendingCount: source.pendingCount
163
+ });
164
+ }
165
+ function createDevtoolsStore(options = {}) {
166
+ const maxEntries = options.maxEntries ?? 250;
167
+ if (!Number.isSafeInteger(maxEntries) || maxEntries <= 0)
168
+ throw new RangeError("maxEntries must be a positive safe integer");
169
+ const records = [];
170
+ const runtimes = /* @__PURE__ */ new Map();
171
+ const runtimeSources = /* @__PURE__ */ new Map();
172
+ const sourceRevisions = /* @__PURE__ */ new Map();
173
+ const runtimeSnapshots = /* @__PURE__ */ new Map();
174
+ const listeners = /* @__PURE__ */ new Set();
175
+ let paused = false;
176
+ let droppedCount = 0;
177
+ let nextCaptureSequence = 0;
178
+ let disposed = false;
179
+ let cachedSnapshot;
180
+ const notify = () => {
181
+ cachedSnapshot = void 0;
182
+ for (const listener of [...listeners]) {
183
+ try {
184
+ listener();
185
+ } catch {
186
+ }
187
+ }
188
+ };
189
+ const snapshot = () => {
190
+ if (cachedSnapshot) return cachedSnapshot;
191
+ const snapshots = Object.freeze(
192
+ Object.fromEntries(
193
+ [...runtimeSnapshots.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([id, value]) => [id, value])
194
+ )
195
+ );
196
+ const sources = Object.freeze(
197
+ Object.fromEntries(
198
+ [...runtimeSources.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([id, source]) => [id, source])
199
+ )
200
+ );
201
+ cachedSnapshot = Object.freeze({
202
+ paused,
203
+ droppedCount,
204
+ records: Object.freeze([...records]),
205
+ runtimeIds: Object.freeze([...runtimes.keys()].sort()),
206
+ runtimeSnapshots: snapshots,
207
+ runtimeSources: sources
208
+ });
209
+ return cachedSnapshot;
210
+ };
211
+ const refreshRuntimeSnapshot = (runtimeId, runtime) => {
212
+ try {
213
+ runtimeSnapshots.set(
214
+ runtimeId,
215
+ copyRuntimeSnapshot(runtime.getDiagnosticSnapshot())
216
+ );
217
+ } catch {
218
+ }
219
+ };
220
+ const refreshAllSnapshots = () => {
221
+ for (const [runtimeId, attachment] of runtimes)
222
+ refreshRuntimeSnapshot(runtimeId, attachment.runtime);
223
+ };
224
+ const appendRecord = (record) => {
225
+ if (records.length === maxEntries) {
226
+ records.shift();
227
+ droppedCount += 1;
228
+ }
229
+ records.push(record);
230
+ };
231
+ const pruneRuntimeSources = () => {
232
+ const retained = new Set(
233
+ records.flatMap(
234
+ (record) => record.runtimeSourceId ? [record.runtimeSourceId] : []
235
+ )
236
+ );
237
+ for (const attachment of runtimes.values())
238
+ if (attachment.sourceId) retained.add(attachment.sourceId);
239
+ for (const sourceId of runtimeSources.keys())
240
+ if (!retained.has(sourceId)) runtimeSources.delete(sourceId);
241
+ };
242
+ const capture = (runtimeId, runtime, runtimeSourceId, event) => {
243
+ if (paused || disposed) return;
244
+ appendRecord(
245
+ asRecord(runtimeId, event, nextCaptureSequence++, runtimeSourceId)
246
+ );
247
+ pruneRuntimeSources();
248
+ refreshRuntimeSnapshot(runtimeId, runtime);
249
+ notify();
250
+ };
251
+ return {
252
+ attachRuntime({ id, runtime, source }) {
253
+ if (disposed)
254
+ throw new Error("Cannot attach to a disposed devtools store");
255
+ if (!id.trim()) throw new TypeError("runtime id must be non-empty");
256
+ const copiedSource = copyRuntimeSource(source);
257
+ let revision = (sourceRevisions.get(id) ?? 0) + 1;
258
+ let sourceId = copiedSource ? revision === 1 ? id : `${id}#${revision}` : void 0;
259
+ while (sourceId && runtimeSources.has(sourceId)) {
260
+ revision += 1;
261
+ sourceId = `${id}#${revision}`;
262
+ }
263
+ let active = false;
264
+ const unsubscribe = runtime.subscribeDiagnosticEvents((event) => {
265
+ if (active) capture(id, runtime, sourceId, event);
266
+ });
267
+ const previous = runtimes.get(id);
268
+ const attachment = {
269
+ runtime,
270
+ unsubscribe,
271
+ source: copiedSource,
272
+ sourceId
273
+ };
274
+ active = true;
275
+ previous?.unsubscribe();
276
+ runtimes.set(id, attachment);
277
+ if (copiedSource && sourceId) {
278
+ runtimeSources.set(sourceId, copiedSource);
279
+ sourceRevisions.set(id, revision);
280
+ }
281
+ refreshRuntimeSnapshot(id, runtime);
282
+ pruneRuntimeSources();
283
+ notify();
284
+ let attached = true;
285
+ return () => {
286
+ if (!attached) return;
287
+ attached = false;
288
+ unsubscribe();
289
+ if (runtimes.get(id) === attachment) {
290
+ runtimes.delete(id);
291
+ runtimeSnapshots.delete(id);
292
+ }
293
+ pruneRuntimeSources();
294
+ notify();
295
+ };
296
+ },
297
+ getSnapshot: snapshot,
298
+ subscribe(listener) {
299
+ if (disposed)
300
+ throw new Error("Cannot subscribe to a disposed devtools store");
301
+ listeners.add(listener);
302
+ return () => listeners.delete(listener);
303
+ },
304
+ pauseCapture() {
305
+ if (disposed || paused) return;
306
+ paused = true;
307
+ notify();
308
+ },
309
+ resumeCapture() {
310
+ if (disposed || !paused) return;
311
+ paused = false;
312
+ notify();
313
+ },
314
+ refreshSnapshots() {
315
+ if (disposed) return;
316
+ refreshAllSnapshots();
317
+ notify();
318
+ },
319
+ recordDelivery(input) {
320
+ if (!input.runtimeId.trim())
321
+ throw new TypeError("delivery runtimeId must be non-empty");
322
+ if (!Number.isFinite(input.result.at))
323
+ throw new TypeError("delivery at must be finite");
324
+ if (paused || disposed) return;
325
+ appendRecord(
326
+ asDeliveryRecord(
327
+ input,
328
+ nextCaptureSequence++,
329
+ runtimes.get(input.runtimeId)?.sourceId
330
+ )
331
+ );
332
+ pruneRuntimeSources();
333
+ notify();
334
+ },
335
+ clear() {
336
+ if (disposed) return;
337
+ records.length = 0;
338
+ droppedCount = 0;
339
+ pruneRuntimeSources();
340
+ notify();
341
+ },
342
+ exportTrace() {
343
+ refreshAllSnapshots();
344
+ notify();
345
+ const current = snapshot();
346
+ return Object.freeze({
347
+ schemaVersion: 1,
348
+ kind: "generative-a11y/devtools-trace",
349
+ paused: current.paused,
350
+ droppedCount: current.droppedCount,
351
+ records: current.records,
352
+ runtimeSnapshots: current.runtimeSnapshots,
353
+ runtimeSources: current.runtimeSources
354
+ });
355
+ },
356
+ dispose() {
357
+ if (disposed) return;
358
+ disposed = true;
359
+ for (const { unsubscribe } of runtimes.values()) unsubscribe();
360
+ runtimes.clear();
361
+ runtimeSnapshots.clear();
362
+ runtimeSources.clear();
363
+ sourceRevisions.clear();
364
+ records.length = 0;
365
+ notify();
366
+ listeners.clear();
367
+ }
368
+ };
369
+ }
370
+ // Annotate the CommonJS export names for ESM import in node:
371
+ 0 && (module.exports = {
372
+ createDevtoolsStore
373
+ });
@@ -0,0 +1,106 @@
1
+ import { GenerativeA11yRuntime, AdapterFidelity, RuntimeDiagnosticEventV1, RuntimeDiagnosticSnapshotV1 } from '@generative-a11y/core';
2
+
3
+ type DevtoolsRecordKind = RuntimeDiagnosticEventV1["kind"] | "dom-delivery";
4
+ interface DevtoolsRecord {
5
+ readonly runtimeId: string;
6
+ /** Opaque key for the immutable adapter evidence captured with this record. */
7
+ readonly runtimeSourceId?: string;
8
+ readonly sequence?: number;
9
+ readonly captureSequence: number;
10
+ readonly at: number;
11
+ readonly kind: DevtoolsRecordKind;
12
+ readonly sourceType?: string;
13
+ readonly sourceEventId?: string;
14
+ readonly disposition?: string;
15
+ readonly reason?: string;
16
+ readonly announcementId?: string;
17
+ readonly responseId?: string;
18
+ readonly responseInstanceId?: string;
19
+ readonly nextResponseInstanceId?: string;
20
+ readonly attempt?: number;
21
+ readonly toolId?: string;
22
+ readonly toolInstanceId?: string;
23
+ readonly interactionId?: string;
24
+ readonly approvalId?: string;
25
+ readonly progress?: number;
26
+ readonly outcome?: string;
27
+ readonly count?: number;
28
+ readonly scheduledAt?: number;
29
+ readonly dueAt?: number;
30
+ readonly delayMs?: number;
31
+ readonly queueSequence?: number;
32
+ readonly channel?: "polite" | "assertive";
33
+ readonly deliveryStatus?: "notified" | "mutated" | "unavailable" | "disposed";
34
+ readonly deliveryMethod?: "aria-notify" | "live-region" | "none";
35
+ readonly errorName?: string;
36
+ }
37
+ /**
38
+ * Explicit, serializable evidence declared by an integration. Devtools never
39
+ * detects framework state or infers fidelity on its own.
40
+ */
41
+ interface DevtoolsRuntimeSource {
42
+ readonly adapter: string;
43
+ readonly evidence: readonly string[];
44
+ readonly fidelity: Readonly<Omit<AdapterFidelity, "optionalEvents"> & {
45
+ readonly optionalEvents?: readonly NonNullable<AdapterFidelity["optionalEvents"]>[number][];
46
+ }>;
47
+ }
48
+ interface DeliveryRecordInput {
49
+ readonly runtimeId: string;
50
+ readonly result: {
51
+ readonly status: "notified" | "mutated" | "unavailable" | "disposed";
52
+ readonly method: "aria-notify" | "live-region" | "none";
53
+ readonly channel: "polite" | "assertive";
54
+ readonly announcementId: string;
55
+ readonly sourceType: string;
56
+ readonly at: number;
57
+ readonly sourceEventId?: string;
58
+ readonly responseId?: string;
59
+ readonly toolId?: string;
60
+ readonly interactionId?: string;
61
+ readonly error?: {
62
+ readonly name: string;
63
+ readonly message?: string;
64
+ };
65
+ };
66
+ }
67
+ interface DevtoolsSnapshot {
68
+ readonly paused: boolean;
69
+ readonly droppedCount: number;
70
+ readonly records: readonly DevtoolsRecord[];
71
+ readonly runtimeIds: readonly string[];
72
+ readonly runtimeSnapshots: Readonly<Record<string, RuntimeDiagnosticSnapshotV1>>;
73
+ readonly runtimeSources: Readonly<Record<string, DevtoolsRuntimeSource>>;
74
+ }
75
+ interface DevtoolsTraceExportV1 {
76
+ readonly schemaVersion: 1;
77
+ readonly kind: "generative-a11y/devtools-trace";
78
+ readonly paused: boolean;
79
+ readonly droppedCount: number;
80
+ readonly records: readonly DevtoolsRecord[];
81
+ readonly runtimeSnapshots: Readonly<Record<string, RuntimeDiagnosticSnapshotV1>>;
82
+ readonly runtimeSources: Readonly<Record<string, DevtoolsRuntimeSource>>;
83
+ }
84
+ interface DevtoolsStoreOptions {
85
+ readonly maxEntries?: number;
86
+ }
87
+ interface AttachRuntimeOptions {
88
+ readonly id: string;
89
+ readonly runtime: Pick<GenerativeA11yRuntime, "subscribeDiagnosticEvents" | "getDiagnosticSnapshot">;
90
+ readonly source?: DevtoolsRuntimeSource;
91
+ }
92
+ interface DevtoolsStore {
93
+ attachRuntime(options: AttachRuntimeOptions): () => void;
94
+ getSnapshot(): DevtoolsSnapshot;
95
+ subscribe(listener: () => void): () => void;
96
+ pauseCapture(): void;
97
+ resumeCapture(): void;
98
+ refreshSnapshots(): void;
99
+ recordDelivery(input: DeliveryRecordInput): void;
100
+ clear(): void;
101
+ exportTrace(): DevtoolsTraceExportV1;
102
+ dispose(): void;
103
+ }
104
+ declare function createDevtoolsStore(options?: DevtoolsStoreOptions): DevtoolsStore;
105
+
106
+ export { type AttachRuntimeOptions, type DeliveryRecordInput, type DevtoolsRecord, type DevtoolsRecordKind, type DevtoolsRuntimeSource, type DevtoolsSnapshot, type DevtoolsStore, type DevtoolsStoreOptions, type DevtoolsTraceExportV1, createDevtoolsStore };