gesso-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.
@@ -0,0 +1,1076 @@
1
+ import { ActionCause, ActionEntry, ChannelErrorEntry, ChannelPort, CommandEntry, DevtoolsEvent, DevtoolsRequest, FrameEntry, FrameMetrics, PatchEntry, RuntimeErrorSource, ShellToRuntimeMessage, UiFramePhase, UiNodeReport, UiTreeNode, WorkerHandle } from "gesso-framework";
2
+ //#region src/sourceMap.d.ts
3
+ /**
4
+ * Enough of Source Map v3 to turn a compiled stack frame back into the
5
+ * line somebody wrote.
6
+ *
7
+ * Written here rather than taken from npm for the reason the rest of
8
+ * the repository's tooling gives (`scripts/gen-layout-fixtures.ts`,
9
+ * `scripts/check-webgpu-parity.ts`): the whole of what this package
10
+ * needs is a VLQ decoder and a binary search, and a dependency that
11
+ * ships a `SourceMapConsumer` with a WASM payload is a heavier thing to
12
+ * put in front of a developer trying to read an error than the error
13
+ * was.
14
+ *
15
+ * Two deliberate omissions. Index maps (`sections`) are not read — no
16
+ * bundler this project builds with emits one. Names are not read
17
+ * either: a stack frame already carries the function name the engine
18
+ * knew, and the mapped name is only occasionally better.
19
+ */
20
+ /** A source map as a bundler writes it. */
21
+ interface SourceMapV3 {
22
+ version: number;
23
+ file?: string;
24
+ sourceRoot?: string;
25
+ sources: (string | null)[];
26
+ sourcesContent?: (string | null)[];
27
+ names?: string[];
28
+ mappings: string;
29
+ }
30
+ /** Where a generated position came from, with 1-based line and column. */
31
+ interface OriginalPosition {
32
+ /** The source as the map names it, with `sourceRoot` already applied. */
33
+ source: string;
34
+ line: number;
35
+ column: number;
36
+ /**
37
+ * The original text of the source, when the map inlined it.
38
+ *
39
+ * Carried on the position rather than fetched from the consumer
40
+ * afterwards because a chained lookup ends in a consumer the caller
41
+ * never sees: the map that knows the text is the last one in the
42
+ * chain, not the one the caller started from. See
43
+ * `SourceMapStore.originalFor`.
44
+ */
45
+ content?: string | null;
46
+ }
47
+ /**
48
+ * One decoded mapping segment.
49
+ *
50
+ * Held as a flat tuple rather than an object because a map for a
51
+ * medium application has hundreds of thousands of them, and they exist
52
+ * only to be searched.
53
+ */
54
+ type Segment = [generatedColumn: number, sourceIndex: number, sourceLine: number, sourceColumn: number];
55
+ /**
56
+ * Decodes the `mappings` string into one array of segments per
57
+ * generated line.
58
+ *
59
+ * Segments carrying only a generated column — a run of output with no
60
+ * original position, which is what a bundler emits for code it
61
+ * synthesised — are dropped rather than kept with nulls. A lookup that
62
+ * lands in one should fall back to the nearest earlier real mapping,
63
+ * and dropping them is how that happens without a second case.
64
+ */
65
+ declare function decodeMappings(mappings: string): Segment[][];
66
+ /**
67
+ * A decoded map, answering "which line of which source is this?".
68
+ *
69
+ * Decoding is done once in the constructor because a page that shows
70
+ * one error usually shows several from the same file, and each of them
71
+ * asks about a handful of positions.
72
+ */
73
+ declare class SourceMapConsumer {
74
+ private readonly lines;
75
+ private readonly sources;
76
+ private readonly contents;
77
+ constructor(map: SourceMapV3);
78
+ /**
79
+ * Maps a generated position — 1-based line and column, as every
80
+ * engine writes them in a stack — back to an original one.
81
+ *
82
+ * Returns the last mapping at or before the column, which is the
83
+ * definition of a source map's coverage: a mapping holds until the
84
+ * next one starts. Null when the line has no mappings at all.
85
+ */
86
+ lookup(line: number, column: number): OriginalPosition | null;
87
+ /** The original text of a source, when the map inlined it. */
88
+ contentFor(source: string): string | null;
89
+ }
90
+ /**
91
+ * The `sourceMappingURL` a script declares, or null.
92
+ *
93
+ * The last one wins, and the search is a `lastIndexOf` over the whole
94
+ * text rather than a regex over the tail: an inlined map is a single
95
+ * comment megabytes long, so "near the end" is not where its opening
96
+ * is.
97
+ */
98
+ declare function parseSourceMappingUrl(script: string): string | null;
99
+ /**
100
+ * Fetches and caches the map for each script a stack mentions.
101
+ *
102
+ * Every method resolves rather than rejects: this runs while
103
+ * something has already gone wrong, and an overlay that throws while
104
+ * explaining a throw is worse than an overlay showing a raw stack.
105
+ * A script with no map, a map that 404s and a map that is not JSON all
106
+ * come back as null, and the caller shows what the engine gave it.
107
+ */
108
+ declare class SourceMapStore {
109
+ private readonly cache;
110
+ private readonly load;
111
+ constructor(load?: (url: string) => Promise<string>);
112
+ /** The consumer for a script URL, fetched at most once per store. */
113
+ consumerFor(scriptUrl: string): Promise<SourceMapConsumer | null>;
114
+ /**
115
+ * Where a position in a script was written, following the chain of
116
+ * maps as far as it goes.
117
+ *
118
+ * One lookup is not enough, and the reason took a browser to find. A
119
+ * frame inside `gesso-framework` names Vite's optimised dependency
120
+ * bundle; that bundle's map points at the package's own
121
+ * `dist/index.js`, because the optimiser does not chain to the map
122
+ * the package ships; and it is the package's map that knows about
123
+ * `src/app/worker/RenderWorkerApp.ts`. Stopping after one step gave
124
+ * a frame in a bundled file with a five-figure line number, which is
125
+ * exactly the thing shipping the maps was meant to prevent.
126
+ *
127
+ * So each answer is resolved against the map that gave it and asked
128
+ * again, until a source has no map of its own — which is the source
129
+ * somebody wrote. `depth` is a guard against a map that names
130
+ * itself; four is more levels than any real toolchain stacks.
131
+ */
132
+ originalFor(scriptUrl: string, line: number, column: number, depth?: number): Promise<OriginalPosition | null>;
133
+ private resolve;
134
+ }
135
+ //#endregion
136
+ //#region src/ErrorOverlay.d.ts
137
+ /**
138
+ * Where an error came from.
139
+ *
140
+ * The render worker's four sources, plus `window` for the thread the
141
+ * overlay itself runs on — the single-thread configuration, and
142
+ * anything the shell does around the app.
143
+ */
144
+ type ErrorOrigin = RuntimeErrorSource | 'window';
145
+ interface ErrorOverlayOptions {
146
+ /**
147
+ * Also write every error to the console (default true).
148
+ *
149
+ * On by default because the overlay is a second place to see an
150
+ * error, not a replacement for the first: the console keeps the live
151
+ * object, its `cause`, and the "expand to see the real frames" that
152
+ * no snapshot of a stack can offer.
153
+ */
154
+ echoToConsole?: boolean;
155
+ /** Where source maps are fetched from. Injected by the specs. */
156
+ sourceMaps?: SourceMapStore;
157
+ }
158
+ /**
159
+ * The error overlay: what a worker threw, drawn over the app that was
160
+ * running when it threw.
161
+ *
162
+ * A canvas UI has no equivalent of a page that stops rendering. When a
163
+ * render worker throws, the last good frame stays on screen — pixels
164
+ * that look exactly like a working application — and the only witness
165
+ * is a console message on a thread the developer has to know to
166
+ * select. `WorkerApp` already forwards those errors to `onError`;
167
+ * this is that callback, with the stack put back through the source
168
+ * maps and the offending line quoted.
169
+ *
170
+ * It is a development tool and it makes a development tool's trade:
171
+ * it fetches source maps, keeps every distinct error of the session,
172
+ * and covers the application it is reporting on.
173
+ */
174
+ declare class ErrorOverlay {
175
+ private readonly host;
176
+ /**
177
+ * The document the host belongs to, rather than the global one.
178
+ *
179
+ * The same rule `SemanticsMirror` follows: everything this class
180
+ * builds hangs off the element it was handed, so it works in a
181
+ * second window and can be driven by a fake document in a spec —
182
+ * which is the only way to test it in a suite that runs in Node.
183
+ */
184
+ private readonly doc;
185
+ private readonly view;
186
+ private readonly container;
187
+ private readonly root;
188
+ private readonly panel;
189
+ private readonly maps;
190
+ private readonly echo;
191
+ private readonly entries;
192
+ private readonly restoreHostPosition;
193
+ private shown;
194
+ private disposed;
195
+ private detachWindow;
196
+ constructor(host: HTMLElement, options?: ErrorOverlayOptions);
197
+ /** How many distinct errors have been reported. */
198
+ get count(): number;
199
+ /** True while the overlay is covering the app. */
200
+ get visible(): boolean;
201
+ /**
202
+ * Reports an error, in the shape `WorkerApp`'s `onError` hands it
203
+ * over, so the whole wiring is `onError: overlay.report`.
204
+ *
205
+ * Bound as a field rather than a method for exactly that: it is
206
+ * passed as a callback far more often than it is called.
207
+ */
208
+ report: (message: string, stack?: string, origin?: ErrorOrigin) => void;
209
+ /** Reports a thrown value, which is usually but not always an Error. */
210
+ reportError: (error: unknown, origin?: ErrorOrigin) => void;
211
+ /**
212
+ * Catches what this thread throws, too.
213
+ *
214
+ * The single-thread configuration runs components here, and even in
215
+ * the worker configuration the shell around the app can throw. Returns
216
+ * a function that stops listening; `dispose` calls it as well.
217
+ */
218
+ captureWindowErrors(target?: Window | null): () => void;
219
+ /** Hides the overlay. The errors are kept and can be shown again. */
220
+ hide(): void;
221
+ /** Hides the overlay and forgets every error it was holding. */
222
+ clear(): void;
223
+ dispose(): void;
224
+ private show;
225
+ private handleKeyDown;
226
+ /**
227
+ * Maps the entry's stack and quotes the line it points at.
228
+ *
229
+ * Deliberately after the first paint: the raw stack is on screen
230
+ * within a frame of the error, and the mapped one replaces it when
231
+ * the network answers. A developer looking at an error should never
232
+ * be waiting on a fetch to see it.
233
+ */
234
+ private resolveSources;
235
+ /** `element`, bound to this overlay's document. */
236
+ private el;
237
+ /** A header button, bound to this overlay's document. */
238
+ private button;
239
+ private render;
240
+ private step;
241
+ private copy;
242
+ }
243
+ /**
244
+ * Mounts an error overlay over an application's host element.
245
+ *
246
+ * const overlay = mountErrorOverlay(host);
247
+ * createApp({ renderWorker, onError: overlay.report });
248
+ * overlay.captureWindowErrors();
249
+ */
250
+ declare function mountErrorOverlay(host: HTMLElement, options?: ErrorOverlayOptions): ErrorOverlay;
251
+ //#endregion
252
+ //#region src/ActionLog.d.ts
253
+ /**
254
+ * The store action log: every command a view sent
255
+ * across the barrier, every patch that came back, on one timeline,
256
+ * with the view rewindable to any point on it.
257
+ *
258
+ * The seam is the port. A channel is two message types over a
259
+ * `ChannelPort`, so a recorder that sits in the middle of one sees
260
+ * both directions at their only crossing, needs nothing from the
261
+ * framework beyond the two type guards the protocol already exports,
262
+ * and can put a patch back on the wire, which is what makes time
263
+ * travel a replay rather than a second write path into the replica.
264
+ *
265
+ * Nothing here touches the DOM. That is deliberate: the tap belongs
266
+ * wherever the ports already are, which in the worker configuration is
267
+ * the render worker, and a recorder that imported a document could not
268
+ * go there. `ActionLogPanel` is the half that draws.
269
+ *
270
+ * **What time travel does.** It rewrites what the *view* holds. The
271
+ * authoritative state lives on the other thread and is not rewound,
272
+ * cannot be rewound from here, and does not know this happened. While
273
+ * the log is pinned to a step, patches still arriving are recorded and
274
+ * held rather than delivered, so the view stays where it was put;
275
+ * going live delivers the state the application actually reached. A
276
+ * command sent from the view while pinned is forwarded like any other,
277
+ * because the application is still running and pretending otherwise
278
+ * would be a lie about a button that visibly did something.
279
+ */
280
+ interface ActionLog {
281
+ /**
282
+ * Wraps a worker handle so every channel opened on it is recorded.
283
+ *
284
+ * `tokens` is read for one thing: the value both ends of a channel
285
+ * start from. The first patch a provider sends is a diff against the
286
+ * token's initial, so a recorder that started from nothing would
287
+ * reconstruct an early step out of a patch whose base it never had.
288
+ */
289
+ tap(handle: WorkerHandle, tokens: readonly ActionLogToken[]): WorkerHandle;
290
+ /**
291
+ * Wraps one port, for a channel fed from this thread.
292
+ *
293
+ * `createChannelRegistry` makes the pair for a `source` registration
294
+ * itself and hands out neither end, so a local channel is tapped by
295
+ * providing it by hand instead: call `provide(token, source, port)`
296
+ * on one end of a `MessageChannel` and register the other through
297
+ * this.
298
+ */
299
+ tapPort(port: ChannelPort, token: ActionLogToken): ChannelPort;
300
+ /**
301
+ * Names what is being answered for as long as the returned function
302
+ * has not been called, so everything recorded meanwhile carries the
303
+ * same `ActionCause`.
304
+ *
305
+ * Called around the dispatch of one input, which is the only moment
306
+ * where a cause is known rather than guessed: the command a click's
307
+ * listener sends is sent synchronously inside it. Nothing is
308
+ * allocated for an input that records nothing, so wrapping every
309
+ * pointer move costs a function call and a null check.
310
+ *
311
+ * The patches that answer such a command are given the same cause,
312
+ * which is an inference and the record says so: the barrier carries
313
+ * no request id, so the recorder ties the next patch batch on that
314
+ * channel to the command that preceded it, until a frame has drawn
315
+ * one or another command replaces it.
316
+ */
317
+ cause(label: string): () => void;
318
+ /**
319
+ * Records the frame that drew whatever has been recorded since the
320
+ * last one, closing the chain from click to command to patches to
321
+ * pixels.
322
+ *
323
+ * Nothing is recorded for a frame with nothing to close, so an
324
+ * application drawing sixty frames a second while its channels are
325
+ * quiet adds nothing to the timeline.
326
+ */
327
+ frame(id: number): void;
328
+ /** The timeline, oldest first. */
329
+ readonly entries: readonly ActionEntry[];
330
+ /** The channels this log is tapping, by name, in the order tapped. */
331
+ readonly channels: readonly string[];
332
+ /**
333
+ * The entry the view is pinned to, or null when the view is live.
334
+ *
335
+ * A sequence number rather than an index, because entries fall off
336
+ * the front of a bounded log and an index would then mean a
337
+ * different entry than it did a moment ago.
338
+ */
339
+ readonly pinnedTo: number | null;
340
+ /**
341
+ * Rewinds every tapped channel's view to the state it held once
342
+ * `seq` had been applied, or catches it up to the application when
343
+ * `seq` is null.
344
+ *
345
+ * Each key is posted to the replica as one `{ op: 'set', path: [] }`,
346
+ * which is the shape a reattaching client is already answered with,
347
+ * so nothing downstream can tell a replay from a resync.
348
+ */
349
+ jumpTo(seq: number | null): void;
350
+ /** Empties the timeline and goes live. Tapped channels stay tapped. */
351
+ clear(): void;
352
+ /** Called whenever the timeline or the pinned step changed. */
353
+ subscribe(listener: () => void): () => void;
354
+ /**
355
+ * Stops recording, leaving every tapped channel working.
356
+ *
357
+ * The relay is not removed. A port handed to a replica cannot be
358
+ * taken back out of the middle of it, so what this does is stop
359
+ * listening: messages pass through, nothing is written down, and a
360
+ * held patch could not be stranded by it.
361
+ */
362
+ dispose(): void;
363
+ }
364
+ /**
365
+ * A channel's identity, structurally.
366
+ *
367
+ * The same erasure `ChannelRegistration` uses, and for the same
368
+ * reason: a list of channels has no single generic instantiation, and
369
+ * the only two things a recorder wants from a token are its name and
370
+ * the value both ends start from.
371
+ */
372
+ interface ActionLogToken {
373
+ readonly name: string;
374
+ readonly initial: object;
375
+ }
376
+ interface ActionLogOptions {
377
+ /**
378
+ * Most entries kept. Default 200.
379
+ *
380
+ * A dropped entry is not forgotten, only unaddressable: its patches
381
+ * are folded into the base state each channel is reconstructed from,
382
+ * so a jump to a step that survived is still exact.
383
+ */
384
+ readonly limit?: number;
385
+ }
386
+ declare function createActionLog(options?: ActionLogOptions): ActionLog;
387
+ /**
388
+ * Sends each new entry as it is recorded, and returns a function that
389
+ * stops.
390
+ *
391
+ * `subscribe` says only that something changed, so the sequence number
392
+ * is what tells a new entry from the ones already sent; a `clear`
393
+ * resets it, and a bounded log retiring old entries does not. Both
394
+ * routes to a panel need exactly this: the hook, for a log in the
395
+ * page, and the render worker tap, for one on the other side of a
396
+ * thread.
397
+ */
398
+ declare function forwardNewEntries(log: ActionLog, send: (entry: ActionEntry) => void): () => void;
399
+ //#endregion
400
+ //#region src/ActionLogPanel.d.ts
401
+ /**
402
+ * The action log's panel: the timeline, and a click on any step to put
403
+ * the view back where it was at that step.
404
+ *
405
+ * DOM in a shadow root over the canvas, for the reasons the error
406
+ * overlay and the node inspector both give: the application owns the
407
+ * canvas, and a panel drawn into the scene would be part of the scene
408
+ * it is describing.
409
+ *
410
+ * It differs from the node inspector in one way that matters. The
411
+ * inspector takes no pointer events because the person is hovering the
412
+ * thing it describes; this panel is operated, so it takes them over
413
+ * itself and nowhere else. That is also why it is behind a toggle
414
+ * rather than always up: while it is showing, the canvas underneath it
415
+ * is not reachable.
416
+ */
417
+ interface ActionLogPanel {
418
+ /** Shows or hides the panel. A hidden panel stops rendering. */
419
+ setVisible(visible: boolean): void;
420
+ readonly visible: boolean;
421
+ dispose(): void;
422
+ }
423
+ interface ActionLogPanelOptions {
424
+ /** Which corner it floats in. Default `'bottom-right'`. */
425
+ readonly corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
426
+ }
427
+ declare function mountActionLogPanel(host: HTMLElement, log: ActionLog, options?: ActionLogPanelOptions): ActionLogPanel;
428
+ /**
429
+ * The direction, as one glyph: up for what the view sent, down for
430
+ * what came back, a square for the frame that drew it.
431
+ */
432
+ declare function actionGlyph(entry: ActionEntry): string;
433
+ /** One entry as a line: the command and its payload, the patched keys, the frame, or the error. */
434
+ declare function describeActionEntry(entry: ActionEntry): string;
435
+ //#endregion
436
+ //#region src/RenderWorkerTap.d.ts
437
+ /**
438
+ * The action log in the worker configuration, and the click at the
439
+ * head of every chain it records.
440
+ *
441
+ * The action log recorded the gap and the reason for it. A channel's
442
+ * ports are made where the replicas are, which in the worker
443
+ * configuration is the render worker; the shell holds neither end and
444
+ * never sees a patch, deliberately. So the recorder was written to run
445
+ * in a worker — it imports no DOM and its entries are plain data — and
446
+ * then nothing ran it there, because the two ways to wire it up were
447
+ * "one line in the render worker's entry" and "route every patch
448
+ * through the main thread", and the second would falsify the very
449
+ * thing the route exists to show.
450
+ *
451
+ * This is that one line, made general. It stands on the wire the way
452
+ * the recorder itself does, which is the rule `0047` set: **the tap
453
+ * goes where the messages already are, never somewhere new.** In a
454
+ * render worker three kinds of message go past one object, the
455
+ * worker's global:
456
+ *
457
+ * - the shell's port to the application worker, which arrives with
458
+ * `init` and is what a tapped channel has to be opened over;
459
+ * - every input the shell forwards, which is where a cause begins;
460
+ * - every frame the runtime reports, which is where one ends.
461
+ *
462
+ * Nothing here reaches into the runtime, and nothing in the framework
463
+ * knows it exists. Used after `renderRoot`, whose constructor installs
464
+ * the handler this wraps:
465
+ *
466
+ * const app = renderRoot(AppRoot).useService(Counter);
467
+ * const actions = createActionLog();
468
+ * const tap = tapRenderWorker(actions);
469
+ * const data = tap.applicationWorker([Catalog, Cart]);
470
+ * app.useChannel(Catalog, { worker: data }).useChannel(Cart, { worker: data });
471
+ *
472
+ * Guard it with `import.meta.env.DEV` or the equivalent: a tap in a
473
+ * production bundle is a recorder holding patch batches for a session
474
+ * nobody is watching.
475
+ */
476
+ /** The worker global, as far as the tap is concerned. */
477
+ interface RenderWorkerHost {
478
+ onmessage: ((event: {
479
+ data: unknown;
480
+ }) => void) | null;
481
+ postMessage(message: unknown, transfer?: Transferable[]): void;
482
+ }
483
+ interface RenderWorkerTapOptions {
484
+ /** Where to stand. Default: the worker's own global. */
485
+ readonly host?: RenderWorkerHost;
486
+ /**
487
+ * Whether entries are posted to the shell as devtools events, so a
488
+ * panel outside the page shows them. Default true.
489
+ *
490
+ * The route a log in the page does not need: there the devtools hook
491
+ * has the log itself to read.
492
+ */
493
+ readonly forward?: boolean;
494
+ }
495
+ interface RenderWorkerTap {
496
+ /**
497
+ * A handle on the application worker the shell supplied, with every
498
+ * channel opened over it recorded.
499
+ *
500
+ * Registrations run before `init` and the shell's port arrives with
501
+ * it, which is why this is a handle rather than a port: it is opened
502
+ * when the runtime starts, by which time the port is here. Passing
503
+ * it to `useChannel` is what replaces the `APPLICATION_WORKER`
504
+ * placeholder the render worker would otherwise swap in.
505
+ */
506
+ applicationWorker(tokens: readonly ActionLogToken[]): WorkerHandle;
507
+ dispose(): void;
508
+ }
509
+ declare function tapRenderWorker(log: ActionLog, options?: RenderWorkerTapOptions): RenderWorkerTap;
510
+ /**
511
+ * What to call the cause an input starts, or null for a message that
512
+ * is not an input.
513
+ *
514
+ * Position is in the label because two presses in different places are
515
+ * two different causes to the person who made them, and the ids alone
516
+ * do not say which was which.
517
+ */
518
+ declare function inputLabel(message: ShellToRuntimeMessage | undefined): string | null;
519
+ //#endregion
520
+ //#region src/NodePicker.d.ts
521
+ /**
522
+ * Click a node on the canvas to pin it in the panel
523
+ * (the first of the two things the node inspector deferred).
524
+ *
525
+ * Picking was left out because the panel could not name a node back
526
+ * to the runtime. The devtools panel built that half: `select` and
527
+ * `highlight` address a node by id, and the inspector already reports
528
+ * the node under the pointer while it is on. What was still missing is
529
+ * the click, and a click is the one part of this that cannot happen in
530
+ * the render thread. By the time the runtime has an event, the event
531
+ * has been dispatched; taking it back would mean asking the
532
+ * application to forget a press it may already have acted on.
533
+ *
534
+ * So the pick happens where the click arrives, in the capture phase,
535
+ * over the element the canvas is in. That is the shell doing what the
536
+ * shell does — forwarding input, or in this case declining to
537
+ * — and it costs nothing while picking is off,
538
+ * because the listeners are attached only then.
539
+ *
540
+ * What it pins is whatever the inspector last reported as hovered, so
541
+ * a picker needs the inspector on; the panel turns it on with the same
542
+ * toggle.
543
+ */
544
+ interface NodePickerOptions {
545
+ /**
546
+ * The element to take clicks over, in the capture phase. The one the
547
+ * application is mounted in, so the canvas is inside it.
548
+ */
549
+ readonly host: EventTarget;
550
+ /** The node under the pointer, from the runtime's `hover` reports. */
551
+ hovered(): string | null;
552
+ }
553
+ /** What a panel drives: arm it, and hear what was picked. */
554
+ interface DevtoolsPicker {
555
+ /** Whether clicking the canvas pins a node instead of reaching the application. */
556
+ setEnabled(enabled: boolean): void;
557
+ readonly enabled: boolean;
558
+ /** Called with the id of the node picked. Pass null to stop listening. */
559
+ onPick(listener: ((id: string) => void) | null): void;
560
+ dispose(): void;
561
+ }
562
+ declare function createNodePicker(options: NodePickerOptions): DevtoolsPicker;
563
+ //#endregion
564
+ //#region src/NodeReportView.d.ts
565
+ interface NodeReportViewOptions {
566
+ /**
567
+ * Makes the props editable, calling this with the new value when one
568
+ * is committed; `null` means "remove it", which puts an inherited
569
+ * value back.
570
+ *
571
+ * Absent for a read-only view. The corner inspector passes nothing,
572
+ * because it sets `pointer-events: none` and could not be typed into
573
+ * anyway.
574
+ */
575
+ onEditProp?(name: string, value: unknown): void;
576
+ }
577
+ /**
578
+ * A `UiNodeReport` as DOM: the node inspector's body, shared with the
579
+ * devtools panel so a node read in a corner of the canvas and a node
580
+ * picked from a tree are described in the same words.
581
+ */
582
+ declare function renderNodeReport(doc: Document, report: UiNodeReport, options?: NodeReportViewOptions): HTMLElement[];
583
+ /**
584
+ * The rules the report's elements use, for any stylesheet that shows one.
585
+ *
586
+ * Colours are the devtools panel's custom properties with the dark
587
+ * palette as fallback, so the corner inspector, which defines none of
588
+ * them, is unchanged, and the panel recolours the same report by
589
+ * defining them.
590
+ */
591
+ declare const NODE_REPORT_STYLES = "\nh1 { margin: 0 0 6px; font-size: 12px; color: var(--gd-accent, #79c0ff); overflow-wrap: anywhere; }\nh2 {\n margin: 10px 0 4px;\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--gd-muted, #8b949e);\n}\np { margin: 0 0 2px; }\n.label { color: var(--gd-muted, #8b949e); }\n.rows { display: grid; grid-template-columns: auto 1fr; gap: 0 8px; margin: 0; }\ndt { color: var(--gd-muted, #8b949e); overflow-wrap: anywhere; }\ndt.modifier { color: var(--gd-purple, #d2a8ff); }\ndt.binding { color: var(--gd-green, #7ee787); }\ndt.provided { color: var(--gd-orange, #ffa657); }\ndd { margin: 0; overflow-wrap: anywhere; }\ndd.editable { cursor: pointer; border-radius: 3px; }\ndd.editable:hover { background: var(--gd-bg-hover, #21262d); }\n.edit {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--gd-accent, #79c0ff);\n border-radius: 3px;\n padding: 0 3px;\n background: var(--gd-bg, #0d1117);\n color: var(--gd-text, #e6edf3);\n font: inherit;\n}\n.note { color: var(--gd-faint, #6e7681); }\n.stream { display: block; color: var(--gd-green, #7ee787); }\n.explanation { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--gd-text-strong, #c9d1d9); }\n";
592
+ //#endregion
593
+ //#region src/PanelProtocol.d.ts
594
+ /**
595
+ * What a devtools panel and the page it inspects say to each other
596
+ *.
597
+ *
598
+ * The framework's `DevtoolsRequest` and `DevtoolsEvent` are one
599
+ * application's vocabulary. A page may run several (a documentation
600
+ * site's examples), and a panel arrives after they started, so this
601
+ * layer adds the two things the framework's protocol does not have: an
602
+ * application id on every message, and a greeting that answers with
603
+ * the list.
604
+ *
605
+ * Both sides speak through a `DevtoolsPort`, which is only `post` and
606
+ * `onMessage`. Two ports are provided here: a pair joined in memory,
607
+ * for a panel mounted in the same page and for tests, and one over
608
+ * `window.postMessage`, which is how a browser extension's content
609
+ * script reaches a page. The extension's own hop, from its content
610
+ * script to its devtools page, is one more port of the same shape and
611
+ * lives with the extension.
612
+ */
613
+ interface DevtoolsAppInfo {
614
+ readonly id: string;
615
+ readonly name: string;
616
+ }
617
+ /** Page to panel. */
618
+ type PageMessage =
619
+ /** The applications the page has connected; sent on `hello` and whenever the list changes. */
620
+ {
621
+ type: 'apps';
622
+ apps: readonly DevtoolsAppInfo[];
623
+ } | {
624
+ type: 'event';
625
+ app: string;
626
+ event: DevtoolsEvent;
627
+ } |
628
+ /** A store action log entry, from an `ActionLog` the application connected alongside itself. */
629
+ {
630
+ type: 'action';
631
+ app: string;
632
+ entry: ActionEntry;
633
+ } |
634
+ /**
635
+ * A node the person clicked on the canvas while the panel was
636
+ * picking. The panel selects it; the click never reaches the
637
+ * application.
638
+ */
639
+ {
640
+ type: 'picked';
641
+ app: string;
642
+ id: string;
643
+ };
644
+ /** Panel to page. */
645
+ type PanelMessage =
646
+ /** "Is anyone there": answered with `apps`. */
647
+ {
648
+ type: 'hello';
649
+ } | {
650
+ type: 'request';
651
+ app: string;
652
+ request: DevtoolsRequest;
653
+ } |
654
+ /**
655
+ * Turns click-to-pick on or off.
656
+ *
657
+ * Not a `DevtoolsRequest`, because it is not a question for the
658
+ * runtime. Picking is a click that must be taken before the
659
+ * application sees it, and the only place a click can be taken is
660
+ * the page, where it arrives; the runtime is a thread away and would
661
+ * have to be asked to un-dispatch something. So the page answers
662
+ * this one, and the runtime is asked to `select` the node the page
663
+ * names, which is a question it already answers.
664
+ */
665
+ {
666
+ type: 'pick';
667
+ app: string;
668
+ enabled: boolean;
669
+ };
670
+ /** One end of a conversation: what it hears and what it says. */
671
+ interface DevtoolsPort<In, Out> {
672
+ post(message: Out): void;
673
+ /** Returns a function that stops listening. */
674
+ onMessage(listener: (message: In) => void): () => void;
675
+ /** Drops every listener and stops posting. */
676
+ close(): void;
677
+ }
678
+ /** The page's end. */
679
+ type PagePort = DevtoolsPort<PanelMessage, PageMessage>;
680
+ /** The panel's end. */
681
+ type PanelPort = DevtoolsPort<PageMessage, PanelMessage>;
682
+ /**
683
+ * Two ports joined in memory, delivering synchronously.
684
+ *
685
+ * For a panel mounted in the page it inspects, and for specs: the
686
+ * hook and the panel are exercised end to end with nothing between
687
+ * them but a function call.
688
+ */
689
+ declare function createDirectPorts(): {
690
+ page: PagePort;
691
+ panel: PanelPort;
692
+ };
693
+ /** What a message carries over `window.postMessage`, so both sides can ignore everything else on the window. */
694
+ interface Envelope<T> {
695
+ readonly source: typeof ENVELOPE_SOURCE;
696
+ /** Who it is for. A page ignores what it sent, and so does a panel. */
697
+ readonly to: 'page' | 'panel';
698
+ readonly message: T;
699
+ }
700
+ declare const ENVELOPE_SOURCE = "gesso-devtools";
701
+ declare function isEnvelope(value: unknown, to: 'page' | 'panel'): value is Envelope<unknown>;
702
+ /** The part of `Window` the transport uses, so a spec can hand it a plain object. */
703
+ interface WindowLike {
704
+ addEventListener(type: 'message', listener: (event: {
705
+ data: unknown;
706
+ source?: unknown;
707
+ }) => void): void;
708
+ removeEventListener(type: 'message', listener: (event: {
709
+ data: unknown;
710
+ source?: unknown;
711
+ }) => void): void;
712
+ postMessage(message: unknown, targetOrigin: string): void;
713
+ readonly location?: {
714
+ readonly origin: string;
715
+ };
716
+ }
717
+ /**
718
+ * The page's end of a conversation over its own window.
719
+ *
720
+ * A content script shares the page's window but not its JavaScript, so
721
+ * `window.postMessage` to the page's own origin is the one channel
722
+ * they have. Messages are posted to that origin rather than to `*`,
723
+ * and the page hears only envelopes addressed to it, so its own posts
724
+ * come straight back through the same listener and are dropped.
725
+ */
726
+ declare function windowPagePort(win: WindowLike): PagePort;
727
+ /** The other end, for whatever sits in the page beside it (an extension's content script). */
728
+ declare function windowPanelPort(win: WindowLike): PanelPort;
729
+ //#endregion
730
+ //#region src/DevtoolsHook.d.ts
731
+ /**
732
+ * The page's side of the devtools panel.
733
+ *
734
+ * One object per page, kept on the window under a well-known name the
735
+ * way React's devtools hook is, so that several bundles (a docs site's
736
+ * examples, an application and its dependency) connect to the same
737
+ * hook and a panel sees one list. Applications register with it;
738
+ * panels attach ports to it; the hook routes requests to the right
739
+ * application and fans every application's events out to every port.
740
+ *
741
+ * `connectDevtools(app)` is the one line an application adds. It also
742
+ * installs the `window.postMessage` transport the first time, which is
743
+ * how a browser extension's content script finds the page: nothing is
744
+ * running in a page that has not connected, and a page that has
745
+ * connected answers `hello`.
746
+ */
747
+ /** What an application must offer: both `WorkerApp` and `GessoApp` do. */
748
+ interface DevtoolsApp {
749
+ devtools(request: DevtoolsRequest): void;
750
+ /**
751
+ * Taken over by the hook while connected. An application that was
752
+ * listening to its own devtools events has to choose: the panel or
753
+ * itself. In practice the events only exist for panels.
754
+ */
755
+ onDevtools(listener: ((event: DevtoolsEvent) => void) | null): void;
756
+ }
757
+ interface ConnectDevtoolsOptions {
758
+ /** What the panel calls this application. Default: the document title, else `app`. */
759
+ readonly name?: string;
760
+ /**
761
+ * A store action log the panel should show alongside. The log stays
762
+ * where it is (it taps ports in the page); the panel is sent each
763
+ * entry as it is recorded.
764
+ */
765
+ readonly actions?: ActionLog;
766
+ /**
767
+ * Lets the panel pick a node by clicking the canvas.
768
+ *
769
+ * The page's half of picking, because a click has to be taken before
770
+ * the application sees it and only the page has it in time. See
771
+ * `createNodePicker`.
772
+ */
773
+ readonly picker?: DevtoolsPicker;
774
+ /**
775
+ * Where the hook lives and where the `postMessage` transport
776
+ * listens. Default: the global window. `null` keeps the hook off any
777
+ * window and installs no transport, for a panel mounted directly.
778
+ */
779
+ readonly window?: (WindowLike & HookHost) | null;
780
+ }
781
+ interface DevtoolsHook {
782
+ /** The connected applications, in the order they connected. */
783
+ readonly apps: readonly DevtoolsAppInfo[];
784
+ /** Connects an application. Returns a function that disconnects it. */
785
+ register(app: DevtoolsApp, options?: Omit<ConnectDevtoolsOptions, 'window'>): () => void;
786
+ /** Attaches a panel's port. Returns a function that detaches it. */
787
+ attach(port: PagePort): () => void;
788
+ }
789
+ /** The property the hook is kept under. */
790
+ declare const HOOK_PROPERTY = "__GESSO_DEVTOOLS__";
791
+ /** A window, as far as the hook is concerned: somewhere to keep itself. */
792
+ interface HookHost {
793
+ [HOOK_PROPERTY]?: DevtoolsHook;
794
+ }
795
+ /**
796
+ * The page's hook, created on first use.
797
+ *
798
+ * With a window, the hook is stored on it and the `postMessage`
799
+ * transport is attached once; without one (`null`), a fresh hook with
800
+ * no transport, for specs and for a panel in the same page.
801
+ */
802
+ declare function getDevtoolsHook(win?: (WindowLike & HookHost) | null): DevtoolsHook;
803
+ /**
804
+ * Connects an application to the page's devtools hook, so a panel can
805
+ * find it. Returns a function that disconnects it; call it when the
806
+ * application is disposed.
807
+ */
808
+ declare function connectDevtools(app: DevtoolsApp, options?: ConnectDevtoolsOptions): () => void;
809
+ //#endregion
810
+ //#region src/DevtoolsPanel.d.ts
811
+ /**
812
+ * The devtools panel: the tree, a node's
813
+ * report, the workers' consoles, the frame profiler and the action log,
814
+ * in one place, docked outside the canvas.
815
+ *
816
+ * It is plain DOM against a `PanelPort`, and knows nothing about where
817
+ * it is mounted: a Chrome extension's devtools page, or a pane in the
818
+ * page itself. That is what lets the same panel be both, and what lets
819
+ * a spec drive it through a port joined in memory.
820
+ *
821
+ * Unlike the in-page inspector, this panel does not float over the
822
+ * application, so it takes pointer events and can be as tall as its
823
+ * host. The trade is that it cannot point at the canvas with the
824
+ * pointer; it points with `highlight`, and the runtime draws the box.
825
+ */
826
+ interface DevtoolsPanel {
827
+ /** The application the panel is showing, or null with none connected. */
828
+ readonly app: DevtoolsAppInfo | null;
829
+ /** Shows a different connected application. */
830
+ show(appId: string): void;
831
+ /** Changes the theme; see `DevtoolsPanelOptions.theme`. */
832
+ setTheme(theme: DevtoolsPanelTheme): void;
833
+ dispose(): void;
834
+ }
835
+ /**
836
+ * `light` and `dark` are the panel's two palettes; `auto` follows the
837
+ * viewer's `prefers-color-scheme`. A host that knows better than the
838
+ * media query says so: Chrome's devtools has its own theme setting,
839
+ * which the extension reads and passes here, and the playground is
840
+ * dark whatever the system prefers.
841
+ */
842
+ type DevtoolsPanelTheme = 'light' | 'dark' | 'auto';
843
+ interface DevtoolsPanelOptions {
844
+ /** Which palette. Default `auto`. */
845
+ readonly theme?: DevtoolsPanelTheme;
846
+ /** How many levels of the tree open on the first snapshot. Default 4. */
847
+ readonly openDepth?: number;
848
+ /** How many console entries are kept. Default 500. */
849
+ readonly consoleLimit?: number;
850
+ /** How many action entries are kept. Default 300. */
851
+ readonly actionLimit?: number;
852
+ }
853
+ declare function mountDevtoolsPanel(host: HTMLElement, port: PanelPort, options?: DevtoolsPanelOptions): DevtoolsPanel;
854
+ //#endregion
855
+ //#region src/TreeRows.d.ts
856
+ /**
857
+ * A tree snapshot as the rows a panel draws.
858
+ *
859
+ * The panel keeps a set of expanded ids and asks for the rows; the
860
+ * tree can be replaced by a new snapshot every frame while the set
861
+ * stays, so a person's unfolding survives the application changing
862
+ * under it. Ids are positional, which is what makes that work and also
863
+ * what makes it approximate: a row that was a button can become a text
864
+ * when a list reorders. The report says what it is now.
865
+ */
866
+ interface TreeRow {
867
+ readonly node: UiTreeNode;
868
+ readonly depth: number;
869
+ readonly expandable: boolean;
870
+ readonly expanded: boolean;
871
+ /** The nearest component anchor at or above this node, if any. */
872
+ readonly owner?: string;
873
+ }
874
+ /** The rows of the tree with `expanded` nodes unfolded, in document order. */
875
+ declare function treeRows(root: UiTreeNode, expanded: ReadonlySet<string>): TreeRow[];
876
+ /** The ids of every node with children down to `depth` levels, for a first unfolding. */
877
+ declare function idsToDepth(root: UiTreeNode, depth: number): string[];
878
+ /** The ids of the ancestors of `id`, root first, or null when the tree has no such node. */
879
+ declare function pathTo(root: UiTreeNode, id: string): string[] | null;
880
+ /** One row's label, as the tree prints it. */
881
+ declare function rowLabel(row: TreeRow): string;
882
+ //#endregion
883
+ //#region src/NodeInspector.d.ts
884
+ /**
885
+ * The node inspector: what the thing under the
886
+ * pointer is, and where every part of it came from.
887
+ *
888
+ * L8 already answered "why is this box that size" and printed it as
889
+ * text. This is the rest of the question a person actually has, which
890
+ * turned out to be four more: what did the element declare, what is a
891
+ * modifier writing over it, what is it inheriting, and which component
892
+ * rendered it. The layout explanation is still here, at the bottom,
893
+ * because it is still the answer to the first one.
894
+ *
895
+ * It is DOM, in a shadow root, over the canvas, for the same reasons
896
+ * the error overlay is: the application it is inspecting owns the
897
+ * canvas, and a panel drawn inside the scene would be part of the
898
+ * scene it is describing. It takes no pointer events at all, because
899
+ * the person is hovering the canvas underneath it and a panel that
900
+ * swallowed the pointer would erase the very thing it is showing.
901
+ */
902
+ interface NodeInspector {
903
+ /** Shows the report, or hides the panel when null. */
904
+ set(report: UiNodeReport | null): void;
905
+ dispose(): void;
906
+ }
907
+ interface NodeInspectorOptions {
908
+ /**
909
+ * Which corner it floats in. Default `'bottom-left'`.
910
+ *
911
+ * A corner rather than a docked side, because the panel must not
912
+ * change the size of the element the application is mounted in: a
913
+ * readout that resized the canvas would relayout the scene it is
914
+ * describing, and the boxes it is pointing at would move.
915
+ */
916
+ readonly corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
917
+ }
918
+ declare function mountNodeInspector(host: HTMLElement, options?: NodeInspectorOptions): NodeInspector;
919
+ //#endregion
920
+ //#region src/FrameProfiler.d.ts
921
+ /**
922
+ * The frame profiler: where a frame's time went, over
923
+ * the last few seconds, as a picture.
924
+ *
925
+ * The timings have existed since L7 and the playground has been showing
926
+ * them as one clipped line of text, which is enough to read a number
927
+ * off and not enough to see a shape. A stall, a phase that only wakes
928
+ * on some frames, a render that grew when a route changed: all three
929
+ * are obvious in a strip of bars and invisible in a running average.
930
+ *
931
+ * It draws into a small canvas rather than a div per bar, because at 60
932
+ * frames a second a DOM per frame is more main-thread work than the
933
+ * thing being profiled. For the same reason it redraws on a timer
934
+ * rather than on every frame: the history is kept per frame, the
935
+ * picture is repainted a few times a second.
936
+ */
937
+ interface FrameProfiler {
938
+ /** Feed it every frame. Cheap: it appends and returns. */
939
+ report(metrics: FrameMetrics): void;
940
+ /** Shows or hides the panel. Hidden panels stop redrawing. */
941
+ setVisible(visible: boolean): void;
942
+ readonly visible: boolean;
943
+ dispose(): void;
944
+ }
945
+ interface FrameProfilerOptions {
946
+ /** How many frames the strip holds. Default 180, about three seconds. */
947
+ readonly history?: number;
948
+ /** How often the picture is repainted, in milliseconds. Default 250. */
949
+ readonly redrawMs?: number;
950
+ readonly corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
951
+ /**
952
+ * `floating` (the default) sits in a corner over the application;
953
+ * `docked` fills its host, for a panel that is not over anything.
954
+ */
955
+ readonly layout?: 'floating' | 'docked';
956
+ }
957
+ /** One bar's worth of history. */
958
+ interface FrameSample {
959
+ readonly phases: Readonly<Record<UiFramePhase, number>>;
960
+ readonly total: number;
961
+ /** Gap since the previous frame, on the rendering thread's clock. */
962
+ readonly gap: number;
963
+ readonly input: number | null;
964
+ }
965
+ declare function mountFrameProfiler(host: HTMLElement, options?: FrameProfilerOptions): FrameProfiler;
966
+ interface FrameSummary {
967
+ readonly fps: number;
968
+ readonly meanTotal: number;
969
+ readonly worstTotal: number;
970
+ readonly worstGap: number;
971
+ readonly worstInput: number | null;
972
+ readonly worstPhase: Record<UiFramePhase, number>;
973
+ }
974
+ /**
975
+ * The window's numbers.
976
+ *
977
+ * Peak rather than mean for the phases, for the reason the playground's
978
+ * status line already gives: patches and environment run on a small
979
+ * minority of frames, so a mean would report them as idle on exactly
980
+ * the frames where they were the cost.
981
+ */
982
+ declare function summarize(samples: readonly FrameSample[]): FrameSummary;
983
+ //#endregion
984
+ //#region src/codeFrame.d.ts
985
+ /** One line of a code frame, with the offending one marked. */
986
+ interface CodeFrameLine {
987
+ number: number;
988
+ text: string;
989
+ /** True for the line the error was reported on. */
990
+ target: boolean;
991
+ }
992
+ /** A few lines of original source around a mapped position. */
993
+ interface CodeFrame {
994
+ lines: CodeFrameLine[];
995
+ /** 1-based column on the target line, for the caret under it. */
996
+ column: number;
997
+ }
998
+ /**
999
+ * Cuts `context` lines either side of a position out of a source file.
1000
+ *
1001
+ * This is the part of an overlay that a stack trace cannot replace: a
1002
+ * file and a line number send a person to their editor, while the line
1003
+ * itself is often the whole answer — a `.length` on something that is
1004
+ * undefined reads as the bug the moment it is on screen.
1005
+ *
1006
+ * Tabs are expanded to two spaces so the caret column below the line
1007
+ * lands where the character does. Nothing else is transformed; the text
1008
+ * reaches the DOM as text, never as markup.
1009
+ */
1010
+ declare function codeFrame(source: string, line: number, column: number, context?: number): CodeFrame | null;
1011
+ //#endregion
1012
+ //#region src/stackTrace.d.ts
1013
+ /** A position in a compiled file, 1-based as every engine reports it. */
1014
+ interface StackLocation {
1015
+ url: string;
1016
+ line: number;
1017
+ column: number;
1018
+ }
1019
+ /** One line of a stack, parsed as far as it could be. */
1020
+ interface StackFrame {
1021
+ /** The line exactly as the engine wrote it. */
1022
+ raw: string;
1023
+ /** The function name the engine knew, or null for an anonymous frame. */
1024
+ fn: string | null;
1025
+ /** Where it ran, or null when the line named no file. */
1026
+ location: StackLocation | null;
1027
+ /** Where it was written, once a source map has been consulted. */
1028
+ original: OriginalPosition | null;
1029
+ }
1030
+ /**
1031
+ * Splits a stack string into frames, dropping the message lines an
1032
+ * engine puts above them.
1033
+ *
1034
+ * The message is dropped rather than parsed because the caller already
1035
+ * has it: `onError` reports the message and the stack separately, and
1036
+ * a V8 stack repeats the message in its first line.
1037
+ */
1038
+ declare function parseStack(stack: string): StackFrame[];
1039
+ /**
1040
+ * Fills in `original` for every frame whose script has a source map.
1041
+ *
1042
+ * Maps are fetched once per script and the frames of one stack usually
1043
+ * name two or three scripts, so this is a couple of requests. It never
1044
+ * rejects: a frame that cannot be mapped keeps its compiled location,
1045
+ * which is what the console would have shown anyway.
1046
+ *
1047
+ * `originalFor` rather than one `lookup`, because a frame inside a
1048
+ * published package is two maps deep: the bundler's map of the
1049
+ * dependency bundle, then the package's own. See its comment.
1050
+ */
1051
+ declare function mapStack(frames: StackFrame[], store: SourceMapStore): Promise<StackFrame[]>;
1052
+ /**
1053
+ * The first frame worth putting a code frame under.
1054
+ *
1055
+ * Frames inside the framework are skipped while any application frame
1056
+ * remains, because an error thrown from a component surfaces through
1057
+ * several layers of runtime and the runtime is almost never where the
1058
+ * bug is. If every frame is a framework frame, the first one wins —
1059
+ * the framework is then genuinely the answer.
1060
+ */
1061
+ declare function primaryFrame(frames: StackFrame[]): StackFrame | null;
1062
+ /**
1063
+ * A path short enough to read in a header: same-origin prefix and
1064
+ * query string removed, `node_modules` collapsed to the package.
1065
+ *
1066
+ * The query matters more than it looks. A dev server rewrites imports
1067
+ * with cache-busting parameters — `/src/App.tsx?t=1724965201` — and a
1068
+ * stack full of those is unreadable for a reason that has nothing to
1069
+ * do with the error.
1070
+ */
1071
+ declare function shortenPath(url: string, origin?: string): string;
1072
+ /** `path:line:column` for a frame, mapped when it could be. */
1073
+ declare function formatFrame(frame: StackFrame, origin?: string): string;
1074
+ //#endregion
1075
+ export { type ActionCause, type ActionEntry, type ActionLog, type ActionLogOptions, type ActionLogPanel, type ActionLogPanelOptions, type ActionLogToken, type ChannelErrorEntry, type CodeFrame, type CodeFrameLine, type CommandEntry, type ConnectDevtoolsOptions, type DevtoolsApp, type DevtoolsAppInfo, type DevtoolsHook, type DevtoolsPanel, type DevtoolsPanelOptions, type DevtoolsPanelTheme, type DevtoolsPicker, type DevtoolsPort, ENVELOPE_SOURCE, type Envelope, type ErrorOrigin, ErrorOverlay, type ErrorOverlayOptions, type FrameEntry, type FrameProfiler, type FrameProfilerOptions, type FrameSample, type FrameSummary, HOOK_PROPERTY, type HookHost, NODE_REPORT_STYLES, type NodeInspector, type NodeInspectorOptions, type NodePickerOptions, type NodeReportViewOptions, type OriginalPosition, type PageMessage, type PagePort, type PanelMessage, type PanelPort, type PatchEntry, type RenderWorkerHost, type RenderWorkerTap, type RenderWorkerTapOptions, SourceMapConsumer, SourceMapStore, type SourceMapV3, type StackFrame, type StackLocation, type TreeRow, type WindowLike, actionGlyph, codeFrame, connectDevtools, createActionLog, createDirectPorts, createNodePicker, decodeMappings, describeActionEntry, formatFrame, forwardNewEntries, getDevtoolsHook, idsToDepth, inputLabel, isEnvelope, mapStack, mountActionLogPanel, mountDevtoolsPanel, mountErrorOverlay, mountFrameProfiler, mountNodeInspector, parseSourceMappingUrl, parseStack, pathTo, primaryFrame, renderNodeReport, rowLabel, shortenPath, summarize, tapRenderWorker, treeRows, windowPagePort, windowPanelPort };
1076
+ //# sourceMappingURL=index.d.ts.map