@weasel-js/devtools 0.0.0-stage → 1.11.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -2
  3. package/dist/DevtoolsPanel.d.ts +20 -0
  4. package/dist/DevtoolsPanel.d.ts.map +1 -0
  5. package/dist/RootPicker.d.ts +10 -0
  6. package/dist/RootPicker.d.ts.map +1 -0
  7. package/dist/client.d.ts +50 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/history.d.ts +13 -0
  10. package/dist/history.d.ts.map +1 -0
  11. package/dist/hooks.d.ts +22 -0
  12. package/dist/hooks.d.ts.map +1 -0
  13. package/dist/inPageTransport.d.ts +9 -0
  14. package/dist/inPageTransport.d.ts.map +1 -0
  15. package/dist/index.d.ts +19 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +2006 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/style.css +632 -0
  20. package/dist/tabs.d.ts +17 -0
  21. package/dist/tabs.d.ts.map +1 -0
  22. package/dist/trace.d.ts +19 -0
  23. package/dist/trace.d.ts.map +1 -0
  24. package/dist/views/Bindings.d.ts +7 -0
  25. package/dist/views/Bindings.d.ts.map +1 -0
  26. package/dist/views/CoordTable.d.ts +24 -0
  27. package/dist/views/CoordTable.d.ts.map +1 -0
  28. package/dist/views/DecisionPath.d.ts +7 -0
  29. package/dist/views/DecisionPath.d.ts.map +1 -0
  30. package/dist/views/DispatchView.d.ts +11 -0
  31. package/dist/views/DispatchView.d.ts.map +1 -0
  32. package/dist/views/FrameView.d.ts +9 -0
  33. package/dist/views/FrameView.d.ts.map +1 -0
  34. package/dist/views/HistoryTable.d.ts +8 -0
  35. package/dist/views/HistoryTable.d.ts.map +1 -0
  36. package/dist/views/HistoryView.d.ts +9 -0
  37. package/dist/views/HistoryView.d.ts.map +1 -0
  38. package/dist/views/NodeDetail.d.ts +12 -0
  39. package/dist/views/NodeDetail.d.ts.map +1 -0
  40. package/dist/views/RegistryEntryDetail.d.ts +12 -0
  41. package/dist/views/RegistryEntryDetail.d.ts.map +1 -0
  42. package/dist/views/RegistryView.d.ts +10 -0
  43. package/dist/views/RegistryView.d.ts.map +1 -0
  44. package/dist/views/SceneView.d.ts +5 -0
  45. package/dist/views/SceneView.d.ts.map +1 -0
  46. package/dist/views/StateView.d.ts +5 -0
  47. package/dist/views/StateView.d.ts.map +1 -0
  48. package/dist/views/TraceTable.d.ts +17 -0
  49. package/dist/views/TraceTable.d.ts.map +1 -0
  50. package/dist/views/ValueView.d.ts +11 -0
  51. package/dist/views/ValueView.d.ts.map +1 -0
  52. package/dist/views/formatAge.d.ts +3 -0
  53. package/dist/views/formatAge.d.ts.map +1 -0
  54. package/dist/views/useSceneTree.d.ts +12 -0
  55. package/dist/views/useSceneTree.d.ts.map +1 -0
  56. package/package.json +53 -4
package/dist/index.js ADDED
@@ -0,0 +1,2006 @@
1
+ import { DEVTOOLS_PROTOCOL, createInPageTransports, installDevtoolsHook } from "@weasel-js/core/devtools-protocol";
2
+ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
+ import { Tab, TabList, TabPanel, Tabs } from "@weasel-js/ui/components/Tabs";
4
+ import { useLatest, useVisibleRaf } from "@weasel-js/react";
5
+ import { Select } from "@weasel-js/ui/components/Select";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Button } from "@weasel-js/ui/components/Button";
8
+ import { Switch } from "@weasel-js/ui/components/Switch";
9
+ import { ToggleBar } from "@weasel-js/ui/components/ToggleBar";
10
+ import { DataGrid } from "@weasel-js/ui/components/DataGrid";
11
+ import { FallthroughDiagram } from "@weasel-js/ui/components/FallthroughDiagram";
12
+ import { Powerline } from "@weasel-js/ui/components/Powerline";
13
+ import { DetailList, DetailRow } from "@weasel-js/ui/components/DetailList";
14
+ import { Disclosure } from "@weasel-js/ui/components/Disclosure";
15
+ import { Badge } from "@weasel-js/ui/components/Badge";
16
+ import { Code } from "@weasel-js/ui/components/Code";
17
+ import { Input } from "@weasel-js/ui/components/Input";
18
+ import { Tree } from "@weasel-js/ui/components/Tree";
19
+ import { GestureRoute } from "@weasel-js/ui/components/GestureRoute";
20
+ import { DEBUG_FEATURES } from "@weasel-js/core";
21
+ //#region src/client.ts
22
+ /**
23
+ * The panel's side of the devtools protocol, free of any framework: the
24
+ * handshake, request ids and their replies, the roots the page reports, and
25
+ * its change notices. The views, the extension and tests all drive this.
26
+ */
27
+ /** A connection over `transport`, sending nothing until it is opened. It is connected once the page says `sup`. */
28
+ function createDevtoolsClient(transport) {
29
+ let state = {
30
+ status: "closed",
31
+ kitVersion: null,
32
+ roots: [],
33
+ error: null
34
+ };
35
+ const listeners = /* @__PURE__ */ new Set();
36
+ const changeListeners = /* @__PURE__ */ new Set();
37
+ const pending = /* @__PURE__ */ new Map();
38
+ let nextId = 1;
39
+ const set = (patch) => {
40
+ state = {
41
+ ...state,
42
+ ...patch
43
+ };
44
+ for (const l of [...listeners]) l();
45
+ };
46
+ const receive = (m) => {
47
+ if (state.status === "closed") return;
48
+ switch (m.type) {
49
+ case "sup":
50
+ set({
51
+ status: "connected",
52
+ kitVersion: m.kitVersion,
53
+ roots: m.roots,
54
+ error: null
55
+ });
56
+ return;
57
+ case "root-added":
58
+ set({ roots: [...state.roots.filter((r) => r.id !== m.root.id), m.root] });
59
+ return;
60
+ case "root-removed":
61
+ set({ roots: state.roots.filter((r) => r.id !== m.rootId) });
62
+ return;
63
+ case "changed": {
64
+ const areas = Object.keys(m.areas);
65
+ for (const l of [...changeListeners]) l(m.rootId, areas);
66
+ return;
67
+ }
68
+ case "result":
69
+ pending.get(m.requestId)?.resolve(m.result);
70
+ pending.delete(m.requestId);
71
+ return;
72
+ case "error":
73
+ if (m.requestId === null) {
74
+ set({
75
+ status: "error",
76
+ error: m.message
77
+ });
78
+ return;
79
+ }
80
+ pending.get(m.requestId)?.reject(new Error(m.message));
81
+ pending.delete(m.requestId);
82
+ return;
83
+ }
84
+ };
85
+ let off = null;
86
+ return {
87
+ state: () => state,
88
+ subscribe(listener) {
89
+ listeners.add(listener);
90
+ return () => {
91
+ listeners.delete(listener);
92
+ };
93
+ },
94
+ onChanged(listener) {
95
+ changeListeners.add(listener);
96
+ return () => {
97
+ changeListeners.delete(listener);
98
+ };
99
+ },
100
+ request(rootId, body) {
101
+ if (state.status === "closed") return Promise.reject(/* @__PURE__ */ new Error("the devtools connection is closed"));
102
+ const requestId = nextId++;
103
+ return new Promise((resolve, reject) => {
104
+ pending.set(requestId, {
105
+ resolve,
106
+ reject
107
+ });
108
+ transport.send({
109
+ ...body,
110
+ protocol: DEVTOOLS_PROTOCOL,
111
+ requestId,
112
+ rootId
113
+ });
114
+ });
115
+ },
116
+ open() {
117
+ if (off) return;
118
+ off = transport.onMessage(receive);
119
+ set({
120
+ status: "connecting",
121
+ error: null
122
+ });
123
+ transport.send({
124
+ type: "yo",
125
+ protocol: DEVTOOLS_PROTOCOL
126
+ });
127
+ },
128
+ close() {
129
+ if (!off) return;
130
+ transport.send({
131
+ type: "bye",
132
+ protocol: DEVTOOLS_PROTOCOL
133
+ });
134
+ off();
135
+ off = null;
136
+ set({ status: "closed" });
137
+ const err = /* @__PURE__ */ new Error("the devtools connection is closed");
138
+ for (const p of pending.values()) p.reject(err);
139
+ pending.clear();
140
+ }
141
+ };
142
+ }
143
+ /** {@link createDevtoolsClient}, opened: it says `yo` at once. */
144
+ function connectDevtools(transport) {
145
+ const client = createDevtoolsClient(transport);
146
+ client.open();
147
+ return client;
148
+ }
149
+ //#endregion
150
+ //#region src/trace.ts
151
+ /** Follow `rootId`'s dispatch trace through `client`, from the oldest record the canvas still holds. */
152
+ function followTrace(client, rootId) {
153
+ let cursor = 0;
154
+ let coordCursor = null;
155
+ let last = Promise.resolve();
156
+ const readOnce = async () => {
157
+ const slice = await client.request(rootId, {
158
+ type: "get-trace",
159
+ cursor,
160
+ ...coordCursor !== null ? { coordCursor } : {}
161
+ });
162
+ cursor = slice.cursor;
163
+ const out = {
164
+ entries: slice.entries,
165
+ live: slice.live
166
+ };
167
+ if (slice.coords && coordCursor !== null) {
168
+ coordCursor = slice.coords.cursor;
169
+ out.coords = slice.coords.entries;
170
+ }
171
+ return out;
172
+ };
173
+ return {
174
+ read() {
175
+ const next = last.then(readOnce, readOnce);
176
+ last = next.catch(() => void 0);
177
+ return next;
178
+ },
179
+ setCoords(on) {
180
+ if (on && coordCursor === null) coordCursor = 0;
181
+ else if (!on) coordCursor = null;
182
+ }
183
+ };
184
+ }
185
+ //#endregion
186
+ //#region src/history.ts
187
+ /** Follow `rootId`'s history steps through `client`, from the oldest step the canvas still holds. */
188
+ function followHistory(client, rootId) {
189
+ let cursor = 0;
190
+ let checkpoints = false;
191
+ let last = Promise.resolve();
192
+ const readOnce = async () => {
193
+ const slice = await client.request(rootId, {
194
+ type: "get-history",
195
+ cursor,
196
+ ...checkpoints ? { checkpoints } : {}
197
+ });
198
+ cursor = slice.cursor;
199
+ return slice;
200
+ };
201
+ return {
202
+ read() {
203
+ const next = last.then(readOnce, readOnce);
204
+ last = next.catch(() => void 0);
205
+ return next;
206
+ },
207
+ setCheckpoints(on) {
208
+ checkpoints = on;
209
+ }
210
+ };
211
+ }
212
+ //#endregion
213
+ //#region src/hooks.ts
214
+ /** The React side of a connection: the client a panel holds, the data it reads per root, and its polls. */
215
+ /** A connection over `transport` for as long as the component is mounted: `yo` on mount, `bye` on unmount. */
216
+ function useDevtoolsClient(transport) {
217
+ const client = useMemo(() => createDevtoolsClient(transport), [transport]);
218
+ useEffect(() => {
219
+ client.open();
220
+ return () => client.close();
221
+ }, [client]);
222
+ return {
223
+ client,
224
+ state: useSyncExternalStore(client.subscribe, client.state)
225
+ };
226
+ }
227
+ /**
228
+ * `read`'s answer about `rootId`, asked again whenever a `changed` notice for
229
+ * that root names one of `areas`, or a dependency in `deps` changes.
230
+ */
231
+ function useRootQuery(client, rootId, areas, read, deps = []) {
232
+ const [data, setData] = useState(void 0);
233
+ const [error, setError] = useState(null);
234
+ const [version, setVersion] = useState(0);
235
+ const readRef = useLatest(read);
236
+ const areaKey = areas.join(",");
237
+ useEffect(() => client.onChanged((id, changed) => {
238
+ if (id === rootId && changed.some((a) => areaKey.split(",").includes(a))) setVersion((v) => v + 1);
239
+ }), [
240
+ client,
241
+ rootId,
242
+ areaKey
243
+ ]);
244
+ useEffect(() => {
245
+ let live = true;
246
+ readRef.current().then((result) => {
247
+ if (live) {
248
+ setData(result);
249
+ setError(null);
250
+ }
251
+ }, (err) => {
252
+ if (live) setError(err.message);
253
+ });
254
+ return () => {
255
+ live = false;
256
+ };
257
+ }, [
258
+ client,
259
+ rootId,
260
+ version,
261
+ readRef,
262
+ ...deps
263
+ ]);
264
+ return {
265
+ data,
266
+ error,
267
+ refresh: useRef(() => setVersion((v) => v + 1)).current
268
+ };
269
+ }
270
+ /** Call `tick` every `ms` while `enabled` and the page is visible, and once at the start. */
271
+ function usePoll(tick, ms, enabled) {
272
+ const tickRef = useLatest(tick);
273
+ const last = useRef(-Infinity);
274
+ const loop = useVisibleRaf((time, l) => {
275
+ if (time - last.current >= ms) {
276
+ last.current = time;
277
+ tickRef.current();
278
+ }
279
+ l.request();
280
+ });
281
+ useEffect(() => {
282
+ if (!enabled) return;
283
+ last.current = -Infinity;
284
+ loop.request();
285
+ return () => loop.cancel();
286
+ }, [enabled, loop]);
287
+ }
288
+ var DevtoolsPanel_module_default = {
289
+ panel: "_panel_485101",
290
+ bar: "_bar_485101",
291
+ picker: "_picker_485101",
292
+ dimension: "_dimension_485101",
293
+ status: "_status_485101",
294
+ tabs: "_tabs_485101",
295
+ tabList: "_tabList_485101",
296
+ tabPanel: "_tabPanel_485101",
297
+ empty: "_empty_485101"
298
+ };
299
+ //#endregion
300
+ //#region src/RootPicker.tsx
301
+ /** Which canvas the panel reads, each named by its label and dimension. */
302
+ function RootPicker({ roots, value, onChange }) {
303
+ return /* @__PURE__ */ jsx(Select, {
304
+ className: DevtoolsPanel_module_default.picker,
305
+ "aria-label": "Canvas",
306
+ width: "fit",
307
+ placeholder: "No canvas",
308
+ isDisabled: roots.length === 0,
309
+ selectedKey: value,
310
+ onSelectionChange: onChange,
311
+ options: roots.map((r) => ({
312
+ value: r.id,
313
+ textValue: `${r.label} ${r.dimension}`,
314
+ label: /* @__PURE__ */ jsxs(Fragment, { children: [
315
+ r.label,
316
+ " ",
317
+ /* @__PURE__ */ jsx("span", {
318
+ className: DevtoolsPanel_module_default.dimension,
319
+ children: r.dimension
320
+ })
321
+ ] })
322
+ }))
323
+ });
324
+ }
325
+ //#endregion
326
+ //#region src/views/formatAge.ts
327
+ /** How long ago something happened, in the shortest unit that reads well: `840ms`, `12.3s`, `4m07s`. */
328
+ function formatAge(ms) {
329
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
330
+ const sec = ms / 1e3;
331
+ if (sec < 60) return `${sec.toFixed(1)}s`;
332
+ return `${Math.floor(sec / 60)}m${String(Math.floor(sec % 60)).padStart(2, "0")}s`;
333
+ }
334
+ var TraceTable_module_default = {
335
+ table: "_table_e7e309",
336
+ age: "_age_e7e309 _numeric_25fd0b",
337
+ count: "_count_e7e309 _numeric_25fd0b",
338
+ unhandled: "_unhandled_e7e309",
339
+ mode: "_mode_e7e309",
340
+ modeDetail: "_modeDetail_e7e309",
341
+ live: "_live_e7e309",
342
+ detail: "_detail_e7e309",
343
+ heading: "_heading_e7e309"
344
+ };
345
+ //#endregion
346
+ //#region src/views/CoordTable.tsx
347
+ /** Wide enough for a coordinate in the tens of thousands, sign included, at one decimal. */
348
+ var fixed = (n, digits = 1) => n.toFixed(digits).padStart(8, " ");
349
+ var pair = (x, y) => `${fixed(x)} ${fixed(y)}`;
350
+ function CoordTable({ entries, now }) {
351
+ const rows = entries.slice(-100).map((entry, i) => ({
352
+ id: `${entry.ts}-${i}`,
353
+ entry
354
+ })).reverse();
355
+ const columns = [
356
+ {
357
+ id: "age",
358
+ header: "Age",
359
+ sortable: false,
360
+ className: TraceTable_module_default.age,
361
+ render: ({ entry }) => formatAge(Math.max(0, now - entry.ts))
362
+ },
363
+ {
364
+ id: "client",
365
+ header: "Client",
366
+ sortable: false,
367
+ className: TraceTable_module_default.count,
368
+ render: ({ entry }) => pair(entry.clientX, entry.clientY)
369
+ },
370
+ {
371
+ id: "world",
372
+ header: "World",
373
+ sortable: false,
374
+ className: TraceTable_module_default.count,
375
+ render: ({ entry }) => pair(entry.world.x, entry.world.y)
376
+ },
377
+ {
378
+ id: "scale",
379
+ header: "Scale",
380
+ sortable: false,
381
+ className: TraceTable_module_default.count,
382
+ render: ({ entry }) => entry.view ? fixed(entry.view.scaleX, 3) : "fallback"
383
+ }
384
+ ];
385
+ return /* @__PURE__ */ jsxs("section", {
386
+ "aria-label": "Coord records",
387
+ children: [/* @__PURE__ */ jsx("h3", {
388
+ className: TraceTable_module_default.heading,
389
+ children: "Coords"
390
+ }), /* @__PURE__ */ jsx(DataGrid, {
391
+ className: TraceTable_module_default.table,
392
+ rows,
393
+ columns,
394
+ empty: "No conversions recorded yet."
395
+ })]
396
+ });
397
+ }
398
+ //#endregion
399
+ //#region src/views/DecisionPath.tsx
400
+ function inputText(record) {
401
+ const { eventKind, key } = record.input;
402
+ return key === void 0 ? eventKind : `${eventKind} ${key === " " ? "Space" : key}`;
403
+ }
404
+ function DecisionPath({ record }) {
405
+ const winner = record.ranked.find((r) => r.walk.kind === "fired" || r.walk.kind === "would-fire");
406
+ const segments = [{
407
+ text: inputText(record),
408
+ status: "info",
409
+ endCap: "chevron"
410
+ }];
411
+ if (winner) segments.push({
412
+ text: winner.candidate.ownerId ?? "action",
413
+ status: "muted",
414
+ endCap: "chevron"
415
+ }, {
416
+ text: winner.candidate.actionId,
417
+ status: "accent",
418
+ endCap: "chevron"
419
+ });
420
+ else segments.push({
421
+ text: `${record.ranked.length} ranked, none fired`,
422
+ status: "muted",
423
+ endCap: "chevron"
424
+ });
425
+ segments.push(record.outcome === "handled" ? {
426
+ text: record.predicted ? "would handle" : "handled",
427
+ status: "success"
428
+ } : {
429
+ text: "unhandled",
430
+ status: "danger"
431
+ });
432
+ return /* @__PURE__ */ jsx(Powerline, {
433
+ segments,
434
+ size: "sm",
435
+ variant: "subtle",
436
+ "aria-label": "Decision path"
437
+ });
438
+ }
439
+ //#endregion
440
+ //#region src/views/TraceTable.tsx
441
+ /** The trace as rows, newest first under the live prediction; a dispatch row opens to the path it took and every stage behind it. */
442
+ var LIVE_ID = "live";
443
+ /** Ids from each entry's timestamp, numbering entries that share one. */
444
+ function withTsIds(entries) {
445
+ const seen = /* @__PURE__ */ new Map();
446
+ return entries.map((entry) => {
447
+ const n = seen.get(entry.ts) ?? 0;
448
+ seen.set(entry.ts, n + 1);
449
+ return {
450
+ id: n === 0 ? String(entry.ts) : `${entry.ts}#${n}`,
451
+ entry
452
+ };
453
+ });
454
+ }
455
+ function renderEvent(entry) {
456
+ if (entry.kind === "mode") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("code", { children: entry.mode }), entry.detail ? /* @__PURE__ */ jsxs("span", {
457
+ className: TraceTable_module_default.modeDetail,
458
+ children: [
459
+ " (",
460
+ entry.detail,
461
+ ")"
462
+ ]
463
+ }) : null] });
464
+ const { key } = entry.input;
465
+ return /* @__PURE__ */ jsxs(Fragment, { children: [entry.input.eventKind, key !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("code", { children: key === " " ? "Space" : key })] }) : null] });
466
+ }
467
+ function renderOutcome(entry) {
468
+ if (entry.kind === "mode") return /* @__PURE__ */ jsxs(Fragment, { children: [
469
+ /* @__PURE__ */ jsx("code", { children: entry.from ?? "∅" }),
470
+ " → ",
471
+ /* @__PURE__ */ jsx("code", { children: entry.to ?? "∅" })
472
+ ] });
473
+ if (entry.outcome === "unhandled") return "unhandled";
474
+ return entry.fired ? /* @__PURE__ */ jsx("code", { children: entry.fired }) : "handled";
475
+ }
476
+ function TraceTable({ entries, live, now, showHandled, showUnhandled, empty }) {
477
+ const [expanded, setExpanded] = useState(/* @__PURE__ */ new Set());
478
+ const rows = withTsIds(entries).filter(({ entry: e }) => e.kind === "mode" || e.outcome === "handled" ? showHandled : showUnhandled).slice(-100).reverse();
479
+ if (live) rows.unshift({
480
+ id: LIVE_ID,
481
+ entry: live
482
+ });
483
+ const columns = [
484
+ {
485
+ id: "age",
486
+ header: "Age",
487
+ sortable: false,
488
+ className: TraceTable_module_default.age,
489
+ render: ({ id, entry }) => id === LIVE_ID ? "live" : formatAge(Math.max(0, now - entry.ts))
490
+ },
491
+ {
492
+ id: "event",
493
+ header: "Event",
494
+ sortable: false,
495
+ render: ({ entry }) => renderEvent(entry)
496
+ },
497
+ {
498
+ id: "outcome",
499
+ header: "Outcome",
500
+ sortable: false,
501
+ render: ({ entry }) => renderOutcome(entry)
502
+ },
503
+ {
504
+ id: "matched",
505
+ header: "Matched",
506
+ sortable: false,
507
+ className: TraceTable_module_default.count,
508
+ render: ({ entry }) => entry.kind === "mode" ? "—" : entry.matched.length
509
+ }
510
+ ];
511
+ return /* @__PURE__ */ jsx(DataGrid, {
512
+ className: TraceTable_module_default.table,
513
+ rows,
514
+ columns,
515
+ empty,
516
+ rowClassName: ({ id, entry }) => id === LIVE_ID ? TraceTable_module_default.live : entry.kind === "mode" ? TraceTable_module_default.mode : entry.outcome === "unhandled" ? TraceTable_module_default.unhandled : void 0,
517
+ rowExpandable: ({ entry }) => entry.kind === "dispatch",
518
+ expandedIds: expanded,
519
+ onExpandedChange: setExpanded,
520
+ onRowClick: ({ id, entry }) => {
521
+ if (entry.kind !== "dispatch") return;
522
+ setExpanded((cur) => cur.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]));
523
+ },
524
+ renderDetail: ({ entry }) => entry.kind === "dispatch" ? /* @__PURE__ */ jsxs("div", {
525
+ className: TraceTable_module_default.detail,
526
+ children: [/* @__PURE__ */ jsx(DecisionPath, { record: entry }), /* @__PURE__ */ jsx(FallthroughDiagram, { record: entry })]
527
+ }) : null
528
+ });
529
+ }
530
+ var DispatchView_module_default = {
531
+ dispatch: "_dispatch_fdab1f",
532
+ toolbar: "_toolbar_fdab1f",
533
+ count: "_count_fdab1f _numeric_25fd0b",
534
+ body: "_body_fdab1f"
535
+ };
536
+ //#endregion
537
+ //#region src/views/DispatchView.tsx
538
+ /**
539
+ * The Dispatch tab: the root's trace of resolved inputs and mode switches,
540
+ * what a press at the pointer would do now, and, behind a toggle, the
541
+ * `clientToWorld` conversions. Read through cursors, so each read carries only
542
+ * what is new; polled while open, because the live prediction moves with the
543
+ * pointer and no notice reports that.
544
+ */
545
+ var POLL_MS$1 = 250;
546
+ function DispatchView({ client, rootId }) {
547
+ const follower = useMemo(() => followTrace(client, rootId), [client, rootId]);
548
+ const [entries, setEntries] = useState([]);
549
+ const [coords, setCoords] = useState([]);
550
+ const [live, setLive] = useState(null);
551
+ const [now, setNow] = useState(() => Date.now());
552
+ const [shown, setShown] = useState(["handled"]);
553
+ const [showCoords, setShowCoords] = useState(false);
554
+ const read = () => {
555
+ follower.read().then((slice) => {
556
+ if (slice.entries.length) setEntries((prev) => [...prev, ...slice.entries].slice(-200));
557
+ if (slice.coords?.length) setCoords((prev) => [...prev, ...slice.coords].slice(-200));
558
+ setLive(slice.live);
559
+ setNow(Date.now());
560
+ }, () => {});
561
+ };
562
+ useEffect(() => client.onChanged((id, areas) => {
563
+ if (id === rootId && areas.includes("trace")) read();
564
+ }));
565
+ usePoll(read, POLL_MS$1, true);
566
+ const toggleCoords = (on) => {
567
+ follower.setCoords(on);
568
+ setShowCoords(on);
569
+ if (!on) setCoords([]);
570
+ };
571
+ return /* @__PURE__ */ jsxs("div", {
572
+ className: DispatchView_module_default.dispatch,
573
+ children: [/* @__PURE__ */ jsxs("div", {
574
+ className: DispatchView_module_default.toolbar,
575
+ children: [
576
+ /* @__PURE__ */ jsx(ToggleBar, {
577
+ mode: "multiple",
578
+ variant: "minimal",
579
+ size: "sm",
580
+ ariaLabel: "Outcome filters",
581
+ items: [{
582
+ value: "handled",
583
+ label: "handled",
584
+ tooltip: "Show handled inputs and mode switches"
585
+ }, {
586
+ value: "unhandled",
587
+ label: "unhandled",
588
+ tooltip: "Show inputs nothing handled"
589
+ }],
590
+ value: shown,
591
+ onChange: (next) => setShown(next)
592
+ }),
593
+ /* @__PURE__ */ jsx(Switch, {
594
+ isSelected: showCoords,
595
+ onChange: toggleCoords,
596
+ children: "coords"
597
+ }),
598
+ /* @__PURE__ */ jsx(Button, {
599
+ size: "sm",
600
+ variant: "ghost",
601
+ disabled: entries.length === 0,
602
+ onClick: () => setEntries([]),
603
+ children: "Clear"
604
+ }),
605
+ /* @__PURE__ */ jsxs("span", {
606
+ className: DispatchView_module_default.count,
607
+ children: [
608
+ entries.length,
609
+ " ",
610
+ entries.length === 1 ? "record" : "records"
611
+ ]
612
+ })
613
+ ]
614
+ }), /* @__PURE__ */ jsxs("div", {
615
+ className: DispatchView_module_default.body,
616
+ children: [/* @__PURE__ */ jsx(TraceTable, {
617
+ entries,
618
+ live,
619
+ now,
620
+ showHandled: shown.includes("handled"),
621
+ showUnhandled: shown.includes("unhandled"),
622
+ empty: entries.length > 0 ? "Every record is hidden by the filters above." : "No input yet. Use the canvas to fill the trace."
623
+ }), showCoords && /* @__PURE__ */ jsx(CoordTable, {
624
+ entries: coords,
625
+ now
626
+ })]
627
+ })]
628
+ });
629
+ }
630
+ var ValueView_module_default = {
631
+ value: "_value_3839e5",
632
+ row: "_row_3839e5",
633
+ children: "_children_3839e5",
634
+ key: "_key_3839e5",
635
+ muted: "_muted_3839e5",
636
+ keyword: "_keyword_3839e5",
637
+ number: "_number_3839e5 _numeric_25fd0b",
638
+ string: "_string_3839e5",
639
+ fn: "_fn_3839e5",
640
+ tag: "_tag_3839e5"
641
+ };
642
+ //#endregion
643
+ //#region src/views/ValueView.tsx
644
+ /** A serialized value as a reader expects to see it: tagged maps, sets and typed arrays named, functions and cycles marked. */
645
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
646
+ /** The children a container lists, or null for a value shown on one line. */
647
+ function entriesOf(v) {
648
+ if (Array.isArray(v)) return v.map((x, i) => [String(i), x]);
649
+ if (!isObj(v)) return null;
650
+ if (v.$type === "Map" || v.$type === "Set") return (Array.isArray(v.entries) ? v.entries : []).map((x, i) => [String(i), x]);
651
+ if (typeof v.$type === "string") return null;
652
+ return Object.entries(v).filter(([k]) => k !== "$class");
653
+ }
654
+ /** What a container is called on its own row: `Array(3)`, `Map(2)`, `Path {…}`. */
655
+ function summaryOf(v) {
656
+ if (Array.isArray(v)) return `Array(${v.length})`;
657
+ if (!isObj(v)) return "";
658
+ if (v.$type === "Map" || v.$type === "Set") return `${v.$type}(${String(v.size)})`;
659
+ const name = typeof v.$class === "string" ? v.$class : "";
660
+ return Object.keys(v).length === 0 ? `${name}{}` : `${name ? `${name} ` : ""}{…}`;
661
+ }
662
+ function Scalar({ value }) {
663
+ if (value === void 0) return /* @__PURE__ */ jsx("span", {
664
+ className: ValueView_module_default.muted,
665
+ children: "undefined"
666
+ });
667
+ if (value === null) return /* @__PURE__ */ jsx("span", {
668
+ className: ValueView_module_default.keyword,
669
+ children: "null"
670
+ });
671
+ if (typeof value === "number") return /* @__PURE__ */ jsx("span", {
672
+ className: ValueView_module_default.number,
673
+ children: value
674
+ });
675
+ if (typeof value === "boolean") return /* @__PURE__ */ jsx("span", {
676
+ className: ValueView_module_default.keyword,
677
+ children: String(value)
678
+ });
679
+ if (typeof value === "string") {
680
+ if (value.startsWith("ƒ ")) return /* @__PURE__ */ jsx("span", {
681
+ className: ValueView_module_default.fn,
682
+ children: value
683
+ });
684
+ if (value === "[cycle]" || value === "…" || value.startsWith("… ")) return /* @__PURE__ */ jsx("span", {
685
+ className: ValueView_module_default.muted,
686
+ children: value
687
+ });
688
+ return /* @__PURE__ */ jsx("span", {
689
+ className: ValueView_module_default.string,
690
+ children: JSON.stringify(value)
691
+ });
692
+ }
693
+ if (isObj(value) && value.$type === "Date") return /* @__PURE__ */ jsxs("span", {
694
+ className: ValueView_module_default.tag,
695
+ children: ["Date ", String(value.value)]
696
+ });
697
+ if (isObj(value) && typeof value.$type === "string") return /* @__PURE__ */ jsxs("span", {
698
+ className: ValueView_module_default.tag,
699
+ children: [
700
+ value.$type,
701
+ "(",
702
+ String(value.length),
703
+ ")"
704
+ ]
705
+ });
706
+ return /* @__PURE__ */ jsx("span", {
707
+ className: ValueView_module_default.muted,
708
+ children: summaryOf(value)
709
+ });
710
+ }
711
+ function Entry({ name, value, depth }) {
712
+ const entries = entriesOf(value);
713
+ const [open, setOpen] = useState(depth < 2);
714
+ if (!entries || entries.length === 0) return /* @__PURE__ */ jsxs("div", {
715
+ className: ValueView_module_default.row,
716
+ children: [/* @__PURE__ */ jsx("span", {
717
+ className: ValueView_module_default.key,
718
+ children: name
719
+ }), /* @__PURE__ */ jsx(Scalar, { value })]
720
+ });
721
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
722
+ className: ValueView_module_default.row,
723
+ children: [
724
+ /* @__PURE__ */ jsx(Disclosure, {
725
+ open,
726
+ onToggle: () => setOpen((o) => !o),
727
+ label: `${String(name)} ${summaryOf(value)}`,
728
+ size: 11
729
+ }),
730
+ /* @__PURE__ */ jsx("span", {
731
+ className: ValueView_module_default.key,
732
+ children: name
733
+ }),
734
+ /* @__PURE__ */ jsx("span", {
735
+ className: ValueView_module_default.muted,
736
+ children: summaryOf(value)
737
+ })
738
+ ]
739
+ }), open && /* @__PURE__ */ jsx("div", {
740
+ className: ValueView_module_default.children,
741
+ children: entries.map(([k, v]) => /* @__PURE__ */ jsx(Entry, {
742
+ name: k,
743
+ value: v,
744
+ depth: depth + 1
745
+ }, k))
746
+ })] });
747
+ }
748
+ /** `value` as nested rows, its containers folding; the top level lists its entries directly. */
749
+ function ValueView({ value, label }) {
750
+ const entries = entriesOf(value);
751
+ return /* @__PURE__ */ jsx("div", {
752
+ className: ValueView_module_default.value,
753
+ role: "group",
754
+ "aria-label": label,
755
+ children: entries && entries.length > 0 ? entries.map(([k, v]) => /* @__PURE__ */ jsx(Entry, {
756
+ name: k,
757
+ value: v,
758
+ depth: 1
759
+ }, k)) : /* @__PURE__ */ jsx(Scalar, { value })
760
+ });
761
+ }
762
+ var FrameView_module_default = {
763
+ frame: "_frame_ae00c9",
764
+ figure: "_figure_ae00c9 _numeric_25fd0b",
765
+ layers: "_layers_ae00c9",
766
+ note: "_note_ae00c9"
767
+ };
768
+ //#endregion
769
+ //#region src/views/FrameView.tsx
770
+ /**
771
+ * The Frame tab: what the last paint cost, overall and per render layer. The
772
+ * canvas measures only while its `fps` overlay is on and announces nothing, so
773
+ * this polls, and only while the tab is open and the overlay on.
774
+ */
775
+ var POLL_MS = 500;
776
+ var isFrameStats = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && typeof v.paintMs === "number" && Array.isArray(v.layers);
777
+ var ms = (n) => n.toFixed(2).padStart(7, " ");
778
+ var COLUMNS = [
779
+ {
780
+ id: "id",
781
+ header: "Layer",
782
+ sortable: false,
783
+ render: (r) => r.id
784
+ },
785
+ {
786
+ id: "calls",
787
+ header: "Draw calls",
788
+ sortable: false,
789
+ className: FrameView_module_default.figure,
790
+ render: (r) => r.drawCalls
791
+ },
792
+ {
793
+ id: "ms",
794
+ header: "CPU ms",
795
+ sortable: false,
796
+ className: FrameView_module_default.figure,
797
+ render: (r) => ms(r.ms)
798
+ }
799
+ ];
800
+ function FrameView({ client, rootId }) {
801
+ const state = useRootQuery(client, rootId, [], () => client.request(rootId, { type: "get-state" }));
802
+ const fps = state.data?.overlays.fps === true;
803
+ const [frame, setFrame] = useState(null);
804
+ usePoll(() => {
805
+ client.request(rootId, { type: "get-frame" }).then(setFrame, () => {});
806
+ }, POLL_MS, fps);
807
+ const setFps = (on) => {
808
+ client.request(rootId, {
809
+ type: "set-overlays",
810
+ overlays: { fps: on }
811
+ }).then(state.refresh, state.refresh);
812
+ };
813
+ return /* @__PURE__ */ jsxs("div", {
814
+ className: FrameView_module_default.frame,
815
+ children: [/* @__PURE__ */ jsx(Switch, {
816
+ isSelected: fps,
817
+ onChange: setFps,
818
+ isDisabled: !state.data,
819
+ children: "fps overlay"
820
+ }), !fps ? /* @__PURE__ */ jsx("p", {
821
+ className: FrameView_module_default.note,
822
+ children: "The canvas measures its paints only while the fps overlay is on."
823
+ }) : isFrameStats(frame) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(DetailList, {
824
+ layout: "inline",
825
+ values: "figures",
826
+ children: [/* @__PURE__ */ jsx(DetailRow, {
827
+ label: "CPU ms",
828
+ children: ms(frame.paintMs)
829
+ }), /* @__PURE__ */ jsx(DetailRow, {
830
+ label: "Draw calls",
831
+ children: frame.drawCalls
832
+ })]
833
+ }), /* @__PURE__ */ jsx(DataGrid, {
834
+ className: FrameView_module_default.layers,
835
+ rows: frame.layers.map((l) => ({ ...l })),
836
+ columns: COLUMNS,
837
+ empty: "No layers painted."
838
+ })] }) : frame === null ? /* @__PURE__ */ jsx("p", {
839
+ className: FrameView_module_default.note,
840
+ children: "No paint measured yet."
841
+ }) : /* @__PURE__ */ jsx(ValueView, {
842
+ value: frame,
843
+ label: "Frame"
844
+ })]
845
+ });
846
+ }
847
+ var HistoryView_module_default = {
848
+ history: "_history_739ab5",
849
+ toolbar: "_toolbar_739ab5",
850
+ position: "_position_739ab5 _numeric_25fd0b",
851
+ body: "_body_739ab5",
852
+ heading: "_heading_739ab5",
853
+ stack: "_stack_739ab5",
854
+ applied: "_applied_739ab5",
855
+ redoable: "_redoable_739ab5",
856
+ table: "_table_739ab5",
857
+ age: "_age_739ab5 _numeric_25fd0b",
858
+ figure: "_figure_739ab5 _numeric_25fd0b",
859
+ entries: "_entries_739ab5",
860
+ entry: "_entry_739ab5",
861
+ entryTitle: "_entryTitle_739ab5",
862
+ ops: "_ops_739ab5",
863
+ op: "_op_739ab5",
864
+ targets: "_targets_739ab5",
865
+ note: "_note_739ab5"
866
+ };
867
+ //#endregion
868
+ //#region src/views/HistoryTable.tsx
869
+ /** History steps as rows, newest first; a row opens to the entries it touched and each entry's ops. */
870
+ var opCount = (step) => step.entries.reduce((n, e) => n + e.opCount, 0);
871
+ function StepDetail({ step }) {
872
+ return /* @__PURE__ */ jsx("div", {
873
+ className: HistoryView_module_default.entries,
874
+ children: step.entries.map((entry) => /* @__PURE__ */ jsxs("section", {
875
+ className: HistoryView_module_default.entry,
876
+ "aria-label": `Entry ${entry.label || entry.id}`,
877
+ children: [/* @__PURE__ */ jsxs("h4", {
878
+ className: HistoryView_module_default.entryTitle,
879
+ children: [entry.label || /* @__PURE__ */ jsx("span", {
880
+ className: HistoryView_module_default.note,
881
+ children: "unlabeled"
882
+ }), /* @__PURE__ */ jsxs("span", {
883
+ className: HistoryView_module_default.figure,
884
+ children: [
885
+ entry.opCount,
886
+ " ",
887
+ entry.opCount === 1 ? "op" : "ops"
888
+ ]
889
+ })]
890
+ }), /* @__PURE__ */ jsx("ul", {
891
+ className: HistoryView_module_default.ops,
892
+ children: entry.ops.map((op, i) => /* @__PURE__ */ jsxs("li", {
893
+ className: HistoryView_module_default.op,
894
+ children: [
895
+ /* @__PURE__ */ jsx(Code, {
896
+ size: "sm",
897
+ children: op.kind
898
+ }),
899
+ op.targets.length > 0 && /* @__PURE__ */ jsx("span", {
900
+ className: HistoryView_module_default.targets,
901
+ children: op.targets.join(", ")
902
+ }),
903
+ /* @__PURE__ */ jsx(ValueView, {
904
+ value: op.summary,
905
+ label: `${op.kind} arguments`
906
+ })
907
+ ]
908
+ }, i))
909
+ })]
910
+ }, entry.id))
911
+ });
912
+ }
913
+ function HistoryTable({ steps, now }) {
914
+ const [expanded, setExpanded] = useState(/* @__PURE__ */ new Set());
915
+ const rows = steps.map((step, i) => ({
916
+ id: `${step.ts}-${i}`,
917
+ step
918
+ })).slice(-100).reverse();
919
+ const columns = [
920
+ {
921
+ id: "age",
922
+ header: "Age",
923
+ sortable: false,
924
+ className: HistoryView_module_default.age,
925
+ render: ({ step }) => formatAge(Math.max(0, now - step.ts))
926
+ },
927
+ {
928
+ id: "step",
929
+ header: "Step",
930
+ sortable: false,
931
+ render: ({ step }) => step.step
932
+ },
933
+ {
934
+ id: "entries",
935
+ header: "Entries",
936
+ sortable: false,
937
+ render: ({ step }) => step.entries.map((e) => e.label || `#${e.id}`).join(", ") || "—"
938
+ },
939
+ {
940
+ id: "ops",
941
+ header: "Ops",
942
+ sortable: false,
943
+ className: HistoryView_module_default.figure,
944
+ render: ({ step }) => opCount(step)
945
+ },
946
+ {
947
+ id: "position",
948
+ header: "Position",
949
+ sortable: false,
950
+ className: HistoryView_module_default.figure,
951
+ render: ({ step }) => `${step.position.index} / ${step.position.total}`
952
+ }
953
+ ];
954
+ return /* @__PURE__ */ jsx(DataGrid, {
955
+ className: HistoryView_module_default.table,
956
+ rows,
957
+ columns,
958
+ empty: "No history steps yet. Edit the scene to fill the log.",
959
+ rowExpandable: ({ step }) => step.entries.length > 0,
960
+ expandedIds: expanded,
961
+ onExpandedChange: setExpanded,
962
+ onRowClick: ({ id }) => setExpanded((cur) => cur.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id])),
963
+ renderDetail: ({ step }) => /* @__PURE__ */ jsx(StepDetail, { step })
964
+ });
965
+ }
966
+ //#endregion
967
+ //#region src/views/HistoryView.tsx
968
+ /**
969
+ * The History tab: each step the scene's history took, newest first, read
970
+ * through its cursor whenever a notice reports one; where the history stands;
971
+ * and, on request, every entry on its stacks.
972
+ */
973
+ function HistoryView({ client, rootId }) {
974
+ const follower = useMemo(() => followHistory(client, rootId), [client, rootId]);
975
+ const [steps, setSteps] = useState([]);
976
+ const [position, setPosition] = useState(null);
977
+ const [checkpoints, setCheckpoints] = useState(null);
978
+ const [showStack, setShowStack] = useState(false);
979
+ const [error, setError] = useState(null);
980
+ const [now, setNow] = useState(() => Date.now());
981
+ useEffect(() => {
982
+ let live = true;
983
+ const read = () => {
984
+ follower.read().then((slice) => {
985
+ if (!live) return;
986
+ if (slice.entries.length) setSteps((prev) => [...prev, ...slice.entries].slice(-200));
987
+ setPosition(slice.position);
988
+ setCheckpoints(slice.checkpoints ?? null);
989
+ setNow(Date.now());
990
+ setError(null);
991
+ }, (err) => {
992
+ if (live) setError(err.message);
993
+ });
994
+ };
995
+ read();
996
+ const off = client.onChanged((id, areas) => {
997
+ if (id === rootId && areas.includes("history")) read();
998
+ });
999
+ return () => {
1000
+ live = false;
1001
+ off();
1002
+ };
1003
+ }, [
1004
+ client,
1005
+ rootId,
1006
+ follower,
1007
+ showStack
1008
+ ]);
1009
+ const toggleStack = (on) => {
1010
+ follower.setCheckpoints(on);
1011
+ setShowStack(on);
1012
+ };
1013
+ if (error) return /* @__PURE__ */ jsx("p", {
1014
+ className: HistoryView_module_default.note,
1015
+ children: error
1016
+ });
1017
+ return /* @__PURE__ */ jsxs("div", {
1018
+ className: HistoryView_module_default.history,
1019
+ children: [/* @__PURE__ */ jsxs("div", {
1020
+ className: HistoryView_module_default.toolbar,
1021
+ children: [
1022
+ /* @__PURE__ */ jsx(Badge, {
1023
+ size: "sm",
1024
+ variant: "subtle",
1025
+ status: position?.canUndo ? "accent" : "muted",
1026
+ children: position?.canUndo ? "can undo" : "nothing to undo"
1027
+ }),
1028
+ /* @__PURE__ */ jsx(Badge, {
1029
+ size: "sm",
1030
+ variant: "subtle",
1031
+ status: position?.canRedo ? "accent" : "muted",
1032
+ children: position?.canRedo ? "can redo" : "nothing to redo"
1033
+ }),
1034
+ /* @__PURE__ */ jsx(Switch, {
1035
+ isSelected: showStack,
1036
+ onChange: toggleStack,
1037
+ children: "checkpoints"
1038
+ }),
1039
+ /* @__PURE__ */ jsx(Button, {
1040
+ size: "sm",
1041
+ variant: "ghost",
1042
+ disabled: steps.length === 0,
1043
+ onClick: () => setSteps([]),
1044
+ children: "Clear"
1045
+ }),
1046
+ /* @__PURE__ */ jsx("span", {
1047
+ className: HistoryView_module_default.position,
1048
+ role: "status",
1049
+ "aria-label": "Position",
1050
+ title: "entries applied / entries in all",
1051
+ children: position ? `${position.index} / ${position.total}` : ""
1052
+ })
1053
+ ]
1054
+ }), /* @__PURE__ */ jsxs("div", {
1055
+ className: HistoryView_module_default.body,
1056
+ children: [showStack && checkpoints && /* @__PURE__ */ jsxs("section", {
1057
+ "aria-label": "Checkpoint stack",
1058
+ children: [/* @__PURE__ */ jsx("h3", {
1059
+ className: HistoryView_module_default.heading,
1060
+ children: "Checkpoints"
1061
+ }), checkpoints.length === 0 ? /* @__PURE__ */ jsx("p", {
1062
+ className: HistoryView_module_default.note,
1063
+ children: "The stacks are empty."
1064
+ }) : /* @__PURE__ */ jsx("ol", {
1065
+ className: HistoryView_module_default.stack,
1066
+ "aria-label": "Checkpoints",
1067
+ children: checkpoints.map((c, i) => /* @__PURE__ */ jsx("li", {
1068
+ className: position && i < position.index ? HistoryView_module_default.applied : HistoryView_module_default.redoable,
1069
+ children: c.label || c.id
1070
+ }, c.id))
1071
+ })]
1072
+ }), /* @__PURE__ */ jsx(HistoryTable, {
1073
+ steps,
1074
+ now
1075
+ })]
1076
+ })]
1077
+ });
1078
+ }
1079
+ var RegistryView_module_default = {
1080
+ split: "_split_9c8486",
1081
+ listPane: "_listPane_9c8486",
1082
+ detailPane: "_detailPane_9c8486",
1083
+ count: "_count_9c8486 _numeric_25fd0b",
1084
+ facts: "_facts_9c8486",
1085
+ detail: "_detail_9c8486",
1086
+ section: "_section_9c8486",
1087
+ title: "_title_9c8486",
1088
+ heading: "_heading_9c8486",
1089
+ codes: "_codes_9c8486",
1090
+ bindings: "_bindings_9c8486",
1091
+ binding: "_binding_9c8486",
1092
+ routes: "_routes_9c8486",
1093
+ arrow: "_arrow_9c8486",
1094
+ kinds: "_kinds_9c8486",
1095
+ note: "_note_9c8486"
1096
+ };
1097
+ //#endregion
1098
+ //#region src/views/Bindings.tsx
1099
+ var hasOpts = (b) => typeof b.opts === "object" && b.opts !== null && Object.keys(b.opts).length > 0;
1100
+ function Bindings({ bindings }) {
1101
+ return /* @__PURE__ */ jsxs("section", {
1102
+ className: RegistryView_module_default.section,
1103
+ "aria-label": "Bindings",
1104
+ children: [/* @__PURE__ */ jsxs("h3", {
1105
+ className: RegistryView_module_default.heading,
1106
+ children: ["Bindings ", /* @__PURE__ */ jsx("span", {
1107
+ className: RegistryView_module_default.count,
1108
+ children: bindings.length
1109
+ })]
1110
+ }), bindings.length === 0 ? /* @__PURE__ */ jsx("p", {
1111
+ className: RegistryView_module_default.note,
1112
+ children: "None."
1113
+ }) : /* @__PURE__ */ jsx("ul", {
1114
+ className: RegistryView_module_default.bindings,
1115
+ children: bindings.map((b, i) => /* @__PURE__ */ jsxs("li", {
1116
+ className: RegistryView_module_default.binding,
1117
+ children: [
1118
+ /* @__PURE__ */ jsx("span", {
1119
+ className: RegistryView_module_default.routes,
1120
+ children: b.routes.length > 0 ? b.routes.map((r) => /* @__PURE__ */ jsx(GestureRoute, {
1121
+ route: r,
1122
+ size: "sm"
1123
+ }, r)) : /* @__PURE__ */ jsx(ValueView, {
1124
+ value: b.spec,
1125
+ label: "Spec"
1126
+ })
1127
+ }),
1128
+ /* @__PURE__ */ jsx("span", {
1129
+ className: RegistryView_module_default.arrow,
1130
+ "aria-hidden": true,
1131
+ children: "→"
1132
+ }),
1133
+ /* @__PURE__ */ jsx(Code, {
1134
+ size: "sm",
1135
+ children: b.actionId
1136
+ }),
1137
+ hasOpts(b) && /* @__PURE__ */ jsx(ValueView, {
1138
+ value: b.opts,
1139
+ label: `Options for ${b.actionId}`
1140
+ })
1141
+ ]
1142
+ }, i))
1143
+ })]
1144
+ });
1145
+ }
1146
+ //#endregion
1147
+ //#region src/views/RegistryEntryDetail.tsx
1148
+ var yesNo$1 = (v) => v ? "yes" : "no";
1149
+ var codes = (ids) => ids.length ? /* @__PURE__ */ jsx("span", {
1150
+ className: RegistryView_module_default.codes,
1151
+ children: ids.map((id) => /* @__PURE__ */ jsx(Code, {
1152
+ size: "sm",
1153
+ children: id
1154
+ }, id))
1155
+ }) : null;
1156
+ function ToolDetail({ tool }) {
1157
+ return /* @__PURE__ */ jsxs("section", {
1158
+ className: RegistryView_module_default.detail,
1159
+ "aria-label": `Tool ${tool.id}`,
1160
+ children: [
1161
+ /* @__PURE__ */ jsx("h3", {
1162
+ className: RegistryView_module_default.title,
1163
+ children: /* @__PURE__ */ jsx(Code, {
1164
+ size: "sm",
1165
+ children: tool.id
1166
+ })
1167
+ }),
1168
+ /* @__PURE__ */ jsxs(DetailList, {
1169
+ layout: "inline",
1170
+ children: [
1171
+ /* @__PURE__ */ jsx(DetailRow, {
1172
+ label: "Slot",
1173
+ children: tool.slot
1174
+ }),
1175
+ /* @__PURE__ */ jsx(DetailRow, {
1176
+ label: "Label",
1177
+ children: tool.label
1178
+ }),
1179
+ /* @__PURE__ */ jsx(DetailRow, {
1180
+ label: "Group",
1181
+ children: tool.group
1182
+ }),
1183
+ /* @__PURE__ */ jsx(DetailRow, {
1184
+ label: "Icon",
1185
+ children: tool.icon
1186
+ }),
1187
+ /* @__PURE__ */ jsx(DetailRow, {
1188
+ label: "Shortcut",
1189
+ children: tool.shortcut
1190
+ }),
1191
+ /* @__PURE__ */ jsx(DetailRow, {
1192
+ label: "Hotkey",
1193
+ children: tool.hotkey
1194
+ }),
1195
+ /* @__PURE__ */ jsx(DetailRow, {
1196
+ label: "Hidden",
1197
+ children: yesNo$1(tool.hidden)
1198
+ }),
1199
+ /* @__PURE__ */ jsx(DetailRow, {
1200
+ label: "Overlay",
1201
+ children: yesNo$1(tool.hasOverlay)
1202
+ }),
1203
+ /* @__PURE__ */ jsx(DetailRow, {
1204
+ label: "Actions",
1205
+ children: codes(tool.actions)
1206
+ }),
1207
+ /* @__PURE__ */ jsx(DetailRow, {
1208
+ label: "Deps",
1209
+ children: codes(tool.deps)
1210
+ })
1211
+ ]
1212
+ }),
1213
+ /* @__PURE__ */ jsx(Bindings, { bindings: tool.bindings }),
1214
+ /* @__PURE__ */ jsx("h3", {
1215
+ className: RegistryView_module_default.heading,
1216
+ children: "Cursor"
1217
+ }),
1218
+ /* @__PURE__ */ jsx(ValueView, {
1219
+ value: tool.cursor,
1220
+ label: "Cursor"
1221
+ }),
1222
+ /* @__PURE__ */ jsx("h3", {
1223
+ className: RegistryView_module_default.heading,
1224
+ children: "Eligibility"
1225
+ }),
1226
+ /* @__PURE__ */ jsx(ValueView, {
1227
+ value: tool.eligibility,
1228
+ label: "Eligibility"
1229
+ })
1230
+ ]
1231
+ });
1232
+ }
1233
+ function ActionDetail({ action }) {
1234
+ return /* @__PURE__ */ jsxs("section", {
1235
+ className: RegistryView_module_default.detail,
1236
+ "aria-label": `Action ${action.id}`,
1237
+ children: [
1238
+ /* @__PURE__ */ jsx("h3", {
1239
+ className: RegistryView_module_default.title,
1240
+ children: /* @__PURE__ */ jsx(Code, {
1241
+ size: "sm",
1242
+ children: action.id
1243
+ })
1244
+ }),
1245
+ /* @__PURE__ */ jsxs(DetailList, {
1246
+ layout: "inline",
1247
+ children: [
1248
+ /* @__PURE__ */ jsx(DetailRow, {
1249
+ label: "Label",
1250
+ children: action.label
1251
+ }),
1252
+ /* @__PURE__ */ jsx(DetailRow, {
1253
+ label: "Enabled",
1254
+ status: action.enabled === true ? "success" : "warn",
1255
+ children: action.enabled === true ? "yes" : action.enabled
1256
+ }),
1257
+ /* @__PURE__ */ jsx(DetailRow, {
1258
+ label: "Active",
1259
+ children: action.active === null ? "no pressed state" : yesNo$1(action.active)
1260
+ }),
1261
+ /* @__PURE__ */ jsx(DetailRow, {
1262
+ label: "Scope",
1263
+ children: action.scope
1264
+ }),
1265
+ /* @__PURE__ */ jsx(DetailRow, {
1266
+ label: "Invoker",
1267
+ children: action.invoker
1268
+ }),
1269
+ /* @__PURE__ */ jsx(DetailRow, {
1270
+ label: "Group",
1271
+ children: action.group
1272
+ }),
1273
+ /* @__PURE__ */ jsx(DetailRow, {
1274
+ label: "Tags",
1275
+ children: codes(action.tags)
1276
+ }),
1277
+ /* @__PURE__ */ jsx(DetailRow, {
1278
+ label: "Shortcut",
1279
+ children: action.shortcut
1280
+ }),
1281
+ /* @__PURE__ */ jsx(DetailRow, {
1282
+ label: "Requires",
1283
+ children: codes(action.requires)
1284
+ }),
1285
+ /* @__PURE__ */ jsx(DetailRow, {
1286
+ label: "Preview",
1287
+ children: yesNo$1(action.hasPreview)
1288
+ }),
1289
+ /* @__PURE__ */ jsx(DetailRow, {
1290
+ label: "Variants",
1291
+ children: codes(action.variants.map((v) => v.key))
1292
+ })
1293
+ ]
1294
+ }),
1295
+ /* @__PURE__ */ jsx(Bindings, { bindings: action.bindings }),
1296
+ /* @__PURE__ */ jsx("h3", {
1297
+ className: RegistryView_module_default.heading,
1298
+ children: "Eligible"
1299
+ }),
1300
+ action.eligible === null ? /* @__PURE__ */ jsx("p", {
1301
+ className: RegistryView_module_default.note,
1302
+ children: "Always."
1303
+ }) : /* @__PURE__ */ jsx(ValueView, {
1304
+ value: action.eligible,
1305
+ label: "Eligible"
1306
+ })
1307
+ ]
1308
+ });
1309
+ }
1310
+ function DepDetail({ dep, users }) {
1311
+ return /* @__PURE__ */ jsxs("section", {
1312
+ className: RegistryView_module_default.detail,
1313
+ "aria-label": `Dep ${dep.name}`,
1314
+ children: [/* @__PURE__ */ jsx("h3", {
1315
+ className: RegistryView_module_default.title,
1316
+ children: /* @__PURE__ */ jsx(Code, {
1317
+ size: "sm",
1318
+ children: dep.name
1319
+ })
1320
+ }), /* @__PURE__ */ jsxs(DetailList, {
1321
+ layout: "inline",
1322
+ children: [
1323
+ /* @__PURE__ */ jsx(DetailRow, {
1324
+ label: "Scope",
1325
+ children: dep.depth === 0 ? "this canvas" : `${dep.depth} above`
1326
+ }),
1327
+ /* @__PURE__ */ jsx(DetailRow, {
1328
+ label: "Stacked",
1329
+ children: dep.stacked
1330
+ }),
1331
+ /* @__PURE__ */ jsx(DetailRow, {
1332
+ label: "Required by",
1333
+ children: codes(users)
1334
+ })
1335
+ ]
1336
+ })]
1337
+ });
1338
+ }
1339
+ function RegistryEntryDetail({ registry, pick }) {
1340
+ if (pick.category === "tools") {
1341
+ const tool = registry.tools.find((t) => t.id === pick.id);
1342
+ if (tool) return /* @__PURE__ */ jsx(ToolDetail, { tool });
1343
+ } else if (pick.category === "actions") {
1344
+ const action = registry.actions.find((a) => a.id === pick.id);
1345
+ if (action) return /* @__PURE__ */ jsx(ActionDetail, { action });
1346
+ } else {
1347
+ const dep = registry.deps.find((d) => d.name === pick.id);
1348
+ const users = registry.actions.filter((a) => a.requires.includes(pick.id)).map((a) => a.id);
1349
+ if (dep) return /* @__PURE__ */ jsx(DepDetail, {
1350
+ dep,
1351
+ users
1352
+ });
1353
+ }
1354
+ return /* @__PURE__ */ jsxs("p", {
1355
+ className: RegistryView_module_default.note,
1356
+ children: [pick.id, " is no longer registered."]
1357
+ });
1358
+ }
1359
+ //#endregion
1360
+ //#region src/views/RegistryView.tsx
1361
+ /**
1362
+ * The Registry tab: what the canvas has registered, filtered by name, beside
1363
+ * the entry picked. Re-read when the registry changes, and on selection and
1364
+ * scene changes too, since an action's `enabled` and `active` move with them
1365
+ * and no notice reports that.
1366
+ */
1367
+ var CATEGORIES = [
1368
+ "tools",
1369
+ "actions",
1370
+ "deps"
1371
+ ];
1372
+ var TITLES = {
1373
+ tools: "Tools",
1374
+ actions: "Actions",
1375
+ deps: "Deps"
1376
+ };
1377
+ function rowsOf(reg, category) {
1378
+ switch (category) {
1379
+ case "tools": return reg.tools.map((t) => ({
1380
+ id: t.id,
1381
+ facts: t.slot
1382
+ }));
1383
+ case "actions": return reg.actions.map((a) => ({
1384
+ id: a.id,
1385
+ facts: [
1386
+ a.scope,
1387
+ a.enabled === true ? "" : "disabled",
1388
+ a.active ? "active" : ""
1389
+ ].filter(Boolean).join(" ")
1390
+ }));
1391
+ case "deps": return reg.deps.map((d) => ({
1392
+ id: d.name,
1393
+ facts: d.depth === 0 ? "" : `+${d.depth}`
1394
+ }));
1395
+ }
1396
+ }
1397
+ function nodesOf(reg, filter) {
1398
+ const q = filter.trim().toLowerCase();
1399
+ return CATEGORIES.map((category) => {
1400
+ const rows = rowsOf(reg, category).filter((r) => !q || r.id.toLowerCase().includes(q));
1401
+ return {
1402
+ id: `cat:${category}`,
1403
+ label: TITLES[category],
1404
+ trailing: /* @__PURE__ */ jsx("span", {
1405
+ className: RegistryView_module_default.count,
1406
+ children: rows.length
1407
+ }),
1408
+ children: rows.map((r) => ({
1409
+ id: `${category}:${r.id}`,
1410
+ textValue: r.id,
1411
+ label: r.id,
1412
+ ...r.facts ? { trailing: /* @__PURE__ */ jsx("span", {
1413
+ className: RegistryView_module_default.facts,
1414
+ children: r.facts
1415
+ }) } : {}
1416
+ }))
1417
+ };
1418
+ });
1419
+ }
1420
+ function parsePick(key) {
1421
+ if (!key || key.startsWith("cat:")) return null;
1422
+ const at = key.indexOf(":");
1423
+ return {
1424
+ category: key.slice(0, at),
1425
+ id: key.slice(at + 1)
1426
+ };
1427
+ }
1428
+ function RegistryView({ client, rootId }) {
1429
+ const registry = useRootQuery(client, rootId, [
1430
+ "registry",
1431
+ "selection",
1432
+ "scene"
1433
+ ], () => client.request(rootId, { type: "get-registry" }));
1434
+ const [filter, setFilter] = useState("");
1435
+ const [picked, setPicked] = useState(null);
1436
+ const [expanded, setExpanded] = useState(() => new Set(CATEGORIES.map((c) => `cat:${c}`)));
1437
+ if (registry.error) return /* @__PURE__ */ jsx("p", {
1438
+ className: RegistryView_module_default.note,
1439
+ children: registry.error
1440
+ });
1441
+ const reg = registry.data;
1442
+ if (!reg) return /* @__PURE__ */ jsx("p", {
1443
+ className: RegistryView_module_default.note,
1444
+ children: "Loading…"
1445
+ });
1446
+ const pick = parsePick(picked);
1447
+ return /* @__PURE__ */ jsxs("div", {
1448
+ className: RegistryView_module_default.split,
1449
+ children: [/* @__PURE__ */ jsxs("div", {
1450
+ className: RegistryView_module_default.listPane,
1451
+ "data-pane": "list",
1452
+ children: [/* @__PURE__ */ jsx(Input, {
1453
+ "aria-label": "Filter",
1454
+ placeholder: "Filter by name",
1455
+ value: filter,
1456
+ onChange: setFilter
1457
+ }), /* @__PURE__ */ jsx(Tree, {
1458
+ "aria-label": "Registry entries",
1459
+ nodes: nodesOf(reg, filter),
1460
+ expandedIds: expanded,
1461
+ onExpandedChange: setExpanded,
1462
+ selectionMode: "single",
1463
+ selectedIds: picked ? [picked] : [],
1464
+ onSelectionChange: (ids) => setPicked(ids.values().next().value ?? null)
1465
+ })]
1466
+ }), /* @__PURE__ */ jsxs("div", {
1467
+ className: RegistryView_module_default.detailPane,
1468
+ "data-pane": "detail",
1469
+ children: [pick ? /* @__PURE__ */ jsx(RegistryEntryDetail, {
1470
+ registry: reg,
1471
+ pick
1472
+ }) : /* @__PURE__ */ jsx("p", {
1473
+ className: RegistryView_module_default.note,
1474
+ children: "Pick an entry to see its bindings."
1475
+ }), reg.routingKinds.length > 0 && /* @__PURE__ */ jsxs("p", {
1476
+ className: RegistryView_module_default.kinds,
1477
+ children: ["Routing kinds: ", reg.routingKinds.join(", ")]
1478
+ })]
1479
+ })]
1480
+ });
1481
+ }
1482
+ var SceneView_module_default = {
1483
+ split: "_split_9a17db",
1484
+ treePane: "_treePane_9a17db",
1485
+ detailPane: "_detailPane_9a17db",
1486
+ facts: "_facts_9a17db",
1487
+ kind: "_kind_9a17db",
1488
+ layer: "_layer_9a17db",
1489
+ count: "_count_9a17db _numeric_25fd0b",
1490
+ flag: "_flag_9a17db",
1491
+ detail: "_detail_9a17db",
1492
+ detailHead: "_detailHead_9a17db",
1493
+ heading: "_heading_9a17db",
1494
+ note: "_note_9a17db"
1495
+ };
1496
+ //#endregion
1497
+ //#region src/views/NodeDetail.tsx
1498
+ var yesNo = (v) => v ? "yes" : "no";
1499
+ function NodeDetail({ client, rootId, nodeId, onPick }) {
1500
+ const node = useRootQuery(client, rootId, ["scene"], () => client.request(rootId, {
1501
+ type: "get-node",
1502
+ nodeId
1503
+ }));
1504
+ if (node.error) return /* @__PURE__ */ jsx("p", {
1505
+ className: SceneView_module_default.note,
1506
+ children: node.error
1507
+ });
1508
+ const d = node.data;
1509
+ if (!d) return /* @__PURE__ */ jsx("p", {
1510
+ className: SceneView_module_default.note,
1511
+ children: "Loading…"
1512
+ });
1513
+ return /* @__PURE__ */ jsxs("div", {
1514
+ className: SceneView_module_default.detail,
1515
+ children: [
1516
+ /* @__PURE__ */ jsxs("div", {
1517
+ className: SceneView_module_default.detailHead,
1518
+ children: [/* @__PURE__ */ jsx(Code, {
1519
+ size: "sm",
1520
+ children: d.id
1521
+ }), /* @__PURE__ */ jsx(Button, {
1522
+ size: "sm",
1523
+ variant: "secondary",
1524
+ onClick: () => {
1525
+ client.request(rootId, {
1526
+ type: "select",
1527
+ ids: [d.id]
1528
+ }).catch(() => {});
1529
+ },
1530
+ children: "Select on canvas"
1531
+ })]
1532
+ }),
1533
+ /* @__PURE__ */ jsxs(DetailList, {
1534
+ layout: "inline",
1535
+ children: [
1536
+ /* @__PURE__ */ jsx(DetailRow, {
1537
+ label: "Kind",
1538
+ children: d.kind
1539
+ }),
1540
+ /* @__PURE__ */ jsx(DetailRow, {
1541
+ label: "Layer",
1542
+ children: d.layer
1543
+ }),
1544
+ /* @__PURE__ */ jsx(DetailRow, {
1545
+ label: "Parent",
1546
+ children: d.parent && /* @__PURE__ */ jsx(Button, {
1547
+ size: "sm",
1548
+ variant: "link",
1549
+ onClick: () => onPick(d.parent),
1550
+ ariaLabel: `Show parent ${d.parent}`,
1551
+ children: d.parent
1552
+ })
1553
+ }),
1554
+ /* @__PURE__ */ jsx(DetailRow, {
1555
+ label: "Children",
1556
+ children: d.childCount
1557
+ }),
1558
+ /* @__PURE__ */ jsx(DetailRow, {
1559
+ label: "Hidden",
1560
+ children: yesNo(d.hidden)
1561
+ }),
1562
+ /* @__PURE__ */ jsx(DetailRow, {
1563
+ label: "Locked",
1564
+ children: yesNo(d.locked)
1565
+ }),
1566
+ /* @__PURE__ */ jsx(DetailRow, {
1567
+ label: "Pickable",
1568
+ children: yesNo(d.pickable)
1569
+ })
1570
+ ]
1571
+ }),
1572
+ /* @__PURE__ */ jsx("h3", {
1573
+ className: SceneView_module_default.heading,
1574
+ children: "Pose"
1575
+ }),
1576
+ /* @__PURE__ */ jsx(ValueView, {
1577
+ value: d.pose,
1578
+ label: "Pose"
1579
+ }),
1580
+ /* @__PURE__ */ jsx("h3", {
1581
+ className: SceneView_module_default.heading,
1582
+ children: "Data"
1583
+ }),
1584
+ /* @__PURE__ */ jsx(ValueView, {
1585
+ value: d.data,
1586
+ label: "Data"
1587
+ })
1588
+ ]
1589
+ });
1590
+ }
1591
+ //#endregion
1592
+ //#region src/views/useSceneTree.ts
1593
+ /** A root's scene tree, loaded a level at a time as branches open, and reloaded where it is open when the scene changes. */
1594
+ /** The key the top level is held under. */
1595
+ var TOP = "";
1596
+ function useSceneTree(client, rootId) {
1597
+ const [levels, setLevels] = useState(/* @__PURE__ */ new Map());
1598
+ const [expanded, setExpandedState] = useState(/* @__PURE__ */ new Set());
1599
+ const [error, setError] = useState(null);
1600
+ const expandedRef = useLatest(expanded);
1601
+ const alive = useRef(true);
1602
+ useEffect(() => {
1603
+ alive.current = true;
1604
+ return () => {
1605
+ alive.current = false;
1606
+ };
1607
+ }, []);
1608
+ const load = useCallback((parent) => {
1609
+ const body = parent === TOP ? { type: "get-children" } : {
1610
+ type: "get-children",
1611
+ nodeId: parent
1612
+ };
1613
+ client.request(rootId, body).then((rows) => {
1614
+ if (!alive.current) return;
1615
+ setLevels((prev) => new Map(prev).set(parent, rows));
1616
+ if (parent === TOP) setError(null);
1617
+ }, (err) => {
1618
+ if (!alive.current) return;
1619
+ if (parent === TOP) setError(err.message);
1620
+ setLevels((prev) => {
1621
+ const next = new Map(prev);
1622
+ next.delete(parent);
1623
+ return next;
1624
+ });
1625
+ });
1626
+ }, [client, rootId]);
1627
+ useEffect(() => {
1628
+ load(TOP);
1629
+ return client.onChanged((id, areas) => {
1630
+ if (id !== rootId || !areas.includes("scene")) return;
1631
+ load(TOP);
1632
+ for (const open of expandedRef.current) load(open);
1633
+ });
1634
+ }, [
1635
+ client,
1636
+ rootId,
1637
+ load,
1638
+ expandedRef
1639
+ ]);
1640
+ return {
1641
+ levels,
1642
+ expanded,
1643
+ setExpanded: useCallback((ids) => {
1644
+ for (const id of ids) if (!expandedRef.current.has(id)) load(id);
1645
+ setExpandedState(ids);
1646
+ }, [load, expandedRef]),
1647
+ error
1648
+ };
1649
+ }
1650
+ //#endregion
1651
+ //#region src/views/SceneView.tsx
1652
+ /** The Scene tab: a root's node tree beside the node picked in it. */
1653
+ function flags$1(row) {
1654
+ return [
1655
+ row.hidden && "hidden",
1656
+ row.locked && "locked",
1657
+ !row.pickable && "unpickable"
1658
+ ].filter((f) => typeof f === "string");
1659
+ }
1660
+ function RowFacts({ row }) {
1661
+ return /* @__PURE__ */ jsxs("span", {
1662
+ className: SceneView_module_default.facts,
1663
+ children: [
1664
+ /* @__PURE__ */ jsx("span", {
1665
+ className: SceneView_module_default.kind,
1666
+ children: row.kind
1667
+ }),
1668
+ /* @__PURE__ */ jsx("span", {
1669
+ className: SceneView_module_default.layer,
1670
+ children: row.layer
1671
+ }),
1672
+ row.kind === "container" && /* @__PURE__ */ jsx("span", {
1673
+ className: SceneView_module_default.count,
1674
+ children: row.childCount
1675
+ }),
1676
+ flags$1(row).map((f) => /* @__PURE__ */ jsx("span", {
1677
+ className: SceneView_module_default.flag,
1678
+ children: f
1679
+ }, f))
1680
+ ]
1681
+ });
1682
+ }
1683
+ function toNodes(levels, parent) {
1684
+ return (levels.get(parent) ?? []).map((row) => ({
1685
+ id: row.id,
1686
+ textValue: row.id,
1687
+ label: /* @__PURE__ */ jsx("span", {
1688
+ "data-node-id": row.id,
1689
+ children: row.id
1690
+ }),
1691
+ trailing: /* @__PURE__ */ jsx(RowFacts, { row }),
1692
+ muted: row.hidden,
1693
+ ...row.kind === "container" && row.childCount > 0 ? { children: toNodes(levels, row.id) } : {}
1694
+ }));
1695
+ }
1696
+ function SceneView({ client, rootId }) {
1697
+ const tree = useSceneTree(client, rootId);
1698
+ const [selected, setSelected] = useState(null);
1699
+ const hovered = useRef(null);
1700
+ const highlight = (id) => {
1701
+ if (hovered.current === id) return;
1702
+ hovered.current = id;
1703
+ client.request(rootId, {
1704
+ type: "highlight",
1705
+ ids: id ? [id] : []
1706
+ }).catch(() => {});
1707
+ };
1708
+ const onPointerOver = (e) => {
1709
+ const row = e.target.closest("[role=\"treeitem\"]");
1710
+ highlight(row?.querySelector("[data-node-id]")?.getAttribute("data-node-id") ?? null);
1711
+ };
1712
+ return /* @__PURE__ */ jsxs("div", {
1713
+ className: SceneView_module_default.split,
1714
+ children: [/* @__PURE__ */ jsx("div", {
1715
+ className: SceneView_module_default.treePane,
1716
+ "data-pane": "tree",
1717
+ onPointerOver,
1718
+ onPointerLeave: () => highlight(null),
1719
+ children: /* @__PURE__ */ jsx(Tree, {
1720
+ "aria-label": "Scene nodes",
1721
+ nodes: toNodes(tree.levels, ""),
1722
+ empty: /* @__PURE__ */ jsx("p", {
1723
+ className: SceneView_module_default.note,
1724
+ children: tree.error ?? "The scene is empty."
1725
+ }),
1726
+ expandedIds: tree.expanded,
1727
+ onExpandedChange: tree.setExpanded,
1728
+ selectionMode: "single",
1729
+ selectedIds: selected ? [selected] : [],
1730
+ onSelectionChange: (ids) => setSelected(ids.values().next().value ?? null)
1731
+ })
1732
+ }), /* @__PURE__ */ jsx("div", {
1733
+ className: SceneView_module_default.detailPane,
1734
+ "data-pane": "detail",
1735
+ children: selected ? /* @__PURE__ */ jsx(NodeDetail, {
1736
+ client,
1737
+ rootId,
1738
+ nodeId: selected,
1739
+ onPick: setSelected
1740
+ }, selected) : /* @__PURE__ */ jsx("p", {
1741
+ className: SceneView_module_default.note,
1742
+ children: "Pick a node to see its pose and data."
1743
+ })
1744
+ })]
1745
+ });
1746
+ }
1747
+ var StateView_module_default = {
1748
+ state: "_state_5edec5",
1749
+ section: "_section_5edec5",
1750
+ heading: "_heading_5edec5",
1751
+ count: "_count_5edec5 _numeric_25fd0b",
1752
+ ids: "_ids_5edec5",
1753
+ flags: "_flags_5edec5",
1754
+ probe: "_probe_5edec5",
1755
+ note: "_note_5edec5"
1756
+ };
1757
+ //#endregion
1758
+ //#region src/views/StateView.tsx
1759
+ /** The tool picker's value for "no tool": a select key cannot be null. */
1760
+ var NO_TOOL = "\0none";
1761
+ function StateView({ client, rootId }) {
1762
+ const state = useRootQuery(client, rootId, [
1763
+ "selection",
1764
+ "view",
1765
+ "tool"
1766
+ ], () => client.request(rootId, { type: "get-state" }));
1767
+ if (state.error) return /* @__PURE__ */ jsx("p", {
1768
+ className: StateView_module_default.note,
1769
+ children: state.error
1770
+ });
1771
+ const st = state.data;
1772
+ if (!st) return /* @__PURE__ */ jsx("p", {
1773
+ className: StateView_module_default.note,
1774
+ children: "Loading…"
1775
+ });
1776
+ const steer = (p) => {
1777
+ p.then(state.refresh, state.refresh);
1778
+ };
1779
+ const probes = Object.entries(st.probes);
1780
+ return /* @__PURE__ */ jsxs("div", {
1781
+ className: StateView_module_default.state,
1782
+ children: [
1783
+ /* @__PURE__ */ jsxs("section", {
1784
+ className: StateView_module_default.section,
1785
+ "aria-label": "Selection",
1786
+ children: [/* @__PURE__ */ jsxs("h3", {
1787
+ className: StateView_module_default.heading,
1788
+ children: ["Selection ", /* @__PURE__ */ jsx("span", {
1789
+ className: StateView_module_default.count,
1790
+ children: st.selection.length
1791
+ })]
1792
+ }), st.selection.length === 0 ? /* @__PURE__ */ jsx("p", {
1793
+ className: StateView_module_default.note,
1794
+ children: "Nothing selected."
1795
+ }) : /* @__PURE__ */ jsx("div", {
1796
+ className: StateView_module_default.ids,
1797
+ children: st.selection.map((id) => /* @__PURE__ */ jsx(Code, {
1798
+ size: "sm",
1799
+ children: id
1800
+ }, id))
1801
+ })]
1802
+ }),
1803
+ /* @__PURE__ */ jsxs("section", {
1804
+ className: StateView_module_default.section,
1805
+ "aria-label": "Tool",
1806
+ children: [/* @__PURE__ */ jsx("h3", {
1807
+ className: StateView_module_default.heading,
1808
+ children: "Tool"
1809
+ }), /* @__PURE__ */ jsx(Select, {
1810
+ "aria-label": "Active tool",
1811
+ width: "fit",
1812
+ selectedKey: st.activeTool ?? NO_TOOL,
1813
+ onSelectionChange: (key) => {
1814
+ steer(client.request(rootId, {
1815
+ type: "set-tool",
1816
+ toolId: key === NO_TOOL ? null : key
1817
+ }));
1818
+ },
1819
+ options: [{
1820
+ value: NO_TOOL,
1821
+ label: "none"
1822
+ }, ...st.tools.map((t) => ({
1823
+ value: t,
1824
+ label: t
1825
+ }))]
1826
+ })]
1827
+ }),
1828
+ /* @__PURE__ */ jsxs("section", {
1829
+ className: StateView_module_default.section,
1830
+ "aria-label": "Overlays",
1831
+ children: [/* @__PURE__ */ jsx("h3", {
1832
+ className: StateView_module_default.heading,
1833
+ children: "Overlays"
1834
+ }), /* @__PURE__ */ jsx("div", {
1835
+ className: StateView_module_default.flags,
1836
+ children: DEBUG_FEATURES.map((flag) => /* @__PURE__ */ jsx(Switch, {
1837
+ isSelected: st.overlays[flag] === true,
1838
+ onChange: (on) => steer(client.request(rootId, {
1839
+ type: "set-overlays",
1840
+ overlays: { [flag]: on }
1841
+ })),
1842
+ children: flag
1843
+ }, flag))
1844
+ })]
1845
+ }),
1846
+ /* @__PURE__ */ jsxs("section", {
1847
+ className: StateView_module_default.section,
1848
+ "aria-label": "View",
1849
+ children: [/* @__PURE__ */ jsx("h3", {
1850
+ className: StateView_module_default.heading,
1851
+ children: "View"
1852
+ }), /* @__PURE__ */ jsx(ValueView, {
1853
+ value: st.view,
1854
+ label: "View"
1855
+ })]
1856
+ }),
1857
+ /* @__PURE__ */ jsxs("section", {
1858
+ className: StateView_module_default.section,
1859
+ "aria-label": "Probes",
1860
+ children: [/* @__PURE__ */ jsx("h3", {
1861
+ className: StateView_module_default.heading,
1862
+ children: "Probes"
1863
+ }), probes.length === 0 ? /* @__PURE__ */ jsx("p", {
1864
+ className: StateView_module_default.note,
1865
+ children: "No probes registered."
1866
+ }) : probes.map(([name, value]) => /* @__PURE__ */ jsxs("div", {
1867
+ className: StateView_module_default.probe,
1868
+ children: [/* @__PURE__ */ jsx(Code, {
1869
+ size: "sm",
1870
+ children: name
1871
+ }), /* @__PURE__ */ jsx(ValueView, {
1872
+ value,
1873
+ label: name
1874
+ })]
1875
+ }, name))]
1876
+ })
1877
+ ]
1878
+ });
1879
+ }
1880
+ //#endregion
1881
+ //#region src/tabs.ts
1882
+ var DEVTOOLS_TABS = [
1883
+ {
1884
+ id: "scene",
1885
+ label: "Scene",
1886
+ View: SceneView
1887
+ },
1888
+ {
1889
+ id: "state",
1890
+ label: "State",
1891
+ View: StateView
1892
+ },
1893
+ {
1894
+ id: "registry",
1895
+ label: "Registry",
1896
+ View: RegistryView
1897
+ },
1898
+ {
1899
+ id: "dispatch",
1900
+ label: "Dispatch",
1901
+ View: DispatchView
1902
+ },
1903
+ {
1904
+ id: "history",
1905
+ label: "History",
1906
+ View: HistoryView
1907
+ },
1908
+ {
1909
+ id: "frame",
1910
+ label: "Frame",
1911
+ View: FrameView
1912
+ }
1913
+ ];
1914
+ //#endregion
1915
+ //#region src/DevtoolsPanel.tsx
1916
+ /**
1917
+ * Reads and steers the canvases of the page `transport` reaches: pick a canvas,
1918
+ * then walk its scene, read its state, follow its dispatch trace, or watch its
1919
+ * frame stats. It fills its container; give that a size.
1920
+ */
1921
+ function DevtoolsPanel({ transport, tabs, defaultTab, className }) {
1922
+ const { client, state } = useDevtoolsClient(transport);
1923
+ const shown = tabs ? DEVTOOLS_TABS.filter((t) => tabs.includes(t.id)) : DEVTOOLS_TABS;
1924
+ const [tab, setTab] = useState(defaultTab ?? shown[0]?.id ?? "scene");
1925
+ const [picked, setPicked] = useState(null);
1926
+ const rootId = state.roots.some((r) => r.id === picked) ? picked : state.roots[0]?.id ?? null;
1927
+ return /* @__PURE__ */ jsxs("section", {
1928
+ className: [DevtoolsPanel_module_default.panel, className].filter(Boolean).join(" "),
1929
+ "aria-label": "Devtools",
1930
+ children: [/* @__PURE__ */ jsxs("div", {
1931
+ className: DevtoolsPanel_module_default.bar,
1932
+ children: [/* @__PURE__ */ jsx(RootPicker, {
1933
+ roots: state.roots,
1934
+ value: rootId,
1935
+ onChange: setPicked
1936
+ }), /* @__PURE__ */ jsx("span", {
1937
+ className: DevtoolsPanel_module_default.status,
1938
+ children: statusText(state.status, state.kitVersion, state.error)
1939
+ })]
1940
+ }), rootId && shown.length === 1 ? /* @__PURE__ */ jsx("div", {
1941
+ className: DevtoolsPanel_module_default.tabPanel,
1942
+ children: renderView(shown[0].View, client, rootId)
1943
+ }) : rootId ? /* @__PURE__ */ jsxs(Tabs, {
1944
+ className: DevtoolsPanel_module_default.tabs,
1945
+ selectedKey: tab,
1946
+ onSelectionChange: (k) => setTab(k),
1947
+ children: [/* @__PURE__ */ jsx(TabList, {
1948
+ "aria-label": "Devtools areas",
1949
+ className: DevtoolsPanel_module_default.tabList,
1950
+ children: shown.map((t) => /* @__PURE__ */ jsx(Tab, {
1951
+ id: t.id,
1952
+ children: t.label
1953
+ }, t.id))
1954
+ }), shown.map(({ id, View }) => /* @__PURE__ */ jsx(TabPanel, {
1955
+ id,
1956
+ className: DevtoolsPanel_module_default.tabPanel,
1957
+ children: renderView(View, client, rootId)
1958
+ }, id))]
1959
+ }) : /* @__PURE__ */ jsx("p", {
1960
+ className: DevtoolsPanel_module_default.empty,
1961
+ children: state.status === "connected" ? "No canvas on this page." : "Connecting…"
1962
+ })]
1963
+ });
1964
+ }
1965
+ /** A view keyed by its root, so picking another canvas starts it fresh. */
1966
+ function renderView(View, client, rootId) {
1967
+ return /* @__PURE__ */ jsx(View, {
1968
+ client,
1969
+ rootId
1970
+ }, rootId);
1971
+ }
1972
+ function statusText(status, kitVersion, error) {
1973
+ if (status === "error") return error ?? "error";
1974
+ if (status === "connected") return `weasel ${kitVersion ?? ""}`;
1975
+ return status;
1976
+ }
1977
+ //#endregion
1978
+ //#region src/inPageTransport.ts
1979
+ /**
1980
+ * A panel's end of a connection to this page's own hook. Each `yo` opens a
1981
+ * fresh session and each `bye` ends it, the way a `yo` posted to the window
1982
+ * does, so a panel that unmounts and mounts again reconnects. Installs the
1983
+ * hook when the page has none.
1984
+ */
1985
+ function createInPagePanelTransport(hook = installDevtoolsHook()) {
1986
+ const { page, panel } = createInPageTransports();
1987
+ let close = null;
1988
+ return {
1989
+ send(message) {
1990
+ if (message.type === "yo" && hook) {
1991
+ close?.();
1992
+ close = hook.connect(page);
1993
+ }
1994
+ panel.send(message);
1995
+ if (message.type === "bye") {
1996
+ close?.();
1997
+ close = null;
1998
+ }
1999
+ },
2000
+ onMessage: (listener) => panel.onMessage(listener)
2001
+ };
2002
+ }
2003
+ //#endregion
2004
+ export { DevtoolsPanel, connectDevtools, createDevtoolsClient, createInPagePanelTransport, followHistory, followTrace };
2005
+
2006
+ //# sourceMappingURL=index.js.map