uidex 0.5.2 → 0.7.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.
Files changed (39) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/cli.cjs +1542 -1227
  3. package/dist/cli/cli.cjs.map +1 -1
  4. package/dist/cloud/index.cjs +385 -175
  5. package/dist/cloud/index.cjs.map +1 -1
  6. package/dist/cloud/index.d.cts +192 -4
  7. package/dist/cloud/index.d.ts +192 -4
  8. package/dist/cloud/index.js +377 -177
  9. package/dist/cloud/index.js.map +1 -1
  10. package/dist/headless/index.cjs +116 -251
  11. package/dist/headless/index.cjs.map +1 -1
  12. package/dist/headless/index.d.cts +6 -11
  13. package/dist/headless/index.d.ts +6 -11
  14. package/dist/headless/index.js +116 -253
  15. package/dist/headless/index.js.map +1 -1
  16. package/dist/index.cjs +776 -1055
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +152 -160
  19. package/dist/index.d.ts +152 -160
  20. package/dist/index.js +792 -1066
  21. package/dist/index.js.map +1 -1
  22. package/dist/react/index.cjs +801 -1019
  23. package/dist/react/index.cjs.map +1 -1
  24. package/dist/react/index.d.cts +102 -86
  25. package/dist/react/index.d.ts +102 -86
  26. package/dist/react/index.js +821 -1038
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/scan/index.cjs +1550 -1220
  29. package/dist/scan/index.cjs.map +1 -1
  30. package/dist/scan/index.d.cts +210 -12
  31. package/dist/scan/index.d.ts +210 -12
  32. package/dist/scan/index.js +1547 -1219
  33. package/dist/scan/index.js.map +1 -1
  34. package/package.json +22 -21
  35. package/templates/claude/SKILL.md +71 -0
  36. package/templates/claude/references/audit.md +43 -0
  37. package/templates/claude/{rules.md → references/conventions.md} +25 -28
  38. package/templates/claude/audit.md +0 -43
  39. /package/templates/claude/{api.md → references/api.md} +0 -0
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ function freezeEntity(entity, flows) {
67
67
  function createRegistry() {
68
68
  const store = emptyStore();
69
69
  let flowsCache = null;
70
+ const patternCache = /* @__PURE__ */ new Map();
70
71
  const getFlows = () => {
71
72
  if (flowsCache === null) flowsCache = Array.from(store.flow.values());
72
73
  return flowsCache;
@@ -76,6 +77,7 @@ function createRegistry() {
76
77
  const key = entityKey(entity);
77
78
  store[entity.kind].set(key, entity);
78
79
  flowsCache = null;
80
+ patternCache.delete(entity.kind);
79
81
  };
80
82
  const get = (kind, id) => {
81
83
  assertEntityKind(kind);
@@ -83,6 +85,51 @@ function createRegistry() {
83
85
  if (raw === void 0) return void 0;
84
86
  return freezeEntity(raw, getFlows());
85
87
  };
88
+ const getPatternsForKind = (kind) => {
89
+ const cached = patternCache.get(kind);
90
+ if (cached !== void 0) return cached;
91
+ const patterns = [];
92
+ for (const [key, entity] of store[kind]) {
93
+ if (key.includes("*")) {
94
+ const segments = key.split("*");
95
+ patterns.push({
96
+ segments,
97
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
98
+ entity
99
+ });
100
+ }
101
+ }
102
+ patternCache.set(
103
+ kind,
104
+ patterns
105
+ );
106
+ return patterns;
107
+ };
108
+ const matchesSegments = (segments, id) => {
109
+ const first = segments[0];
110
+ const last = segments[segments.length - 1];
111
+ if (!id.startsWith(first)) return false;
112
+ let pos = first.length;
113
+ for (let i = 1; i < segments.length - 1; i++) {
114
+ const idx = id.indexOf(segments[i], pos);
115
+ if (idx === -1) return false;
116
+ pos = idx + segments[i].length;
117
+ }
118
+ return id.endsWith(last) && id.length - last.length >= pos;
119
+ };
120
+ const matchPattern = (kind, id) => {
121
+ assertEntityKind(kind);
122
+ const patterns = getPatternsForKind(kind);
123
+ if (patterns.length === 0) return void 0;
124
+ let best;
125
+ for (const entry of patterns) {
126
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
127
+ best = entry;
128
+ }
129
+ }
130
+ if (best === void 0) return void 0;
131
+ return freezeEntity(best.entity, getFlows());
132
+ };
86
133
  const list = (kind) => {
87
134
  assertEntityKind(kind);
88
135
  const flows = getFlows();
@@ -133,6 +180,7 @@ function createRegistry() {
133
180
  return {
134
181
  add,
135
182
  get,
183
+ matchPattern,
136
184
  list,
137
185
  query,
138
186
  byScope,
@@ -304,9 +352,6 @@ function createConsoleCapture(options = {}) {
304
352
 
305
353
  // src/browser/ingest/native-fetch.ts
306
354
  var nativeFetch = typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
307
- function getNativeFetch() {
308
- return nativeFetch;
309
- }
310
355
 
311
356
  // src/browser/ingest/network.ts
312
357
  var DEFAULT_LIMIT2 = 20;
@@ -389,7 +434,7 @@ function createNetworkCapture(options = {}) {
389
434
 
390
435
  // src/browser/ingest/index.ts
391
436
  function createIngest(options = {}) {
392
- const { session, ...opts } = options;
437
+ const opts = options;
393
438
  const wantConsole = opts.captureConsole !== false;
394
439
  const wantNetwork = opts.captureNetwork !== false;
395
440
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -408,14 +453,12 @@ function createIngest(options = {}) {
408
453
  consoleCapture?.start();
409
454
  networkCapture?.start();
410
455
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
411
- if (active) session?.setIngest(true);
412
456
  }
413
457
  function stop() {
414
458
  if (!active) return;
415
459
  consoleCapture?.stop();
416
460
  networkCapture?.stop();
417
461
  active = false;
418
- session?.setIngest(false);
419
462
  }
420
463
  return {
421
464
  start,
@@ -537,8 +580,7 @@ var COMMAND_PALETTE_ENTRY = {
537
580
  function createModeStore(options) {
538
581
  const { nav, bindings } = options;
539
582
  const store = createStore(() => ({
540
- mode: "idle",
541
- inspectorActive: false
583
+ mode: "idle"
542
584
  }));
543
585
  const transition = {
544
586
  openPalette() {
@@ -547,17 +589,17 @@ function createModeStore(options) {
547
589
  bindings?.destroyInspector?.();
548
590
  }
549
591
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
550
- store.setState({ mode: "palette", inspectorActive: false });
592
+ store.setState({ mode: "palette" });
551
593
  },
552
594
  openInspector() {
553
595
  bindings?.mountInspector?.();
554
596
  nav.nav.clear();
555
- store.setState({ mode: "inspecting", inspectorActive: true });
597
+ store.setState({ mode: "inspecting" });
556
598
  },
557
599
  closeInspector() {
558
600
  bindings?.destroyInspector?.();
559
601
  nav.nav.clear();
560
- store.setState({ mode: "idle", inspectorActive: false });
602
+ store.setState({ mode: "idle" });
561
603
  },
562
604
  toggleInspector() {
563
605
  if (store.getState().mode === "inspecting") {
@@ -572,7 +614,7 @@ function createModeStore(options) {
572
614
  bindings?.destroyInspector?.();
573
615
  }
574
616
  nav.nav.reset(initialStack);
575
- store.setState({ mode: "viewing", inspectorActive: false });
617
+ store.setState({ mode: "viewing" });
576
618
  },
577
619
  dismiss() {
578
620
  const prev = store.getState();
@@ -580,7 +622,7 @@ function createModeStore(options) {
580
622
  bindings?.destroyInspector?.();
581
623
  }
582
624
  nav.nav.clear();
583
- store.setState({ mode: "idle", inspectorActive: false });
625
+ store.setState({ mode: "idle" });
584
626
  },
585
627
  popOrTransition() {
586
628
  const { stack } = nav.getState();
@@ -588,12 +630,12 @@ function createModeStore(options) {
588
630
  nav.nav.pop();
589
631
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
590
632
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
591
- store.setState({ mode: "palette", inspectorActive: false });
633
+ store.setState({ mode: "palette" });
592
634
  } else if (stack.length === 2) {
593
635
  nav.nav.pop();
594
636
  } else {
595
637
  nav.nav.clear();
596
- store.setState({ mode: "idle", inspectorActive: false });
638
+ store.setState({ mode: "idle" });
597
639
  }
598
640
  },
599
641
  pushView(entry) {
@@ -610,7 +652,7 @@ function createModeStore(options) {
610
652
  case "inspecting":
611
653
  bindings?.destroyInspector?.();
612
654
  nav.nav.reset([entry]);
613
- store.setState({ mode: "viewing", inspectorActive: false });
655
+ store.setState({ mode: "viewing" });
614
656
  break;
615
657
  case "palette":
616
658
  case "viewing":
@@ -645,14 +687,6 @@ function createNavigationStore() {
645
687
  store.setState({ stack: s.slice(0, -1) });
646
688
  }
647
689
  },
648
- replace(entry) {
649
- const s = store.getState().stack;
650
- if (s.length === 0) {
651
- store.setState({ stack: [entry] });
652
- } else {
653
- store.setState({ stack: [...s.slice(0, -1), entry] });
654
- }
655
- },
656
690
  clear() {
657
691
  store.setState({ stack: [] });
658
692
  },
@@ -667,14 +701,11 @@ function createNavigationStore() {
667
701
 
668
702
  // src/browser/session/store.ts
669
703
  var defaultSnapshot = {
670
- hover: null,
671
- selection: null,
672
704
  stack: [],
673
705
  pinnedHighlight: null,
674
- inspectorActive: false,
706
+ mode: "idle",
675
707
  theme: "auto",
676
708
  resolvedTheme: "light",
677
- ingestActive: false,
678
709
  user: null
679
710
  };
680
711
  function resolveTheme(preference, detect) {
@@ -725,7 +756,6 @@ function createSession(options = {}) {
725
756
  } else if (highlightMode === "transient") {
726
757
  onUpdateOverlay?.(hlCtx);
727
758
  }
728
- store.setState({ hover: ref2 });
729
759
  },
730
760
  unhover() {
731
761
  if (highlightMode === "transient") {
@@ -735,7 +765,6 @@ function createSession(options = {}) {
735
765
  hlCtx.color = null;
736
766
  onHideOverlay?.();
737
767
  }
738
- store.setState({ hover: null });
739
768
  },
740
769
  pin(ref2) {
741
770
  const pinRef = ref2 ?? hlCtx.ref;
@@ -763,14 +792,11 @@ function createSession(options = {}) {
763
792
  };
764
793
  const store = createStore3(() => ({
765
794
  ...defaultSnapshot,
766
- hover: overrides.hover ?? null,
767
- selection: overrides.selection ?? null,
768
795
  stack: [],
769
796
  pinnedHighlight: null,
770
- inspectorActive: false,
797
+ mode: "idle",
771
798
  theme: initialPref,
772
799
  resolvedTheme: initialResolved,
773
- ingestActive: overrides.ingestActive ?? false,
774
800
  user: overrides.user ?? null
775
801
  }));
776
802
  nav.subscribe(() => {
@@ -780,29 +806,21 @@ function createSession(options = {}) {
780
806
  }
781
807
  });
782
808
  modeStore.subscribe(() => {
783
- const { inspectorActive } = modeStore.getState();
784
- if (store.getState().inspectorActive !== inspectorActive) {
785
- store.setState({ inspectorActive });
809
+ const { mode } = modeStore.getState();
810
+ if (store.getState().mode !== mode) {
811
+ store.setState({ mode });
786
812
  }
787
813
  });
788
814
  const session = store;
789
815
  session.nav = nav;
790
816
  session.mode = modeStore;
791
817
  session.highlight = highlightActions;
792
- session.select = (ref2) => {
793
- if (sameRef(store.getState().selection, ref2)) return;
794
- store.setState({ selection: ref2 });
795
- };
796
818
  session.setTheme = (theme, resolved) => {
797
819
  const state = store.getState();
798
820
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
799
821
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
800
822
  store.setState({ theme, resolvedTheme: nextResolved });
801
823
  };
802
- session.setIngest = (active) => {
803
- if (store.getState().ingestActive === active) return;
804
- store.setState({ ingestActive: active });
805
- };
806
824
  if (initialStack.length > 0) {
807
825
  modeStore.transition.openPalette();
808
826
  for (let i = 1; i < initialStack.length; i++) {
@@ -1094,6 +1112,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1094
1112
  .relative {
1095
1113
  position: relative;
1096
1114
  }
1115
+ .static {
1116
+ position: static;
1117
+ }
1097
1118
  .inset-0 {
1098
1119
  inset: calc(var(--spacing) * 0);
1099
1120
  }
@@ -1112,9 +1133,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1112
1133
  .right-0 {
1113
1134
  right: calc(var(--spacing) * 0);
1114
1135
  }
1115
- .right-2 {
1116
- right: calc(var(--spacing) * 2);
1117
- }
1118
1136
  .bottom-full {
1119
1137
  bottom: 100%;
1120
1138
  }
@@ -1157,9 +1175,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1157
1175
  .mx-2 {
1158
1176
  margin-inline: calc(var(--spacing) * 2);
1159
1177
  }
1160
- .my-1 {
1161
- margin-block: calc(var(--spacing) * 1);
1162
- }
1163
1178
  .ms-auto {
1164
1179
  margin-inline-start: auto;
1165
1180
  }
@@ -1196,9 +1211,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1196
1211
  .inline-flex {
1197
1212
  display: inline-flex;
1198
1213
  }
1199
- .list-item {
1200
- display: list-item;
1201
- }
1202
1214
  .table {
1203
1215
  display: table;
1204
1216
  }
@@ -1206,10 +1218,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1206
1218
  width: calc(var(--spacing) * 2);
1207
1219
  height: calc(var(--spacing) * 2);
1208
1220
  }
1209
- .size-3 {
1210
- width: calc(var(--spacing) * 3);
1211
- height: calc(var(--spacing) * 3);
1212
- }
1213
1221
  .size-3\\.5 {
1214
1222
  width: calc(var(--spacing) * 3.5);
1215
1223
  height: calc(var(--spacing) * 3.5);
@@ -1267,15 +1275,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1267
1275
  .h-\\[26rem\\] {
1268
1276
  height: 26rem;
1269
1277
  }
1270
- .h-auto {
1271
- height: auto;
1272
- }
1273
1278
  .h-full {
1274
1279
  height: 100%;
1275
1280
  }
1276
- .h-px {
1277
- height: 1px;
1278
- }
1279
1281
  .max-h-32 {
1280
1282
  max-height: calc(var(--spacing) * 32);
1281
1283
  }
@@ -1285,9 +1287,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1285
1287
  .min-h-0 {
1286
1288
  min-height: calc(var(--spacing) * 0);
1287
1289
  }
1288
- .min-h-7 {
1289
- min-height: calc(var(--spacing) * 7);
1290
- }
1291
1290
  .min-h-8 {
1292
1291
  min-height: calc(var(--spacing) * 8);
1293
1292
  }
@@ -1312,9 +1311,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1312
1311
  .w-56 {
1313
1312
  width: calc(var(--spacing) * 56);
1314
1313
  }
1315
- .w-auto {
1316
- width: auto;
1317
- }
1318
1314
  .w-full {
1319
1315
  width: 100%;
1320
1316
  }
@@ -1389,9 +1385,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1389
1385
  .animate-spin {
1390
1386
  animation: var(--animate-spin);
1391
1387
  }
1392
- .cursor-default {
1393
- cursor: default;
1394
- }
1395
1388
  .cursor-pointer {
1396
1389
  cursor: pointer;
1397
1390
  }
@@ -1401,9 +1394,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1401
1394
  .scroll-py-2 {
1402
1395
  scroll-padding-block: calc(var(--spacing) * 2);
1403
1396
  }
1404
- .list-none {
1405
- list-style-type: none;
1406
- }
1407
1397
  .appearance-none {
1408
1398
  appearance: none;
1409
1399
  }
@@ -1425,9 +1415,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1425
1415
  .justify-center {
1426
1416
  justify-content: center;
1427
1417
  }
1428
- .justify-start {
1429
- justify-content: flex-start;
1430
- }
1431
1418
  .gap-0 {
1432
1419
  gap: calc(var(--spacing) * 0);
1433
1420
  }
@@ -1452,9 +1439,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1452
1439
  .gap-6 {
1453
1440
  gap: calc(var(--spacing) * 6);
1454
1441
  }
1455
- .self-start {
1456
- align-self: flex-start;
1457
- }
1458
1442
  .truncate {
1459
1443
  overflow: hidden;
1460
1444
  text-overflow: ellipsis;
@@ -1669,9 +1653,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1669
1653
  .p-6 {
1670
1654
  padding: calc(var(--spacing) * 6);
1671
1655
  }
1672
- .px-0 {
1673
- padding-inline: calc(var(--spacing) * 0);
1674
- }
1675
1656
  .px-1 {
1676
1657
  padding-inline: calc(var(--spacing) * 1);
1677
1658
  }
@@ -1696,9 +1677,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1696
1677
  .px-6 {
1697
1678
  padding-inline: calc(var(--spacing) * 6);
1698
1679
  }
1699
- .py-0 {
1700
- padding-block: calc(var(--spacing) * 0);
1701
- }
1702
1680
  .py-1 {
1703
1681
  padding-block: calc(var(--spacing) * 1);
1704
1682
  }
@@ -1771,9 +1749,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1771
1749
  .text-\\[9px\\] {
1772
1750
  font-size: 9px;
1773
1751
  }
1774
- .text-\\[10px\\] {
1775
- font-size: 10px;
1776
- }
1777
1752
  .text-\\[11px\\] {
1778
1753
  font-size: 11px;
1779
1754
  }
@@ -2013,10 +1988,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2013
1988
  outline-style: var(--tw-outline-style);
2014
1989
  outline-width: 1px;
2015
1990
  }
2016
- .blur {
2017
- --tw-blur: blur(8px);
2018
- filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
2019
- }
2020
1991
  .filter {
2021
1992
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
2022
1993
  }
@@ -2182,25 +2153,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2182
2153
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2183
2154
  }
2184
2155
  }
2185
- .focus-within\\:border-ring {
2186
- &:focus-within {
2187
- border-color: var(--ring);
2188
- }
2189
- }
2190
- .focus-within\\:ring-\\[3px\\] {
2191
- &:focus-within {
2192
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2193
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2194
- }
2195
- }
2196
- .focus-within\\:ring-ring\\/30 {
2197
- &:focus-within {
2198
- --tw-ring-color: var(--ring);
2199
- @supports (color: color-mix(in lab, red, red)) {
2200
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2201
- }
2202
- }
2203
- }
2204
2156
  .hover\\:border-destructive\\/30 {
2205
2157
  &:hover {
2206
2158
  @media (hover: hover) {
@@ -2344,11 +2296,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2344
2296
  pointer-events: none;
2345
2297
  }
2346
2298
  }
2347
- .disabled\\:cursor-not-allowed {
2348
- &:disabled {
2349
- cursor: not-allowed;
2350
- }
2351
- }
2352
2299
  .disabled\\:opacity-50 {
2353
2300
  &:disabled {
2354
2301
  opacity: 50%;
@@ -2359,11 +2306,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2359
2306
  opacity: 60%;
2360
2307
  }
2361
2308
  }
2362
- .has-disabled\\:opacity-60 {
2363
- &:has(*:disabled) {
2364
- opacity: 60%;
2365
- }
2366
- }
2367
2309
  .aria-invalid\\:border-destructive\\/40 {
2368
2310
  &[aria-invalid="true"] {
2369
2311
  border-color: var(--destructive);
@@ -2770,32 +2712,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2770
2712
  height: calc(var(--spacing) * 4.5);
2771
2713
  }
2772
2714
  }
2773
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2774
- &>svg {
2775
- pointer-events: none;
2776
- }
2777
- }
2778
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2779
- &>svg {
2780
- margin-inline: calc(var(--spacing) * -0.5);
2781
- }
2782
- }
2783
- .\\[\\&\\>svg\\]\\:shrink-0 {
2784
- &>svg {
2785
- flex-shrink: 0;
2786
- }
2787
- }
2788
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2789
- &>svg:not([class*='opacity-']) {
2790
- opacity: 80%;
2791
- }
2792
- }
2793
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2794
- &>svg:not([class*='size-']) {
2795
- width: calc(var(--spacing) * 4);
2796
- height: calc(var(--spacing) * 4);
2797
- }
2798
- }
2799
2715
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2800
2716
  [data-kbd-nav] & {
2801
2717
  &:focus-within {
@@ -3357,18 +3273,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3357
3273
 
3358
3274
  // src/browser/surface/constants.ts
3359
3275
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3360
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3361
3276
  var Z_BASE = 2147483630;
3362
3277
  var Z_OVERLAY = 2147483635;
3363
3278
  var Z_PIN_LAYER = 2147483640;
3364
3279
  var Z_CHROME = 2147483645;
3365
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3280
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3366
3281
  var UIDEX_ATTR_TO_KIND = [
3367
3282
  ["data-uidex", "element"],
3368
3283
  ["data-uidex-region", "region"],
3369
3284
  ["data-uidex-widget", "widget"],
3370
3285
  ["data-uidex-primitive", "primitive"]
3371
3286
  ];
3287
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3288
+ "element",
3289
+ "region",
3290
+ "widget",
3291
+ "primitive"
3292
+ ]);
3372
3293
 
3373
3294
  // src/browser/surface/host.ts
3374
3295
  function createSurfaceHost(options) {
@@ -3546,7 +3467,7 @@ function createCursorTooltip(deps) {
3546
3467
  // src/browser/surface/inspector.ts
3547
3468
  function entityForRef(ref2, registry) {
3548
3469
  if (registry) {
3549
- const found = registry.get(ref2.kind, ref2.id);
3470
+ const found = registry.get(ref2.kind, ref2.id) ?? registry.matchPattern?.(ref2.kind, ref2.id);
3550
3471
  if (found) return found;
3551
3472
  }
3552
3473
  if (ref2.kind === "route") return { kind: "route", path: ref2.id, page: ref2.id };
@@ -3561,29 +3482,6 @@ function entityForRef(ref2, registry) {
3561
3482
  }
3562
3483
  return { kind: ref2.kind, id: ref2.id };
3563
3484
  }
3564
- function defaultResolveMatch(target, registry) {
3565
- if (target.closest(SURFACE_IGNORE_SELECTOR)) return null;
3566
- let node = target;
3567
- while (node) {
3568
- if (node instanceof HTMLElement) {
3569
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
3570
- const id = node.getAttribute(attr);
3571
- if (id) {
3572
- const ref2 = { kind, id };
3573
- const entity = entityForRef(ref2, registry);
3574
- return {
3575
- element: node,
3576
- ref: ref2,
3577
- entity,
3578
- label: displayName(entity, node)
3579
- };
3580
- }
3581
- }
3582
- }
3583
- node = node.parentElement;
3584
- }
3585
- return null;
3586
- }
3587
3485
  function defaultResolveAllMatches(target, registry) {
3588
3486
  if (target.closest(SURFACE_IGNORE_SELECTOR)) return [];
3589
3487
  const semantic = [];
@@ -3628,27 +3526,6 @@ function resolveEntityElement(ref2) {
3628
3526
  if (el2 instanceof HTMLElement && el2.isConnected) return el2;
3629
3527
  return null;
3630
3528
  }
3631
- function createHighlightController(overlay) {
3632
- let lastKey = null;
3633
- return {
3634
- show(ref2, opts) {
3635
- const el2 = resolveEntityElement(ref2);
3636
- if (!el2) {
3637
- lastKey = null;
3638
- overlay.hide();
3639
- return;
3640
- }
3641
- const key = `${ref2.kind}:${ref2.id}`;
3642
- if (key === lastKey && overlay.isVisible) return;
3643
- lastKey = key;
3644
- overlay.show(el2, opts);
3645
- },
3646
- hide() {
3647
- lastKey = null;
3648
- overlay.hide();
3649
- }
3650
- };
3651
- }
3652
3529
  function createInspector(options) {
3653
3530
  const {
3654
3531
  session,
@@ -3702,7 +3579,6 @@ function createInspector(options) {
3702
3579
  e.preventDefault();
3703
3580
  e.stopPropagation();
3704
3581
  const match = stack[layerIndex];
3705
- session.select(match.ref);
3706
3582
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3707
3583
  };
3708
3584
  const onContextMenu = (e) => {
@@ -3751,8 +3627,6 @@ function createInspector(options) {
3751
3627
 
3752
3628
  // src/browser/surface/menu-bar.ts
3753
3629
  import {
3754
- ChevronLeft,
3755
- ChevronRight,
3756
3630
  Command,
3757
3631
  Highlighter,
3758
3632
  MapPin,
@@ -4030,49 +3904,12 @@ function createMenuBar(options) {
4030
3904
  },
4031
3905
  pinIcon
4032
3906
  );
4033
- const commitCycler = el("div", {
4034
- class: "relative z-1 inline-flex items-center gap-0.5",
4035
- attrs: { "data-uidex-menubar-commit-cycler": "" }
4036
- });
4037
- commitCycler.hidden = true;
4038
- const prevIcon = createLucideElement2(ChevronLeft);
4039
- prevIcon.setAttribute("class", "size-3");
4040
- prevIcon.setAttribute("aria-hidden", "true");
4041
- const prevBtn = el(
4042
- "button",
4043
- {
4044
- class: BUTTON_CLASS,
4045
- attrs: { type: "button", "aria-label": "Previous commit" },
4046
- style: { width: "18px", height: "18px" }
4047
- },
4048
- prevIcon
4049
- );
4050
- const commitLabel = el("span", {
4051
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4052
- attrs: { "data-uidex-menubar-commit-label": "" }
4053
- });
4054
- const nextIcon = createLucideElement2(ChevronRight);
4055
- nextIcon.setAttribute("class", "size-3");
4056
- nextIcon.setAttribute("aria-hidden", "true");
4057
- const nextBtn = el(
4058
- "button",
4059
- {
4060
- class: BUTTON_CLASS,
4061
- attrs: { type: "button", "aria-label": "Next commit" },
4062
- style: { width: "18px", height: "18px" }
4063
- },
4064
- nextIcon
4065
- );
4066
- commitCycler.appendChild(prevBtn);
4067
- commitCycler.appendChild(commitLabel);
4068
- commitCycler.appendChild(nextBtn);
4069
3907
  const pinWrapper = el("div", {
4070
3908
  class: "relative z-1 inline-flex items-center gap-0.5",
4071
3909
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4072
3910
  });
4073
3911
  pinWrapper.hidden = true;
4074
3912
  pinWrapper.appendChild(pinBtn);
4075
- pinWrapper.appendChild(commitCycler);
4076
3913
  root.appendChild(pinWrapper);
4077
3914
  const updatePinUI = () => {
4078
3915
  if (!activePinLayer) {
@@ -4080,16 +3917,7 @@ function createMenuBar(options) {
4080
3917
  return;
4081
3918
  }
4082
3919
  const pinsVisible = activePinLayer.visible;
4083
- const state = activePinLayer.filterState;
4084
- const hasCommits = state.commits.length > 0;
4085
3920
  pinWrapper.hidden = false;
4086
- commitCycler.hidden = !pinsVisible || !hasCommits;
4087
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4088
- commitLabel.textContent = `all (${state.commits.length})`;
4089
- } else {
4090
- const sha = state.commits[state.commitIndex] ?? "";
4091
- commitLabel.textContent = sha.slice(0, 7);
4092
- }
4093
3921
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4094
3922
  };
4095
3923
  pinBtn.addEventListener("click", (e) => {
@@ -4098,14 +3926,6 @@ function createMenuBar(options) {
4098
3926
  activePinLayer.setVisible(!activePinLayer.visible);
4099
3927
  }
4100
3928
  });
4101
- prevBtn.addEventListener("click", (e) => {
4102
- e.stopPropagation();
4103
- activePinLayer?.prevCommit();
4104
- });
4105
- nextBtn.addEventListener("click", (e) => {
4106
- e.stopPropagation();
4107
- activePinLayer?.nextCommit();
4108
- });
4109
3929
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4110
3930
  const presenceIcon = createLucideElement2(Users);
4111
3931
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4199,7 +4019,7 @@ function createMenuBar(options) {
4199
4019
  container.appendChild(root);
4200
4020
  const syncButtonStates = () => {
4201
4021
  const state = session.getState();
4202
- const inspectActive = state.inspectorActive;
4022
+ const inspectActive = state.mode === "inspecting";
4203
4023
  inspectBtn.setAttribute(
4204
4024
  "data-uidex-menubar-inspect-active",
4205
4025
  inspectActive ? "true" : "false"
@@ -4318,6 +4138,49 @@ function createMenuBar(options) {
4318
4138
  };
4319
4139
  }
4320
4140
 
4141
+ // src/browser/internal/repositioner.ts
4142
+ function createRepositioner(onReflow) {
4143
+ let rafId = null;
4144
+ let attached = false;
4145
+ const schedule = () => {
4146
+ if (rafId !== null) return;
4147
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4148
+ rafId = null;
4149
+ onReflow();
4150
+ }) : setTimeout(() => {
4151
+ rafId = null;
4152
+ onReflow();
4153
+ }, 0);
4154
+ };
4155
+ const cancel = () => {
4156
+ if (rafId === null) return;
4157
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4158
+ else clearTimeout(rafId);
4159
+ rafId = null;
4160
+ };
4161
+ const onScroll = () => schedule();
4162
+ const onResize = () => schedule();
4163
+ const attach = () => {
4164
+ if (attached) return;
4165
+ attached = true;
4166
+ window.addEventListener("resize", onResize);
4167
+ window.addEventListener("scroll", onScroll, {
4168
+ capture: true,
4169
+ passive: true
4170
+ });
4171
+ };
4172
+ const detach = () => {
4173
+ if (!attached) return;
4174
+ attached = false;
4175
+ window.removeEventListener("resize", onResize);
4176
+ window.removeEventListener("scroll", onScroll, {
4177
+ capture: true
4178
+ });
4179
+ cancel();
4180
+ };
4181
+ return { schedule, cancel, attach, detach };
4182
+ }
4183
+
4321
4184
  // src/browser/surface/overlay.ts
4322
4185
  var DEFAULT_COLOR = "#34d399";
4323
4186
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4385,44 +4248,7 @@ function createOverlay(deps) {
4385
4248
  fillOpacity: DEFAULT_FILL_OPACITY,
4386
4249
  backdrop: false
4387
4250
  };
4388
- let rafId = null;
4389
- let attached = false;
4390
- const schedule = () => {
4391
- if (rafId !== null) return;
4392
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4393
- rafId = null;
4394
- updatePosition();
4395
- }) : setTimeout(() => {
4396
- rafId = null;
4397
- updatePosition();
4398
- }, 0);
4399
- };
4400
- const cancelSchedule = () => {
4401
- if (rafId === null) return;
4402
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4403
- else clearTimeout(rafId);
4404
- rafId = null;
4405
- };
4406
- const onScroll = () => schedule();
4407
- const onResize = () => schedule();
4408
- const attach = () => {
4409
- if (attached) return;
4410
- attached = true;
4411
- window.addEventListener("resize", onResize);
4412
- window.addEventListener("scroll", onScroll, {
4413
- capture: true,
4414
- passive: true
4415
- });
4416
- };
4417
- const detach = () => {
4418
- if (!attached) return;
4419
- attached = false;
4420
- window.removeEventListener("resize", onResize);
4421
- window.removeEventListener("scroll", onScroll, {
4422
- capture: true
4423
- });
4424
- cancelSchedule();
4425
- };
4251
+ const repositioner = createRepositioner(() => updatePosition());
4426
4252
  function updatePosition() {
4427
4253
  if (!target) return;
4428
4254
  const rect = target.getBoundingClientRect();
@@ -4486,16 +4312,16 @@ function createOverlay(deps) {
4486
4312
  box.offsetHeight;
4487
4313
  }
4488
4314
  box.style.opacity = "1";
4489
- attach();
4315
+ repositioner.attach();
4490
4316
  },
4491
4317
  hide() {
4492
4318
  target = null;
4493
4319
  box.style.opacity = "0";
4494
4320
  backdrop.style.opacity = "0";
4495
- detach();
4321
+ repositioner.detach();
4496
4322
  },
4497
4323
  destroy() {
4498
- detach();
4324
+ repositioner.detach();
4499
4325
  box.remove();
4500
4326
  backdrop.remove();
4501
4327
  target = null;
@@ -4612,8 +4438,7 @@ function createSurfaceShell(options) {
4612
4438
  const overlay = createOverlay({ container: host.shadowRoot });
4613
4439
  cleanup.add(overlay);
4614
4440
  const tooltip = createCursorTooltip({
4615
- container: host.chromeEl,
4616
- session: options.session
4441
+ container: host.chromeEl
4617
4442
  });
4618
4443
  cleanup.add(tooltip);
4619
4444
  const afterHover = options.inspector?.onAfterHover;
@@ -4805,9 +4630,6 @@ function createPinLayer(options) {
4805
4630
  const seenIds = /* @__PURE__ */ new Set();
4806
4631
  const byComp = /* @__PURE__ */ new Map();
4807
4632
  const indicators = /* @__PURE__ */ new Map();
4808
- let filter = { branch: null, commit: null };
4809
- let commits = [];
4810
- let commitIndex = -1;
4811
4633
  const filterCbs = /* @__PURE__ */ new Set();
4812
4634
  const notifyFilter = () => {
4813
4635
  for (const cb of filterCbs) cb();
@@ -4822,8 +4644,6 @@ function createPinLayer(options) {
4822
4644
  }
4823
4645
  };
4824
4646
  const rerender = () => {
4825
- commits = [];
4826
- commitIndex = -1;
4827
4647
  rebuildFiltered();
4828
4648
  for (const id of Array.from(indicators.keys())) {
4829
4649
  if (!byComp.has(id)) removeIndicator(id);
@@ -4832,45 +4652,8 @@ function createPinLayer(options) {
4832
4652
  notifyFilter();
4833
4653
  onPinsChanged?.();
4834
4654
  };
4835
- let rafId = null;
4836
- let winAttached = false;
4837
4655
  let obs = null;
4838
- const schedulePos = () => {
4839
- if (rafId !== null) return;
4840
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4841
- rafId = null;
4842
- posAll();
4843
- }) : setTimeout(() => {
4844
- rafId = null;
4845
- posAll();
4846
- }, 0);
4847
- };
4848
- const cancelPos = () => {
4849
- if (rafId === null) return;
4850
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4851
- else clearTimeout(rafId);
4852
- rafId = null;
4853
- };
4854
- const onScroll = () => schedulePos();
4855
- const onResize = () => schedulePos();
4856
- const attachWin = () => {
4857
- if (winAttached) return;
4858
- winAttached = true;
4859
- window.addEventListener("scroll", onScroll, {
4860
- capture: true,
4861
- passive: true
4862
- });
4863
- window.addEventListener("resize", onResize);
4864
- };
4865
- const detachWin = () => {
4866
- if (!winAttached) return;
4867
- winAttached = false;
4868
- window.removeEventListener("scroll", onScroll, {
4869
- capture: true
4870
- });
4871
- window.removeEventListener("resize", onResize);
4872
- cancelPos();
4873
- };
4656
+ const repositioner = createRepositioner(() => posAll());
4874
4657
  const attachObs = () => {
4875
4658
  if (obs || typeof MutationObserver === "undefined") return;
4876
4659
  obs = new MutationObserver((recs) => {
@@ -4894,7 +4677,7 @@ function createPinLayer(options) {
4894
4677
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4895
4678
  changed = true;
4896
4679
  }
4897
- if (changed) schedulePos();
4680
+ if (changed) repositioner.schedule();
4898
4681
  };
4899
4682
  const expand = (st) => {
4900
4683
  if (st.expanded) return;
@@ -5136,7 +4919,7 @@ function createPinLayer(options) {
5136
4919
  if (!st) {
5137
4920
  st = buildIndicator(componentId);
5138
4921
  indicators.set(componentId, st);
5139
- attachWin();
4922
+ repositioner.attach();
5140
4923
  attachObs();
5141
4924
  }
5142
4925
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5150,7 +4933,7 @@ function createPinLayer(options) {
5150
4933
  st.wrap.remove();
5151
4934
  indicators.delete(componentId);
5152
4935
  if (indicators.size === 0) {
5153
- detachWin();
4936
+ repositioner.detach();
5154
4937
  detachObs();
5155
4938
  }
5156
4939
  };
@@ -5185,17 +4968,22 @@ function createPinLayer(options) {
5185
4968
  seenIds.clear();
5186
4969
  byComp.clear();
5187
4970
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5188
- commits = [];
5189
- commitIndex = -1;
5190
4971
  notifyFilter();
5191
4972
  },
5192
4973
  getPinsForElement: (id) => byComp.get(id) ?? [],
5193
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5194
4974
  getAllPins: () => allPins.slice(),
5195
4975
  attachChannel(channel) {
5196
- return channel.onPin((pin) => {
4976
+ const offPin = channel.onPin((pin) => {
5197
4977
  layer.addPin(pin);
5198
4978
  });
4979
+ const offArchived = channel.onPinArchived?.((reportId) => {
4980
+ layer.removePin(reportId);
4981
+ }) ?? (() => {
4982
+ });
4983
+ return () => {
4984
+ offPin();
4985
+ offArchived();
4986
+ };
5199
4987
  },
5200
4988
  attachCloud(opts) {
5201
4989
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5226,39 +5014,6 @@ function createPinLayer(options) {
5226
5014
  async refresh() {
5227
5015
  if (activeRefresh) await activeRefresh();
5228
5016
  },
5229
- get filterState() {
5230
- return {
5231
- branch: filter.branch,
5232
- commit: filter.commit,
5233
- commits,
5234
- commitIndex
5235
- };
5236
- },
5237
- setFilter(next) {
5238
- filter = {
5239
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5240
- commit: next.commit !== void 0 ? next.commit : filter.commit
5241
- };
5242
- rerender();
5243
- },
5244
- nextCommit() {
5245
- if (!commits.length) return;
5246
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5247
- filter = {
5248
- ...filter,
5249
- commit: commitIndex === -1 ? null : commits[commitIndex]
5250
- };
5251
- rerender();
5252
- },
5253
- prevCommit() {
5254
- if (!commits.length) return;
5255
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5256
- filter = {
5257
- ...filter,
5258
- commit: commitIndex === -1 ? null : commits[commitIndex]
5259
- };
5260
- rerender();
5261
- },
5262
5017
  onFilterChange(cb) {
5263
5018
  filterCbs.add(cb);
5264
5019
  return () => {
@@ -5277,7 +5032,7 @@ function createPinLayer(options) {
5277
5032
  },
5278
5033
  destroy() {
5279
5034
  layer.clear();
5280
- detachWin();
5035
+ repositioner.detach();
5281
5036
  detachObs();
5282
5037
  layerEl.remove();
5283
5038
  activeRefresh = null;
@@ -5320,9 +5075,11 @@ function createRouter(options) {
5320
5075
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5321
5076
  throw new ViewValidationError("View must have a non-empty string id");
5322
5077
  }
5323
- if (typeof view.surface !== "function") {
5078
+ const hasSurface = typeof view.surface === "function";
5079
+ const hasRender = typeof view.render === "function";
5080
+ if (!hasSurface && !hasRender) {
5324
5081
  throw new ViewValidationError(
5325
- `View ${view.id}: 'surface' must be a function`
5082
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5326
5083
  );
5327
5084
  }
5328
5085
  if (!view.matches && !view.palette) {
@@ -5380,7 +5137,6 @@ function createRouter(options) {
5380
5137
  if (idx >= 0) recentRefs.splice(idx, 1);
5381
5138
  recentRefs.unshift(ref2);
5382
5139
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5383
- options.session.select(ref2);
5384
5140
  const entry = { id: match.view.id, ref: ref2 };
5385
5141
  const { mode } = options.session.mode.getState();
5386
5142
  if (mode === "idle" || mode === "inspecting") {
@@ -5435,57 +5191,7 @@ function detectDev() {
5435
5191
 
5436
5192
  // src/browser/internal/lit.ts
5437
5193
  import { html, svg, render, nothing } from "lit-html";
5438
- import { repeat } from "lit-html/directives/repeat.js";
5439
5194
  import { ref, createRef } from "lit-html/directives/ref.js";
5440
- import { classMap } from "lit-html/directives/class-map.js";
5441
-
5442
- // src/browser/internal/apply-props.ts
5443
- function applyProps(node, props) {
5444
- const removers = [];
5445
- for (const [key, rawValue] of Object.entries(props)) {
5446
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5447
- if (key.startsWith("on") && typeof rawValue === "function") {
5448
- const eventName = key.slice(2).toLowerCase();
5449
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5450
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5451
- const listener = rawValue;
5452
- node.addEventListener(effectiveEvent, listener);
5453
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5454
- continue;
5455
- }
5456
- if (rawValue === void 0 || rawValue === null) {
5457
- node.removeAttribute(key);
5458
- continue;
5459
- }
5460
- if (typeof rawValue === "boolean") {
5461
- if (rawValue) node.setAttribute(key, "");
5462
- else node.removeAttribute(key);
5463
- continue;
5464
- }
5465
- if (key === "style" && typeof rawValue === "object") {
5466
- Object.assign(
5467
- node.style,
5468
- rawValue
5469
- );
5470
- continue;
5471
- }
5472
- if (key === "className" || key === "class") {
5473
- node.setAttribute("class", String(rawValue));
5474
- continue;
5475
- }
5476
- if (key === "htmlFor") {
5477
- node.setAttribute("for", String(rawValue));
5478
- continue;
5479
- }
5480
- if (key === "tabIndex") {
5481
- ;
5482
- node.tabIndex = Number(rawValue);
5483
- continue;
5484
- }
5485
- node.setAttribute(key, String(rawValue));
5486
- }
5487
- return () => removers.forEach((fn) => fn());
5488
- }
5489
5195
 
5490
5196
  // src/browser/internal/lit-icon.ts
5491
5197
  import { noChange } from "lit-html";
@@ -5523,6 +5229,16 @@ var LucideIconDirective = class extends Directive {
5523
5229
  };
5524
5230
  var icon = directive(LucideIconDirective);
5525
5231
 
5232
+ // src/browser/views/primitives/chip.ts
5233
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5234
+ function createChip(options = {}, children = []) {
5235
+ const { class: extra, ...rest } = options;
5236
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5237
+ }
5238
+
5239
+ // src/browser/views/primitives/kind-icon.ts
5240
+ import { createElement as createLucideElement4 } from "lucide";
5241
+
5526
5242
  // src/browser/ui/cva.ts
5527
5243
  function cva(base, config = {}) {
5528
5244
  return (props = {}) => {
@@ -5561,17 +5277,15 @@ var badgeVariants = cva(badgeBase, {
5561
5277
  }
5562
5278
  }
5563
5279
  });
5564
-
5565
- // src/browser/views/primitives/chip.ts
5566
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5567
- function createChip(options = {}, children = []) {
5568
- const { class: extra, ...rest } = options;
5569
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5280
+ function badgeTpl(content, options = {}) {
5281
+ const { variant, size, class: extra } = options;
5282
+ return html`
5283
+ <span class=${cn(badgeVariants({ variant, size }), extra)} data-slot="badge"
5284
+ >${content}</span
5285
+ >
5286
+ `;
5570
5287
  }
5571
5288
 
5572
- // src/browser/views/primitives/kind-icon.ts
5573
- import { createElement as createLucideElement4 } from "lucide";
5574
-
5575
5289
  // src/browser/views/primitives/icon-tile.ts
5576
5290
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5577
5291
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6079,6 +5793,54 @@ function createScrollArea(props = {}) {
6079
5793
  );
6080
5794
  }
6081
5795
 
5796
+ // src/browser/internal/apply-props.ts
5797
+ function applyProps(node, props) {
5798
+ const removers = [];
5799
+ for (const [key, rawValue] of Object.entries(props)) {
5800
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5801
+ if (key.startsWith("on") && typeof rawValue === "function") {
5802
+ const eventName = key.slice(2).toLowerCase();
5803
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5804
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5805
+ const listener = rawValue;
5806
+ node.addEventListener(effectiveEvent, listener);
5807
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5808
+ continue;
5809
+ }
5810
+ if (rawValue === void 0 || rawValue === null) {
5811
+ node.removeAttribute(key);
5812
+ continue;
5813
+ }
5814
+ if (typeof rawValue === "boolean") {
5815
+ if (rawValue) node.setAttribute(key, "");
5816
+ else node.removeAttribute(key);
5817
+ continue;
5818
+ }
5819
+ if (key === "style" && typeof rawValue === "object") {
5820
+ Object.assign(
5821
+ node.style,
5822
+ rawValue
5823
+ );
5824
+ continue;
5825
+ }
5826
+ if (key === "className" || key === "class") {
5827
+ node.setAttribute("class", String(rawValue));
5828
+ continue;
5829
+ }
5830
+ if (key === "htmlFor") {
5831
+ node.setAttribute("for", String(rawValue));
5832
+ continue;
5833
+ }
5834
+ if (key === "tabIndex") {
5835
+ ;
5836
+ node.tabIndex = Number(rawValue);
5837
+ continue;
5838
+ }
5839
+ node.setAttribute(key, String(rawValue));
5840
+ }
5841
+ return () => removers.forEach((fn) => fn());
5842
+ }
5843
+
6082
5844
  // src/browser/views/builder/spread-props.ts
6083
5845
  function spreadProps(node, props) {
6084
5846
  return applyProps(node, props);
@@ -6098,21 +5860,6 @@ function createPersistentSpreads() {
6098
5860
  };
6099
5861
  }
6100
5862
 
6101
- // src/browser/views/render/detail.ts
6102
- import {
6103
- ArchiveX,
6104
- Camera,
6105
- ChevronDown,
6106
- Copy,
6107
- Highlighter as Highlighter2,
6108
- Inbox,
6109
- MessageCircleWarning,
6110
- StickyNote as StickyNote2,
6111
- TicketPlus,
6112
- View,
6113
- createElement as createLucideElement5
6114
- } from "lucide";
6115
-
6116
5863
  // src/browser/internal/arrow-nav.ts
6117
5864
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6118
5865
  function bindArrowNav(options) {
@@ -6184,30 +5931,6 @@ function focusItem(items, idx) {
6184
5931
  items[idx].focus();
6185
5932
  }
6186
5933
 
6187
- // src/browser/views/builder/filter.ts
6188
- function normalizeQuery(query) {
6189
- return query.trim().toLowerCase();
6190
- }
6191
- function matchesQuery(haystack, query) {
6192
- const q = normalizeQuery(query);
6193
- if (!q) return true;
6194
- return haystack.toLowerCase().includes(q);
6195
- }
6196
- function filterEntities(entities, query) {
6197
- const q = normalizeQuery(query);
6198
- if (!q) return entities;
6199
- return entities.filter((e) => {
6200
- const name = displayName(e).toLowerCase();
6201
- const id = entityKey(e).toLowerCase();
6202
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6203
- });
6204
- }
6205
- function filterIds(ids, query) {
6206
- const q = normalizeQuery(query);
6207
- if (!q) return ids;
6208
- return ids.filter((id) => id.toLowerCase().includes(q));
6209
- }
6210
-
6211
5934
  // src/browser/views/labels.ts
6212
5935
  var SECTION_LABELS = {
6213
5936
  acceptance: "Acceptance criteria",
@@ -6232,171 +5955,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6232
5955
  var LIST_ITEM_INTERACTIVE_CLASS = "uidex-item-interactive [[data-kbd-nav]_&]:focus:bg-accent [[data-kbd-nav]_&]:focus:text-accent-foreground [[data-kbd-nav]_&]:focus-within:bg-accent [[data-kbd-nav]_&]:focus-within:text-accent-foreground";
6233
5956
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6234
5957
 
6235
- // src/browser/ui/button.ts
6236
- var buttonBase = "focus-visible:ring-ring focus-visible:ring-offset-background disabled:opacity-60 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border text-sm font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg]:pointer-events-none [&_svg]:shrink-0";
6237
- var buttonVariants = cva(buttonBase, {
6238
- defaultVariants: { size: "default", variant: "default" },
6239
- variants: {
6240
- size: {
6241
- default: "h-8 px-3",
6242
- sm: "h-7 gap-1.5 px-2.5",
6243
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6244
- lg: "h-9 px-3.5",
6245
- xl: "h-10 px-4 text-base",
6246
- icon: "size-8",
6247
- "icon-sm": "size-7",
6248
- "icon-lg": "size-9",
6249
- "icon-xs": "size-6 rounded-md"
6250
- },
6251
- variant: {
6252
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6253
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6254
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6255
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6256
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6257
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6258
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6259
- }
6260
- }
6261
- });
6262
-
6263
- // src/browser/views/primitives/entity-presence.ts
6264
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6265
- "element",
6266
- "region",
6267
- "widget",
6268
- "primitive"
6269
- ]);
6270
- var TOUCH_RESOLVE_KINDS = [
6271
- "element",
6272
- "widget",
6273
- "region",
6274
- "primitive"
6275
- ];
6276
- function isAbsentFromPage(ref2, registry) {
6277
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6278
- return !resolveEntityElement(ref2);
6279
- }
6280
- if (ref2.kind === "flow" && registry) {
6281
- const flow = registry.get("flow", ref2.id);
6282
- if (!flow) return true;
6283
- for (const touchId of flow.touches) {
6284
- for (const kind of TOUCH_RESOLVE_KINDS) {
6285
- const entity = registry.get(kind, touchId);
6286
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6287
- }
6288
- }
6289
- return true;
6290
- }
6291
- return false;
6292
- }
6293
-
6294
- // src/browser/ui/kbd.ts
6295
- var KBD_CLASS = "bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3";
6296
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6297
- function createCommandShortcut(options = {}, children = []) {
6298
- const { class: extra, attrs, ...rest } = options;
6299
- return el(
6300
- "kbd",
6301
- {
6302
- ...rest,
6303
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6304
- attrs: { "data-slot": "command-shortcut", ...attrs }
6305
- },
6306
- children
6307
- );
6308
- }
6309
- function kbdTpl(text, className) {
6310
- return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6311
- >${text}</kbd
6312
- >`;
6313
- }
6314
- function commandShortcutTpl(text, className) {
6315
- return html`<kbd
6316
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6317
- data-slot="command-shortcut"
6318
- >${text}</kbd
6319
- >`;
6320
- }
6321
-
6322
- // src/browser/views/primitives/row.ts
6323
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6324
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6325
- function fillRowWithHandle(host, content) {
6326
- if (content.leading) host.append(content.leading);
6327
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6328
- host.append(label);
6329
- if (content.subtitle) {
6330
- host.append(
6331
- el("span", {
6332
- class: SUBTITLE_CLASS,
6333
- attrs: { "data-uidex-row-subtitle": "" },
6334
- text: content.subtitle
6335
- })
6336
- );
6337
- }
6338
- if (content.trailing != null) {
6339
- host.append(
6340
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6341
- );
6342
- }
6343
- return { label };
6344
- }
6345
- function rowTpl(content) {
6346
- return html`
6347
- ${content.leading ?? nothing}
6348
- <span class=${LABEL_CLASS}>${content.label}</span>
6349
- ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6350
- >${content.subtitle}</span
6351
- >` : nothing}
6352
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6353
- `;
6354
- }
6355
-
6356
- // src/browser/views/primitives/entity-link.ts
6357
- var ACTION_CLASS = cn(
6358
- LIST_ITEM_CLASS,
6359
- LIST_ITEM_INTERACTIVE_CLASS,
6360
- "w-full cursor-pointer text-left font-normal"
6361
- );
6362
- function entityLinkTpl(options) {
6363
- const { ctx, target, label, leading, class: extraClass } = options;
6364
- const absent = isAbsentFromPage(target);
6365
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6366
- const onClick = () => ctx.views.navigate(target);
6367
- const preview = () => {
6368
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6369
- };
6370
- const restoreParent = () => {
6371
- if (ctx.ref) {
6372
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6373
- } else {
6374
- ctx.highlight.hide();
6375
- }
6376
- };
6377
- return html`
6378
- <button
6379
- type="button"
6380
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6381
- data-slot="list-item-button"
6382
- data-uidex-entity-link
6383
- data-uidex-ref-kind=${target.kind}
6384
- data-uidex-ref-id=${target.id}
6385
- title=${absent ? "Not on this page" : ""}
6386
- @click=${onClick}
6387
- @mouseenter=${preview}
6388
- @mouseleave=${restoreParent}
6389
- @focus=${preview}
6390
- @blur=${restoreParent}
6391
- >
6392
- ${rowTpl({
6393
- leading,
6394
- label: resolvedLabel
6395
- })}
6396
- </button>
6397
- `;
6398
- }
6399
-
6400
5958
  // src/browser/views/primitives/text.ts
6401
5959
  var MUTED_CLASS = "text-sm text-muted-foreground";
6402
5960
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6408,7 +5966,20 @@ function headingTpl(text, className) {
6408
5966
  return html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6409
5967
  }
6410
5968
 
6411
- // src/browser/views/render/detail.ts
5969
+ // src/browser/views/render/detail-actions.ts
5970
+ import {
5971
+ ArchiveX,
5972
+ Camera,
5973
+ ChevronDown,
5974
+ Copy,
5975
+ Highlighter as Highlighter2,
5976
+ Inbox,
5977
+ MessageCircleWarning,
5978
+ StickyNote as StickyNote2,
5979
+ TicketPlus,
5980
+ View,
5981
+ createElement as createLucideElement5
5982
+ } from "lucide";
6412
5983
  var ICON_MAP = {
6413
5984
  "archive-x": ArchiveX,
6414
5985
  copy: Copy,
@@ -6424,23 +5995,6 @@ var ICON_MAP = {
6424
5995
  function iconFor(icon2) {
6425
5996
  return icon2 ? ICON_MAP[icon2] : null;
6426
5997
  }
6427
- function subtitleTpl(subtitle) {
6428
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6429
- return html`<p
6430
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6431
- data-uidex-detail-subtitle
6432
- >
6433
- ${text}
6434
- </p>`;
6435
- }
6436
- function notFoundTpl(ref2) {
6437
- return html`<p
6438
- class=${cn("text-muted-foreground text-sm", "p-4")}
6439
- data-uidex-detail-missing
6440
- >
6441
- ${ref2.kind}: ${ref2.id} not found in registry
6442
- </p>`;
6443
- }
6444
5998
  function renderActions(actions, ctx, root, heading) {
6445
5999
  const cleanups = [];
6446
6000
  const buttons = [];
@@ -6551,6 +6105,169 @@ function renderActions(actions, ctx, root, heading) {
6551
6105
  }
6552
6106
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6553
6107
  }
6108
+
6109
+ // src/browser/views/render/detail-sections.ts
6110
+ import {
6111
+ html as staticHtml,
6112
+ literal
6113
+ } from "lit-html/static.js";
6114
+
6115
+ // src/browser/views/builder/filter.ts
6116
+ function normalizeQuery(query) {
6117
+ return query.trim().toLowerCase();
6118
+ }
6119
+ function matchesQuery(haystack, query) {
6120
+ const q = normalizeQuery(query);
6121
+ if (!q) return true;
6122
+ return haystack.toLowerCase().includes(q);
6123
+ }
6124
+ function filterEntities(entities, query) {
6125
+ const q = normalizeQuery(query);
6126
+ if (!q) return entities;
6127
+ return entities.filter((e) => {
6128
+ const name = displayName(e).toLowerCase();
6129
+ const id = entityKey(e).toLowerCase();
6130
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6131
+ });
6132
+ }
6133
+ function filterIds(ids, query) {
6134
+ const q = normalizeQuery(query);
6135
+ if (!q) return ids;
6136
+ return ids.filter((id) => id.toLowerCase().includes(q));
6137
+ }
6138
+
6139
+ // src/browser/views/primitives/entity-presence.ts
6140
+ var TOUCH_RESOLVE_KINDS = [
6141
+ "element",
6142
+ "widget",
6143
+ "region",
6144
+ "primitive"
6145
+ ];
6146
+ function isAbsentFromPage(ref2, registry) {
6147
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6148
+ return !resolveEntityElement(ref2);
6149
+ }
6150
+ if (ref2.kind === "flow" && registry) {
6151
+ const flow = registry.get("flow", ref2.id);
6152
+ if (!flow) return true;
6153
+ for (const touchId of flow.touches) {
6154
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6155
+ const entity = registry.get(kind, touchId);
6156
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6157
+ }
6158
+ }
6159
+ return true;
6160
+ }
6161
+ return false;
6162
+ }
6163
+
6164
+ // src/browser/ui/kbd.ts
6165
+ var KBD_CLASS = "bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3";
6166
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6167
+ function createCommandShortcut(options = {}, children = []) {
6168
+ const { class: extra, attrs, ...rest } = options;
6169
+ return el(
6170
+ "kbd",
6171
+ {
6172
+ ...rest,
6173
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6174
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6175
+ },
6176
+ children
6177
+ );
6178
+ }
6179
+ function kbdTpl(text, className) {
6180
+ return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6181
+ >${text}</kbd
6182
+ >`;
6183
+ }
6184
+ function commandShortcutTpl(text, className) {
6185
+ return html`<kbd
6186
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6187
+ data-slot="command-shortcut"
6188
+ >${text}</kbd
6189
+ >`;
6190
+ }
6191
+
6192
+ // src/browser/views/primitives/row.ts
6193
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6194
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6195
+ function fillRowWithHandle(host, content) {
6196
+ if (content.leading) host.append(content.leading);
6197
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6198
+ host.append(label);
6199
+ if (content.subtitle) {
6200
+ host.append(
6201
+ el("span", {
6202
+ class: SUBTITLE_CLASS,
6203
+ attrs: { "data-uidex-row-subtitle": "" },
6204
+ text: content.subtitle
6205
+ })
6206
+ );
6207
+ }
6208
+ if (content.trailing != null) {
6209
+ host.append(
6210
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6211
+ );
6212
+ }
6213
+ return { label };
6214
+ }
6215
+ function rowTpl(content) {
6216
+ return html`
6217
+ ${content.leading ?? nothing}
6218
+ <span class=${LABEL_CLASS}>${content.label}</span>
6219
+ ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6220
+ >${content.subtitle}</span
6221
+ >` : nothing}
6222
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6223
+ `;
6224
+ }
6225
+
6226
+ // src/browser/views/primitives/entity-link.ts
6227
+ var ACTION_CLASS = cn(
6228
+ LIST_ITEM_CLASS,
6229
+ LIST_ITEM_INTERACTIVE_CLASS,
6230
+ "w-full cursor-pointer text-left font-normal"
6231
+ );
6232
+ function entityLinkTpl(options) {
6233
+ const { ctx, target, label, leading, class: extraClass } = options;
6234
+ const absent = isAbsentFromPage(target);
6235
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6236
+ const onClick = () => ctx.views.navigate(target);
6237
+ const preview = () => {
6238
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6239
+ };
6240
+ const restoreParent = () => {
6241
+ if (ctx.ref) {
6242
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6243
+ } else {
6244
+ ctx.highlight.hide();
6245
+ }
6246
+ };
6247
+ return html`
6248
+ <button
6249
+ type="button"
6250
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6251
+ data-slot="list-item-button"
6252
+ data-uidex-entity-link
6253
+ data-uidex-ref-kind=${target.kind}
6254
+ data-uidex-ref-id=${target.id}
6255
+ title=${absent ? "Not on this page" : ""}
6256
+ @click=${onClick}
6257
+ @mouseenter=${preview}
6258
+ @mouseleave=${restoreParent}
6259
+ @focus=${preview}
6260
+ @blur=${restoreParent}
6261
+ >
6262
+ ${rowTpl({
6263
+ leading,
6264
+ label: resolvedLabel
6265
+ })}
6266
+ </button>
6267
+ `;
6268
+ }
6269
+
6270
+ // src/browser/views/render/detail-sections.ts
6554
6271
  function entityListItems(ctx, entities) {
6555
6272
  return html`${entities.map((entity) => {
6556
6273
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6564,71 +6281,29 @@ function entityListItems(ctx, entities) {
6564
6281
  </li>`;
6565
6282
  })}`;
6566
6283
  }
6567
- function composesListTpl(ctx, label, entities) {
6568
- if (entities.length === 0) return null;
6569
- return html`
6570
- <section class="flex flex-col" data-uidex-detail-composes>
6571
- ${headingTpl(label)}
6572
- <ul class="flex flex-col">
6573
- ${entityListItems(ctx, entities)}
6574
- </ul>
6575
- </section>
6576
- `;
6577
- }
6578
- function usedByListTpl(ctx, label, entities) {
6579
- if (entities.length === 0) return null;
6580
- return html`
6581
- <section class="flex flex-col" data-uidex-detail-used-by>
6582
- ${headingTpl(label)}
6583
- <ul class="flex flex-col">
6584
- ${entityListItems(ctx, entities)}
6585
- </ul>
6586
- </section>
6587
- `;
6588
- }
6589
- function flowListTpl(ctx, flows) {
6590
- if (flows.length === 0) return null;
6591
- return html`
6592
- <section class="flex flex-col" data-uidex-detail-flows>
6593
- ${headingTpl(SECTION_LABELS.flows)}
6594
- <ul class="flex flex-col">
6595
- ${flows.map(
6596
- (flow) => html`<li>
6597
- ${entityLinkTpl({
6598
- ctx,
6599
- target: { kind: "flow", id: flow.id },
6600
- label: displayName(flow),
6601
- leading: kindIconTileTpl("flow")
6602
- })}
6603
- </li>`
6604
- )}
6605
- </ul>
6606
- </section>
6607
- `;
6608
- }
6609
- function touchesTpl(ctx, entities, query) {
6284
+ var DETAIL_SECTION_ATTRS = {
6285
+ composes: literal`data-uidex-detail-composes`,
6286
+ "used-by": literal`data-uidex-detail-used-by`,
6287
+ flows: literal`data-uidex-detail-flows`,
6288
+ touches: literal`data-uidex-detail-touches`
6289
+ };
6290
+ function entitySectionTpl(ctx, opts) {
6291
+ const { label, entities, dataAttr, emptyText } = opts;
6292
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6610
6293
  if (entities.length === 0) {
6611
- return html`
6612
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6613
- ${headingTpl(SECTION_LABELS.touches)}
6614
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6294
+ if (emptyText === void 0) return null;
6295
+ return staticHtml`
6296
+ <section class="flex flex-col gap-2" ${attr}>
6297
+ ${headingTpl(label)}
6298
+ ${mutedTextTpl(emptyText)}
6615
6299
  </section>
6616
6300
  `;
6617
6301
  }
6618
- return html`
6619
- <section class="flex flex-col" data-uidex-detail-touches>
6620
- ${headingTpl(SECTION_LABELS.touches)}
6302
+ return staticHtml`
6303
+ <section class="flex flex-col" ${attr}>
6304
+ ${headingTpl(label)}
6621
6305
  <ul class="flex flex-col">
6622
- ${entities.map(
6623
- (entity) => html`<li>
6624
- ${entityLinkTpl({
6625
- ctx,
6626
- target: { kind: entity.kind, id: entityKey(entity) },
6627
- label: displayName(entity),
6628
- leading: kindIconTileTpl(entity.kind)
6629
- })}
6630
- </li>`
6631
- )}
6306
+ ${entityListItems(ctx, entities)}
6632
6307
  </ul>
6633
6308
  </section>
6634
6309
  `;
@@ -6785,6 +6460,20 @@ function screenshotTpl(url) {
6785
6460
  </section>
6786
6461
  `;
6787
6462
  }
6463
+ function lazyScreenshotEl(load) {
6464
+ const holder = document.createElement("div");
6465
+ holder.hidden = true;
6466
+ void load().then(
6467
+ (url) => {
6468
+ if (!url) return;
6469
+ render(screenshotTpl(url), holder);
6470
+ holder.hidden = false;
6471
+ },
6472
+ () => {
6473
+ }
6474
+ );
6475
+ return holder;
6476
+ }
6788
6477
  function metadataListTpl(entries) {
6789
6478
  return html`
6790
6479
  <div
@@ -6815,28 +6504,30 @@ function sectionTpl(section, ctx, query) {
6815
6504
  return acceptanceChecklistTpl(section.items);
6816
6505
  }
6817
6506
  case "composes":
6818
- return composesListTpl(
6819
- ctx,
6820
- section.label,
6821
- section.filterable ? filterEntities(section.entities, query) : section.entities
6822
- );
6507
+ return entitySectionTpl(ctx, {
6508
+ label: section.label,
6509
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6510
+ dataAttr: "composes"
6511
+ });
6823
6512
  case "used-by":
6824
- return usedByListTpl(
6825
- ctx,
6826
- section.label,
6827
- section.filterable ? filterEntities(section.entities, query) : section.entities
6828
- );
6513
+ return entitySectionTpl(ctx, {
6514
+ label: section.label,
6515
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6516
+ dataAttr: "used-by"
6517
+ });
6829
6518
  case "flows":
6830
- return flowListTpl(
6831
- ctx,
6832
- section.filterable ? filterEntities(section.flows, query) : section.flows
6833
- );
6519
+ return entitySectionTpl(ctx, {
6520
+ label: SECTION_LABELS.flows,
6521
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6522
+ dataAttr: "flows"
6523
+ });
6834
6524
  case "touches":
6835
- return touchesTpl(
6836
- ctx,
6837
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6838
- query
6839
- );
6525
+ return entitySectionTpl(ctx, {
6526
+ label: SECTION_LABELS.touches,
6527
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6528
+ dataAttr: "touches",
6529
+ emptyText: query ? "No matches" : "No entities touched"
6530
+ });
6840
6531
  case "steps":
6841
6532
  return stepsTpl(
6842
6533
  ctx,
@@ -6848,11 +6539,32 @@ function sectionTpl(section, ctx, query) {
6848
6539
  section.filterable ? filterIds(section.paths, query) : section.paths
6849
6540
  );
6850
6541
  case "screenshot":
6851
- return screenshotTpl(section.url);
6542
+ if (section.url) return screenshotTpl(section.url);
6543
+ if (section.load) return html`${lazyScreenshotEl(section.load)}`;
6544
+ return null;
6852
6545
  case "metadata":
6853
6546
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6854
6547
  }
6855
6548
  }
6549
+
6550
+ // src/browser/views/render/detail.ts
6551
+ function subtitleTpl(subtitle) {
6552
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6553
+ return html`<p
6554
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6555
+ data-uidex-detail-subtitle
6556
+ >
6557
+ ${text}
6558
+ </p>`;
6559
+ }
6560
+ function notFoundTpl(ref2) {
6561
+ return html`<p
6562
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6563
+ data-uidex-detail-missing
6564
+ >
6565
+ ${ref2.kind}: ${ref2.id} not found in registry
6566
+ </p>`;
6567
+ }
6856
6568
  function hasFilterableSections(sections) {
6857
6569
  return sections.some((s) => "filterable" in s && s.filterable === true);
6858
6570
  }
@@ -6917,7 +6629,10 @@ function renderDetailSurface(surface, ctx, root) {
6917
6629
  >
6918
6630
  ${surface.title}
6919
6631
  </h2>` : nothing}
6920
- <span class="ml-auto">${kindBadgeTpl(surface.entityKind)}</span>
6632
+ <span class="ml-auto flex items-center gap-1">
6633
+ ${surface.unregistered ? badgeTpl("Unregistered", { variant: "warning", size: "sm" }) : nothing}
6634
+ ${kindBadgeTpl(surface.entityKind)}
6635
+ </span>
6921
6636
  </div>
6922
6637
  ${surface.subtitle ? subtitleTpl(surface.subtitle) : nothing}
6923
6638
  <div
@@ -7210,6 +6925,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7210
6925
  };
7211
6926
  }
7212
6927
 
6928
+ // src/browser/ui/button.ts
6929
+ var buttonBase = "focus-visible:ring-ring focus-visible:ring-offset-background disabled:opacity-60 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border text-sm font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg]:pointer-events-none [&_svg]:shrink-0";
6930
+ var buttonVariants = cva(buttonBase, {
6931
+ defaultVariants: { size: "default", variant: "default" },
6932
+ variants: {
6933
+ size: {
6934
+ default: "h-8 px-3",
6935
+ sm: "h-7 gap-1.5 px-2.5",
6936
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6937
+ lg: "h-9 px-3.5",
6938
+ xl: "h-10 px-4 text-base",
6939
+ icon: "size-8",
6940
+ "icon-sm": "size-7",
6941
+ "icon-lg": "size-9",
6942
+ "icon-xs": "size-6 rounded-md"
6943
+ },
6944
+ variant: {
6945
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6946
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6947
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6948
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6949
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6950
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6951
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6952
+ }
6953
+ }
6954
+ });
6955
+
7213
6956
  // src/browser/views/render/form.ts
7214
6957
  var fieldSeq = 0;
7215
6958
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7406,9 +7149,9 @@ function iconMediaTpl(iconTpl) {
7406
7149
  </div>
7407
7150
  `;
7408
7151
  }
7409
- function loadingViewTpl() {
7152
+ function loadingViewTpl(rootRef) {
7410
7153
  return html`
7411
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7154
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${ref(rootRef)}>
7412
7155
  ${iconMediaTpl(icon(Loader2, "animate-spin"))}
7413
7156
  <div class="flex max-w-sm flex-col items-center text-center">
7414
7157
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7421,42 +7164,47 @@ function loadingViewTpl() {
7421
7164
  </div>
7422
7165
  `;
7423
7166
  }
7424
- function successViewTpl() {
7167
+ function successViewTpl(refs) {
7425
7168
  return html`
7426
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7169
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${ref(refs.root)}>
7427
7170
  ${iconMediaTpl(icon(CircleCheck))}
7428
7171
  <div class="flex max-w-sm flex-col items-center text-center">
7429
7172
  <div
7430
7173
  class="font-heading text-xl font-semibold"
7431
7174
  data-uidex-success-title
7175
+ ${ref(refs.title)}
7432
7176
  ></div>
7433
7177
  </div>
7434
7178
  <div
7435
7179
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7436
7180
  data-uidex-success-actions
7181
+ ${ref(refs.actions)}
7437
7182
  >
7438
7183
  <a
7439
7184
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7440
7185
  target="_blank"
7441
7186
  rel="noreferrer"
7442
7187
  data-uidex-success-link
7188
+ ${ref(refs.link)}
7443
7189
  ></a>
7444
7190
  </div>
7445
7191
  </div>
7446
7192
  `;
7447
7193
  }
7448
- function errorViewTpl() {
7194
+ function errorViewTpl(refs) {
7449
7195
  return html`
7450
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7196
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${ref(refs.root)}>
7451
7197
  ${iconMediaTpl(icon(CircleX))}
7452
7198
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7453
7199
  <div
7454
7200
  class="font-heading text-xl font-semibold"
7455
7201
  data-uidex-error-title
7202
+ ${ref(refs.title)}
7456
7203
  ></div>
7457
7204
  <p
7458
7205
  class="text-muted-foreground text-sm"
7459
7206
  data-uidex-error-description
7207
+ ${ref(refs.description)}
7460
7208
  ></p>
7461
7209
  </div>
7462
7210
  <div
@@ -7467,6 +7215,7 @@ function errorViewTpl() {
7467
7215
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7468
7216
  data-slot="button"
7469
7217
  data-uidex-retry-button
7218
+ ${ref(refs.retry)}
7470
7219
  >
7471
7220
  Try Again
7472
7221
  </button>
@@ -7508,8 +7257,15 @@ function renderFormSurface(surface, ctx, root) {
7508
7257
  });
7509
7258
  }
7510
7259
  const formRef = createRef();
7511
- const statusRef = createRef();
7512
- const submitRef = createRef();
7260
+ const loadingRef = createRef();
7261
+ const successRef = createRef();
7262
+ const successTitleRef = createRef();
7263
+ const successLinkRef = createRef();
7264
+ const successActionsRef = createRef();
7265
+ const errorRef = createRef();
7266
+ const errorTitleRef = createRef();
7267
+ const errorDescriptionRef = createRef();
7268
+ const retryRef = createRef();
7513
7269
  render(
7514
7270
  html`
7515
7271
  <section
@@ -7524,7 +7280,19 @@ function renderFormSurface(surface, ctx, root) {
7524
7280
  novalidate
7525
7281
  data-uidex-form=${surface.id}
7526
7282
  ></form>
7527
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7283
+ ${loadingViewTpl(loadingRef)}
7284
+ ${successViewTpl({
7285
+ root: successRef,
7286
+ title: successTitleRef,
7287
+ link: successLinkRef,
7288
+ actions: successActionsRef
7289
+ })}
7290
+ ${errorViewTpl({
7291
+ root: errorRef,
7292
+ title: errorTitleRef,
7293
+ description: errorDescriptionRef,
7294
+ retry: retryRef
7295
+ })}
7528
7296
  </section>
7529
7297
  `,
7530
7298
  root
@@ -7554,32 +7322,15 @@ function renderFormSurface(surface, ctx, root) {
7554
7322
  }
7555
7323
  });
7556
7324
  form.append(status);
7557
- const sectionEl = root.querySelector(
7558
- "[data-uidex-form-surface]"
7559
- );
7560
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7561
- const successView = sectionEl.querySelector(
7562
- "[data-uidex-success-view]"
7563
- );
7564
- const successTitle = sectionEl.querySelector(
7565
- "[data-uidex-success-title]"
7566
- );
7567
- const successLink = sectionEl.querySelector(
7568
- "[data-uidex-success-link]"
7569
- );
7570
- const successActions = successLink.parentElement;
7571
- const errorView = sectionEl.querySelector(
7572
- "[data-uidex-error-view]"
7573
- );
7574
- const errorTitle = sectionEl.querySelector(
7575
- "[data-uidex-error-title]"
7576
- );
7577
- const errorDescription = sectionEl.querySelector(
7578
- "[data-uidex-error-description]"
7579
- );
7580
- const retryButton = sectionEl.querySelector(
7581
- "[data-uidex-retry-button]"
7582
- );
7325
+ const loadingView = loadingRef.value;
7326
+ const successView = successRef.value;
7327
+ const successTitle = successTitleRef.value;
7328
+ const successLink = successLinkRef.value;
7329
+ const successActions = successActionsRef.value;
7330
+ const errorView = errorRef.value;
7331
+ const errorTitle = errorTitleRef.value;
7332
+ const errorDescription = errorDescriptionRef.value;
7333
+ const retryButton = retryRef.value;
7583
7334
  function clearAllFieldErrors() {
7584
7335
  for (const f of fields) f.setError(null);
7585
7336
  }
@@ -7980,7 +7731,7 @@ function defaultFilter(item, query) {
7980
7731
  function rowTag(item) {
7981
7732
  return item.tag ?? item.value;
7982
7733
  }
7983
- function resolveLeadingTpl(item, ctx) {
7734
+ function resolveLeadingTpl(item) {
7984
7735
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
7985
7736
  if (item.leading) {
7986
7737
  const node = item.leading();
@@ -8085,7 +7836,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8085
7836
  data-uidex-item-value=${item.value}
8086
7837
  >
8087
7838
  ${rowTpl({
8088
- leading: resolveLeadingTpl(item, ctx),
7839
+ leading: resolveLeadingTpl(item),
8089
7840
  label: item.label,
8090
7841
  subtitle: item.subtitle,
8091
7842
  trailing: item.trailing ?? item.shortcut
@@ -8130,7 +7881,7 @@ function renderListSurface(surface, ctx, root) {
8130
7881
  return root.ownerDocument ?? document;
8131
7882
  };
8132
7883
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8133
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7884
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8134
7885
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8135
7886
  const controller = createListController({
8136
7887
  surfaceId: surface.id,
@@ -8826,52 +8577,16 @@ function sameRef2(a, b) {
8826
8577
  if (a === null || b === null) return false;
8827
8578
  return a.kind === b.kind && a.id === b.id;
8828
8579
  }
8829
- function resolveHints(view, ctx) {
8830
- const h = view.hints;
8831
- if (!h) return [];
8832
- if (typeof h === "function") {
8833
- try {
8834
- return h(ctx) ?? [];
8835
- } catch (err) {
8836
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8837
- return [];
8838
- }
8839
- }
8840
- return h;
8841
- }
8842
- function resolveTitle(view, ctx) {
8843
- const t = view.title;
8844
- if (!t) return "";
8845
- if (typeof t === "function") {
8846
- try {
8847
- return t(ctx) ?? "";
8848
- } catch (err) {
8849
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8850
- return "";
8851
- }
8852
- }
8853
- return t;
8854
- }
8855
- function resolveActions(view, ctx, globalActions) {
8856
- const result = [];
8857
- const fn = view.actions;
8858
- if (fn) {
8859
- try {
8860
- const viewActions2 = fn(ctx) ?? [];
8861
- result.push(...viewActions2);
8862
- } catch (err) {
8863
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8864
- }
8865
- }
8866
- if (globalActions) {
8580
+ function resolveProp(view, ctx, value, propName, fallback) {
8581
+ if (typeof value === "function") {
8867
8582
  try {
8868
- const globals = globalActions(ctx) ?? [];
8869
- result.push(...globals);
8583
+ return value(ctx) ?? fallback;
8870
8584
  } catch (err) {
8871
- console.error(`[uidex] globalActions() threw`, err);
8585
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8586
+ return fallback;
8872
8587
  }
8873
8588
  }
8874
- return result;
8589
+ return value ?? fallback;
8875
8590
  }
8876
8591
  function createViewStack(options) {
8877
8592
  const { container, views, session, registry, highlight } = options;
@@ -8939,39 +8654,40 @@ function createViewStack(options) {
8939
8654
  color: KIND_STYLE[top.ctx.ref.kind].color
8940
8655
  });
8941
8656
  }
8942
- function updateChrome() {
8943
- if (!shell) return;
8944
- const top = mounted[mounted.length - 1];
8945
- if (!top) return;
8657
+ function updateNavButtons(shell2, top) {
8946
8658
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8947
- shell.backBtn.hidden = atRoot;
8948
- shell.searchIcon.hidden = !atRoot;
8659
+ shell2.backBtn.hidden = atRoot;
8660
+ shell2.searchIcon.hidden = !atRoot;
8661
+ }
8662
+ function updateTitle(shell2, top) {
8949
8663
  const searchable = top.view.searchable !== false;
8950
- shell.searchInput.hidden = !searchable;
8951
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8952
- shell.headerTitle.textContent = titleText;
8953
- shell.headerTitle.hidden = searchable || !titleText;
8954
- shell.footerLeft.replaceChildren();
8664
+ shell2.searchInput.hidden = !searchable;
8665
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8666
+ shell2.headerTitle.textContent = titleText;
8667
+ shell2.headerTitle.hidden = searchable || !titleText;
8668
+ }
8669
+ function updateFooterChip(shell2, top) {
8670
+ shell2.footerLeft.replaceChildren();
8955
8671
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8956
8672
  if (refEntity) {
8957
- shell.footerLeft.append(
8673
+ shell2.footerLeft.append(
8958
8674
  renderKindChip({
8959
8675
  entity: refEntity,
8960
8676
  withKindName: true
8961
8677
  })
8962
8678
  );
8963
8679
  } else {
8964
- shell.footerLeft.append(shell.logo);
8680
+ shell2.footerLeft.append(shell2.logo);
8965
8681
  }
8966
- hintChangeSub?.();
8967
- hintChangeSub = null;
8968
- highlightActionsSub?.();
8969
- highlightActionsSub = null;
8970
- shell.footerRight.replaceChildren();
8971
- const footerItems = [];
8972
- const enterHint = resolveHints(top.view, top.ctx).find(
8973
- (h) => h.key.includes("\u21B5")
8974
- );
8682
+ }
8683
+ function buildEnterHint(top, footerItems) {
8684
+ const enterHint = resolveProp(
8685
+ top.view,
8686
+ top.ctx,
8687
+ top.view.hints,
8688
+ "hints",
8689
+ []
8690
+ ).find((h) => h.key.includes("\u21B5"));
8975
8691
  const src = top.mounted.submitIntent;
8976
8692
  if (src) {
8977
8693
  const key = enterHint?.key ?? "\u21B5";
@@ -8992,28 +8708,52 @@ function createViewStack(options) {
8992
8708
  } else if (enterHint) {
8993
8709
  footerItems.push(createHint(enterHint.key, enterHint.label));
8994
8710
  }
8711
+ }
8712
+ function buildActions(shell2, top, footerItems) {
8995
8713
  const actionsDivider = createFooterDivider();
8996
- const viewActions2 = resolveActions(top.view, top.ctx);
8714
+ const viewActions2 = resolveProp(
8715
+ top.view,
8716
+ top.ctx,
8717
+ top.view.actions,
8718
+ "actions",
8719
+ []
8720
+ );
8997
8721
  const hlSrc = top.mounted.highlightActions;
8998
8722
  const syncActions = () => {
8999
8723
  const hlActions = hlSrc?.get() ?? [];
9000
8724
  const combined = [...hlActions, ...viewActions2];
9001
- shell.actionsPopup.setActions(combined);
8725
+ shell2.actionsPopup.setActions(combined);
9002
8726
  const visible = combined.length > 0;
9003
- shell.actionsPopup.trigger.hidden = !visible;
8727
+ shell2.actionsPopup.trigger.hidden = !visible;
9004
8728
  actionsDivider.hidden = !visible || footerItems.length === 0;
9005
8729
  };
9006
8730
  if (hlSrc) {
9007
8731
  highlightActionsSub = hlSrc.subscribe(syncActions);
9008
8732
  }
9009
8733
  for (let i = 0; i < footerItems.length; i++) {
9010
- if (i > 0) shell.footerRight.append(createFooterDivider());
9011
- shell.footerRight.append(footerItems[i]);
8734
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8735
+ shell2.footerRight.append(footerItems[i]);
9012
8736
  }
9013
- shell.footerRight.append(actionsDivider);
9014
- shell.footerRight.append(shell.actionsPopup.trigger);
8737
+ shell2.footerRight.append(actionsDivider);
8738
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
9015
8739
  syncActions();
9016
8740
  }
8741
+ function updateChrome() {
8742
+ if (!shell) return;
8743
+ const top = mounted[mounted.length - 1];
8744
+ if (!top) return;
8745
+ updateNavButtons(shell, top);
8746
+ updateTitle(shell, top);
8747
+ updateFooterChip(shell, top);
8748
+ hintChangeSub?.();
8749
+ hintChangeSub = null;
8750
+ highlightActionsSub?.();
8751
+ highlightActionsSub = null;
8752
+ shell.footerRight.replaceChildren();
8753
+ const footerItems = [];
8754
+ buildEnterHint(top, footerItems);
8755
+ buildActions(shell, top, footerItems);
8756
+ }
9017
8757
  function render2() {
9018
8758
  if (!container.isConnected) return;
9019
8759
  const stack = session.getState().stack;
@@ -9110,7 +8850,7 @@ function createViewStack(options) {
9110
8850
 
9111
8851
  // src/browser/views/built-in/ids.ts
9112
8852
  var BUILT_IN_VIEW_IDS = {
9113
- archiveReason: "archive-reason",
8853
+ closeReason: "close-reason",
9114
8854
  commandPalette: "command-palette",
9115
8855
  elements: "elements",
9116
8856
  entityReports: "entity-reports",
@@ -9161,7 +8901,7 @@ function parentDetail(ref2) {
9161
8901
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9162
8902
  }
9163
8903
 
9164
- // src/browser/views/built-in/archive-reason.ts
8904
+ // src/browser/views/built-in/close-reason.ts
9165
8905
  import {
9166
8906
  Ban,
9167
8907
  BugOff,
@@ -9171,10 +8911,10 @@ import {
9171
8911
  createElement as createLucideElement6
9172
8912
  } from "lucide";
9173
8913
  var pendingReportId = null;
9174
- var afterArchive = null;
9175
- function setArchiveTarget(reportId, onDone) {
8914
+ var afterClose = null;
8915
+ function setCloseTarget(reportId, onDone) {
9176
8916
  pendingReportId = reportId;
9177
- afterArchive = onDone;
8917
+ afterClose = onDone;
9178
8918
  }
9179
8919
  function leadingIcon(iconNode) {
9180
8920
  return () => {
@@ -9189,16 +8929,16 @@ function spinnerTile() {
9189
8929
  return createIconTile(spinner);
9190
8930
  }
9191
8931
  var REASONS = [
9192
- { value: "fixed", label: "Fixed", icon: CircleCheck2 },
8932
+ { value: "fixed", label: "Resolved", icon: CircleCheck2 },
9193
8933
  { value: "not_a_bug", label: "Not a bug", icon: BugOff },
9194
8934
  { value: "duplicate", label: "Duplicate", icon: Copy2 },
9195
8935
  { value: "wont_fix", label: "Won't fix", icon: Ban }
9196
8936
  ];
9197
- var archiveReasonView = {
9198
- id: BUILT_IN_VIEW_IDS.archiveReason,
8937
+ var closeReasonView = {
8938
+ id: BUILT_IN_VIEW_IDS.closeReason,
9199
8939
  matches: () => false,
9200
8940
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9201
- title: "Archive reason",
8941
+ title: "Close reason",
9202
8942
  searchable: false,
9203
8943
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9204
8944
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9211,12 +8951,12 @@ var archiveReasonView = {
9211
8951
  }));
9212
8952
  const surface = {
9213
8953
  kind: "list",
9214
- id: "uidex-archive-reason",
8954
+ id: "uidex-close-reason",
9215
8955
  searchable: false,
9216
8956
  items,
9217
8957
  emptyLabel: "No reasons available",
9218
8958
  onSelect: async (item) => {
9219
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8959
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9220
8960
  busy = true;
9221
8961
  const row = root.querySelector(
9222
8962
  `[data-uidex-item-value="${item.value}"]`
@@ -9228,13 +8968,10 @@ var archiveReasonView = {
9228
8968
  row.style.pointerEvents = "none";
9229
8969
  }
9230
8970
  try {
9231
- await ctx.registry.archiveReport(
9232
- pendingReportId,
9233
- item.value
9234
- );
9235
- const done = afterArchive;
8971
+ await ctx.registry.closeReport(pendingReportId, item.value);
8972
+ const done = afterClose;
9236
8973
  pendingReportId = null;
9237
- afterArchive = null;
8974
+ afterClose = null;
9238
8975
  done?.();
9239
8976
  } catch {
9240
8977
  busy = false;
@@ -9252,7 +8989,7 @@ var archiveReasonView = {
9252
8989
  }
9253
8990
  const error = document.createElement("p");
9254
8991
  error.className = "text-destructive px-4 py-2 text-xs";
9255
- error.textContent = "Archive failed \u2014 try again";
8992
+ error.textContent = "Close failed \u2014 try again";
9256
8993
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9257
8994
  setTimeout(() => error.remove(), 3e3);
9258
8995
  }
@@ -9579,7 +9316,8 @@ function createCommandPaletteView(shortcut) {
9579
9316
  }
9580
9317
 
9581
9318
  // src/browser/internal/screenshot.ts
9582
- import { domToPng } from "modern-screenshot";
9319
+ import { domToPng, domToWebp } from "modern-screenshot";
9320
+ var WEBP_QUALITY = 0.85;
9583
9321
  function resolveBackgroundColor(el2) {
9584
9322
  let node = el2;
9585
9323
  while (node) {
@@ -9593,8 +9331,7 @@ function isUidexChrome(node) {
9593
9331
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9594
9332
  const el2 = node;
9595
9333
  if (el2.shadowRoot !== null) return true;
9596
- const cls = el2.classList;
9597
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9334
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9598
9335
  }
9599
9336
  async function captureScreenshot(options = {}) {
9600
9337
  if (typeof document === "undefined") {
@@ -9606,8 +9343,10 @@ async function captureScreenshot(options = {}) {
9606
9343
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9607
9344
  const padding = 16;
9608
9345
  const height = target.scrollHeight || target.clientHeight;
9609
- return domToPng(target, {
9346
+ const encode = options.format === "png" ? domToPng : domToWebp;
9347
+ return encode(target, {
9610
9348
  scale,
9349
+ quality: WEBP_QUALITY,
9611
9350
  width: width + padding * 2,
9612
9351
  height: height + padding * 2,
9613
9352
  backgroundColor: resolveBackgroundColor(target),
@@ -9663,12 +9402,6 @@ function collectFlowsTouching(ctx, targetId) {
9663
9402
  }
9664
9403
 
9665
9404
  // src/browser/views/builder/detail-builder.ts
9666
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9667
- "element",
9668
- "region",
9669
- "widget",
9670
- "primitive"
9671
- ]);
9672
9405
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9673
9406
  function copyPathAction(ref2, loc) {
9674
9407
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9770,7 +9503,10 @@ function copyScreenshotAction(ref2) {
9770
9503
  const target = resolveEntityElement(ref2) ?? void 0;
9771
9504
  const dataUrl = await captureScreenshot({
9772
9505
  target,
9773
- maxWidth: 1280
9506
+ maxWidth: 1280,
9507
+ // Clipboard write requires PNG — the async Clipboard API rejects
9508
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9509
+ format: "png"
9774
9510
  });
9775
9511
  const res = await fetch(dataUrl);
9776
9512
  const blob = await res.blob();
@@ -9803,12 +9539,11 @@ function createEntityDetailView(config) {
9803
9539
  if (!ctx.ref || ctx.ref.kind !== kind) {
9804
9540
  return { kind: "detail", entityKind: kind };
9805
9541
  }
9806
- const entity = ctx.registry.get(kind, ctx.ref.id);
9807
- if (!entity) {
9808
- return { kind: "detail", entityKind: kind, notFound: ctx.ref };
9809
- }
9810
- const metaEntity = entity;
9811
- const meta = metaEntity.meta;
9542
+ const exactEntity = ctx.registry.get(kind, ctx.ref.id);
9543
+ const patternEntity = exactEntity ? void 0 : ctx.registry.matchPattern?.(kind, ctx.ref.id);
9544
+ const entity = exactEntity ?? patternEntity;
9545
+ const metaEntity = entity ? entity : null;
9546
+ const meta = metaEntity?.meta;
9812
9547
  const actions = [];
9813
9548
  const cloud = ctx.cloud;
9814
9549
  actions.push({ ...reportAction(ctx.ref), group: "Report" });
@@ -9822,18 +9557,20 @@ function createEntityDetailView(config) {
9822
9557
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9823
9558
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9824
9559
  }
9825
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9560
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9826
9561
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9827
9562
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9828
9563
  }
9829
- actions.push({
9830
- ...copyPathAction(ctx.ref, metaEntity.loc),
9831
- group: "Inspect"
9832
- });
9833
- actions.push({
9834
- ...copySnapshotAction(ctx.ref, metaEntity.loc),
9835
- group: "Inspect"
9836
- });
9564
+ if (metaEntity?.loc) {
9565
+ actions.push({
9566
+ ...copyPathAction(ctx.ref, metaEntity.loc),
9567
+ group: "Inspect"
9568
+ });
9569
+ actions.push({
9570
+ ...copySnapshotAction(ctx.ref, metaEntity.loc),
9571
+ group: "Inspect"
9572
+ });
9573
+ }
9837
9574
  const sections = [];
9838
9575
  if (meta?.description) {
9839
9576
  sections.push({ id: "description", text: meta.description });
@@ -9841,10 +9578,12 @@ function createEntityDetailView(config) {
9841
9578
  if (offerAcceptance && meta?.acceptance?.length) {
9842
9579
  sections.push({ id: "acceptance", items: meta.acceptance });
9843
9580
  }
9844
- for (const s of config.extraSections?.(ctx, entity) ?? []) {
9845
- sections.push(s);
9581
+ if (entity) {
9582
+ for (const s of config.extraSections?.(ctx, entity) ?? []) {
9583
+ sections.push(s);
9584
+ }
9846
9585
  }
9847
- if (!DOM_BACKED_KINDS2.has(kind)) {
9586
+ if (!DOM_BACKED_KINDS.has(kind)) {
9848
9587
  sections.push({
9849
9588
  id: "composes",
9850
9589
  label: SECTION_LABELS.contains,
@@ -9852,7 +9591,7 @@ function createEntityDetailView(config) {
9852
9591
  filterable: true
9853
9592
  });
9854
9593
  }
9855
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9594
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9856
9595
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9857
9596
  if (featureEntities.length > 0) {
9858
9597
  sections.push({
@@ -9871,8 +9610,9 @@ function createEntityDetailView(config) {
9871
9610
  return {
9872
9611
  kind: "detail",
9873
9612
  entityKind: kind,
9874
- title: displayName(metaEntity),
9875
- subtitle: config.subtitle?.(ctx, entity),
9613
+ title: patternEntity ? ctx.ref.id : metaEntity ? displayName(metaEntity) : ctx.ref.id,
9614
+ subtitle: exactEntity ? config.subtitle?.(ctx, exactEntity) : void 0,
9615
+ unregistered: !entity,
9876
9616
  actions,
9877
9617
  sections
9878
9618
  };
@@ -9881,35 +9621,14 @@ function createEntityDetailView(config) {
9881
9621
  }
9882
9622
 
9883
9623
  // src/browser/views/built-in/entity-detail.ts
9884
- function collectDomParents(ctx, ref2) {
9885
- const el2 = resolveEntityElement(ref2);
9886
- if (!el2) return [];
9887
- const parents = [];
9888
- const seen = /* @__PURE__ */ new Set();
9889
- let node = el2.parentElement;
9890
- while (node) {
9891
- if (node instanceof HTMLElement) {
9892
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9893
- const id = node.getAttribute(attr);
9894
- if (id) {
9895
- const key = `${kind}:${id}`;
9896
- if (!seen.has(key)) {
9897
- seen.add(key);
9898
- const entity = ctx.registry.get(kind, id);
9899
- if (entity) parents.push(entity);
9900
- }
9901
- }
9902
- }
9903
- }
9904
- node = node.parentElement;
9905
- }
9906
- return parents;
9624
+ function collectFeatureConsumers(ctx, featureId) {
9625
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9907
9626
  }
9908
- function usedBySection(ctx, ref2) {
9627
+ function usedBySection(ctx, featureId) {
9909
9628
  return {
9910
9629
  id: "used-by",
9911
9630
  label: SECTION_LABELS.usedBy,
9912
- entities: collectDomParents(ctx, ref2),
9631
+ entities: collectFeatureConsumers(ctx, featureId),
9913
9632
  filterable: true
9914
9633
  };
9915
9634
  }
@@ -9930,9 +9649,7 @@ var featureDetailView = createEntityDetailView({
9930
9649
  id: "feature-detail",
9931
9650
  kind: "feature",
9932
9651
  fallbackTitle: "Feature",
9933
- extraSections: (ctx, entity) => [
9934
- usedBySection(ctx, { kind: "feature", id: entity.id })
9935
- ]
9652
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9936
9653
  });
9937
9654
  var regionDetailView = createEntityDetailView({
9938
9655
  id: "region-detail",
@@ -10060,6 +9777,15 @@ var reportDetailView = {
10060
9777
  }
10061
9778
  if (report.screenshot) {
10062
9779
  sections.push({ id: "screenshot", url: report.screenshot });
9780
+ } else {
9781
+ const pins = ctx.cloud?.pins;
9782
+ const fetchScreenshot = pins?.screenshot;
9783
+ if (pins && fetchScreenshot) {
9784
+ sections.push({
9785
+ id: "screenshot",
9786
+ load: () => fetchScreenshot.call(pins, report.id)
9787
+ });
9788
+ }
10063
9789
  }
10064
9790
  const metaEntries = [];
10065
9791
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -10070,14 +9796,14 @@ var reportDetailView = {
10070
9796
  sections.push({ id: "metadata", entries: metaEntries });
10071
9797
  }
10072
9798
  const actions = [];
10073
- if (ctx.registry.archiveReport) {
9799
+ if (ctx.registry.closeReport) {
10074
9800
  actions.push({
10075
- id: "archive",
10076
- label: "Archive",
9801
+ id: "close",
9802
+ label: "Close",
10077
9803
  icon: "archive-x",
10078
9804
  run: () => {
10079
- setArchiveTarget(report.id, () => ctx.pop());
10080
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9805
+ setCloseTarget(report.id, () => ctx.pop());
9806
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10081
9807
  }
10082
9808
  });
10083
9809
  }
@@ -10115,13 +9841,13 @@ function reportToItem(report, ctx) {
10115
9841
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10116
9842
  const kind = ctx.ref?.kind ?? "element";
10117
9843
  const actions = [];
10118
- if (ctx.registry.archiveReport) {
9844
+ if (ctx.registry.closeReport) {
10119
9845
  actions.push({
10120
- id: `archive-${report.id}`,
10121
- label: "Archive",
9846
+ id: `close-${report.id}`,
9847
+ label: "Close",
10122
9848
  perform: () => {
10123
- setArchiveTarget(report.id, () => ctx.pop());
10124
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9849
+ setCloseTarget(report.id, () => ctx.pop());
9850
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10125
9851
  }
10126
9852
  });
10127
9853
  }
@@ -10274,7 +10000,7 @@ function capitalize2(s) {
10274
10000
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10275
10001
  }
10276
10002
  function renderPayloadMarkdown(payload) {
10277
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
10003
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10278
10004
  const ctx = payload.context;
10279
10005
  const lines = [
10280
10006
  `# ${heading}`,
@@ -10343,12 +10069,6 @@ var reportFields = [
10343
10069
  ];
10344
10070
 
10345
10071
  // src/browser/views/built-in/report/view-builder.ts
10346
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10347
- "element",
10348
- "region",
10349
- "widget",
10350
- "primitive"
10351
- ]);
10352
10072
  var KIND_TO_ATTR = new Map(
10353
10073
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10354
10074
  );
@@ -10419,7 +10139,7 @@ function createReportView(config) {
10419
10139
  const cloud = resolveCloud(ctx);
10420
10140
  const extra = extraFields ? extraFields(ctx) : [];
10421
10141
  const fields = [...extra, ...baseFields ?? reportFields];
10422
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10142
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10423
10143
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10424
10144
  const screenshotPromise = isDomBacked ? captureScreenshot({
10425
10145
  target: targetEl ?? void 0,
@@ -11002,7 +10722,7 @@ var pinSettingsView = {
11002
10722
  // src/browser/views/built-in/index.ts
11003
10723
  function buildDefaultViews(shortcut) {
11004
10724
  return [
11005
- archiveReasonView,
10725
+ closeReasonView,
11006
10726
  createCommandPaletteView(shortcut),
11007
10727
  explorePageView,
11008
10728
  componentDetailView,
@@ -11030,6 +10750,27 @@ function buildDefaultViews(shortcut) {
11030
10750
  }
11031
10751
 
11032
10752
  // src/browser/create-uidex.ts
10753
+ function getVisibleEntities() {
10754
+ if (typeof document === "undefined") return [];
10755
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10756
+ const nodes = document.querySelectorAll(selector);
10757
+ const ids = [];
10758
+ const seen = /* @__PURE__ */ new Set();
10759
+ for (const node of nodes) {
10760
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10761
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10762
+ const id = node.getAttribute(attr);
10763
+ if (!id) continue;
10764
+ const key = `${kind}:${id}`;
10765
+ if (!seen.has(key)) {
10766
+ seen.add(key);
10767
+ ids.push(key);
10768
+ }
10769
+ break;
10770
+ }
10771
+ }
10772
+ return ids;
10773
+ }
11033
10774
  function createUidex(options = {}) {
11034
10775
  const registry = createRegistry();
11035
10776
  const inspectorRef = { current: null };
@@ -11064,7 +10805,7 @@ function createUidex(options = {}) {
11064
10805
  const views = createRouter({ session });
11065
10806
  const cloud = options.cloud ?? null;
11066
10807
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
11067
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10808
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
11068
10809
  if (options.defaultViews !== false) {
11069
10810
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
11070
10811
  }
@@ -11074,6 +10815,94 @@ function createUidex(options = {}) {
11074
10815
  let shadowRoot = null;
11075
10816
  const mountCleanup = createCleanupStack();
11076
10817
  let mounted = false;
10818
+ function syncReportsToRegistry() {
10819
+ const layer = pinLayerRef.current;
10820
+ if (!layer) return;
10821
+ const byEntity = /* @__PURE__ */ new Map();
10822
+ for (const pin of layer.getAllPins()) {
10823
+ const cid = pin.entity ?? "";
10824
+ if (!cid) continue;
10825
+ const list = byEntity.get(cid);
10826
+ if (list) list.push(pin);
10827
+ else byEntity.set(cid, [pin]);
10828
+ }
10829
+ for (const [cid, pins] of byEntity) {
10830
+ const ref2 = parseComponentRef(cid);
10831
+ registry.setReports(ref2.kind, ref2.id, pins);
10832
+ }
10833
+ }
10834
+ function setupKeyBindings(root, viewStack) {
10835
+ const bindings = bindShadowKeys({
10836
+ shadowRoot: root,
10837
+ session,
10838
+ getActionsPopup: () => viewStack.getActionsPopup(),
10839
+ getPinLayer: () => pinLayerRef.current,
10840
+ shortcut: options.shortcut
10841
+ });
10842
+ mountCleanup.add(bindings);
10843
+ return bindings;
10844
+ }
10845
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10846
+ if (cloud) {
10847
+ registry.closeReport = async (reportId, status) => {
10848
+ pinLayerRef.current?.removePin(reportId);
10849
+ await cloud.pins.close(
10850
+ reportId,
10851
+ status
10852
+ );
10853
+ syncReportsToRegistry();
10854
+ };
10855
+ }
10856
+ const pinLayer = createPinLayer({
10857
+ container: root,
10858
+ onOpenPinDetail: (componentId, reportId) => {
10859
+ const ref2 = parseComponentRef(componentId);
10860
+ setSelectedReportId(reportId);
10861
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10862
+ session.mode.transition.enterViewing(views.buildStack(entry));
10863
+ },
10864
+ onHoverPin: (anchor, componentId) => {
10865
+ const overlay = overlayRef.current;
10866
+ if (!overlay) return;
10867
+ if (!anchor || !componentId) {
10868
+ overlay.hide();
10869
+ return;
10870
+ }
10871
+ overlay.show(anchor, {
10872
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10873
+ });
10874
+ },
10875
+ onPinsChanged: syncReportsToRegistry
10876
+ });
10877
+ shell.menuBar.setPinLayer(pinLayer);
10878
+ pinLayerRef.current = pinLayer;
10879
+ mountCleanup.add(() => {
10880
+ pinLayer.destroy();
10881
+ pinLayerRef.current = null;
10882
+ registry.closeReport = void 0;
10883
+ });
10884
+ if (cloud) {
10885
+ const detach = pinLayer.attachCloud({
10886
+ cloud,
10887
+ channel,
10888
+ getRoute: getCurrentRoute,
10889
+ getPathname,
10890
+ getVisibleEntities,
10891
+ getMatchMode,
10892
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10893
+ });
10894
+ mountCleanup.add(detach);
10895
+ if (channel && typeof window !== "undefined") {
10896
+ const onRouteChange = () => {
10897
+ const route = getCurrentRoute();
10898
+ channel?.joinRoute(route);
10899
+ void pinLayer.refresh();
10900
+ };
10901
+ const detachRoute = bindRouteChange(onRouteChange);
10902
+ mountCleanup.add(detachRoute);
10903
+ }
10904
+ }
10905
+ }
11077
10906
  function mount(target) {
11078
10907
  if (mounted) return;
11079
10908
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11082,28 +10911,7 @@ function createUidex(options = {}) {
11082
10911
  }
11083
10912
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11084
10913
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11085
- const getVisibleEntities = () => {
11086
- if (typeof document === "undefined") return [];
11087
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11088
- const nodes = document.querySelectorAll(selector);
11089
- const ids = [];
11090
- const seen = /* @__PURE__ */ new Set();
11091
- for (const node of nodes) {
11092
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11093
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11094
- const id = node.getAttribute(attr);
11095
- if (!id) continue;
11096
- const key = `${kind}:${id}`;
11097
- if (!seen.has(key)) {
11098
- seen.add(key);
11099
- ids.push(key);
11100
- }
11101
- break;
11102
- }
11103
- }
11104
- return ids;
11105
- };
11106
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10914
+ const getMatchMode = () => readMode();
11107
10915
  let channel = null;
11108
10916
  if (cloud && options.user) {
11109
10917
  channel = cloud.realtime.connect({
@@ -11124,7 +10932,6 @@ function createUidex(options = {}) {
11124
10932
  onSelect: (match) => {
11125
10933
  const route = views.resolve(match.ref);
11126
10934
  if (!route) return;
11127
- session.select(match.ref);
11128
10935
  const entry = { id: route.view.id, ref: match.ref };
11129
10936
  session.mode.transition.enterViewing(views.buildStack(entry));
11130
10937
  }
@@ -11175,92 +10982,15 @@ function createUidex(options = {}) {
11175
10982
  });
11176
10983
  mountCleanup.add(viewStack);
11177
10984
  if (shadowRoot) {
11178
- keyBindings = bindShadowKeys({
10985
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10986
+ setupPinLayer(
11179
10987
  shadowRoot,
11180
- session,
11181
- getActionsPopup: () => viewStack.getActionsPopup(),
11182
- getPinLayer: () => pinLayerRef.current,
11183
- shortcut: options.shortcut
11184
- });
11185
- mountCleanup.add(keyBindings);
11186
- }
11187
- if (shadowRoot) {
11188
- const syncReportsToRegistry = () => {
11189
- const layer = pinLayerRef.current;
11190
- if (!layer) return;
11191
- const byEntity = /* @__PURE__ */ new Map();
11192
- for (const pin of layer.getAllPins()) {
11193
- const cid = pin.entity ?? "";
11194
- if (!cid) continue;
11195
- const list = byEntity.get(cid);
11196
- if (list) list.push(pin);
11197
- else byEntity.set(cid, [pin]);
11198
- }
11199
- for (const [cid, pins] of byEntity) {
11200
- const ref2 = parseComponentRef(cid);
11201
- registry.setReports(ref2.kind, ref2.id, pins);
11202
- }
11203
- };
11204
- if (cloud) {
11205
- registry.archiveReport = async (reportId, reason) => {
11206
- pinLayerRef.current?.removePin(reportId);
11207
- await cloud.pins.archive(
11208
- reportId,
11209
- reason
11210
- );
11211
- syncReportsToRegistry();
11212
- };
11213
- }
11214
- const pinLayer = createPinLayer({
11215
- container: shadowRoot,
11216
- currentBranch: options.git?.branch ?? null,
11217
- onOpenPinDetail: (componentId, reportId) => {
11218
- const ref2 = parseComponentRef(componentId);
11219
- setSelectedReportId(reportId);
11220
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11221
- session.mode.transition.enterViewing(views.buildStack(entry));
11222
- },
11223
- onHoverPin: (anchor, componentId) => {
11224
- const overlay = overlayRef.current;
11225
- if (!overlay) return;
11226
- if (!anchor || !componentId) {
11227
- overlay.hide();
11228
- return;
11229
- }
11230
- overlay.show(anchor, {
11231
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11232
- });
11233
- },
11234
- onPinsChanged: syncReportsToRegistry
11235
- });
11236
- shell.menuBar.setPinLayer(pinLayer);
11237
- pinLayerRef.current = pinLayer;
11238
- mountCleanup.add(() => {
11239
- pinLayer.destroy();
11240
- pinLayerRef.current = null;
11241
- registry.archiveReport = void 0;
11242
- });
11243
- if (cloud) {
11244
- const detach = pinLayer.attachCloud({
11245
- cloud,
11246
- channel,
11247
- getRoute: getCurrentRoute,
11248
- getPathname,
11249
- getVisibleEntities,
11250
- getMatchMode,
11251
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11252
- });
11253
- mountCleanup.add(detach);
11254
- if (channel && typeof window !== "undefined") {
11255
- const onRouteChange = () => {
11256
- const route = getCurrentRoute();
11257
- channel?.joinRoute(route);
11258
- void pinLayer.refresh();
11259
- };
11260
- const detachRoute = bindRouteChange(onRouteChange);
11261
- mountCleanup.add(detachRoute);
11262
- }
11263
- }
10988
+ shell,
10989
+ channel,
10990
+ getCurrentRoute,
10991
+ getMatchMode,
10992
+ getPathname
10993
+ );
11264
10994
  }
11265
10995
  if (ingest) {
11266
10996
  ingest.start();
@@ -11290,7 +11020,6 @@ function createUidex(options = {}) {
11290
11020
  export {
11291
11021
  ENTITY_KINDS,
11292
11022
  KIND_STYLE,
11293
- SURFACE_CONTAINER_CLASS,
11294
11023
  SURFACE_HOST_CLASS,
11295
11024
  SURFACE_IGNORE_SELECTOR,
11296
11025
  UnknownEntityKindError,
@@ -11305,7 +11034,6 @@ export {
11305
11034
  createCommandPaletteView,
11306
11035
  createConsoleCapture,
11307
11036
  createCursorTooltip,
11308
- createHighlightController,
11309
11037
  createIngest,
11310
11038
  createInspector,
11311
11039
  createMenuBar,
@@ -11322,13 +11050,11 @@ export {
11322
11050
  createThemeDetector,
11323
11051
  createUidex,
11324
11052
  createViewStack,
11325
- defaultResolveMatch,
11326
11053
  displayName,
11327
11054
  entityKey,
11328
11055
  featureDetailView,
11329
11056
  flowDetailView,
11330
11057
  formatShortcutLabel,
11331
- getNativeFetch,
11332
11058
  isMetaKind,
11333
11059
  nativeFetch,
11334
11060
  pageDetailView,