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.cjs CHANGED
@@ -32,7 +32,6 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  ENTITY_KINDS: () => ENTITY_KINDS,
34
34
  KIND_STYLE: () => KIND_STYLE,
35
- SURFACE_CONTAINER_CLASS: () => SURFACE_CONTAINER_CLASS,
36
35
  SURFACE_HOST_CLASS: () => SURFACE_HOST_CLASS,
37
36
  SURFACE_IGNORE_SELECTOR: () => SURFACE_IGNORE_SELECTOR,
38
37
  UnknownEntityKindError: () => UnknownEntityKindError,
@@ -47,7 +46,6 @@ __export(src_exports, {
47
46
  createCommandPaletteView: () => createCommandPaletteView,
48
47
  createConsoleCapture: () => createConsoleCapture,
49
48
  createCursorTooltip: () => createCursorTooltip,
50
- createHighlightController: () => createHighlightController,
51
49
  createIngest: () => createIngest,
52
50
  createInspector: () => createInspector,
53
51
  createMenuBar: () => createMenuBar,
@@ -64,13 +62,11 @@ __export(src_exports, {
64
62
  createThemeDetector: () => createThemeDetector,
65
63
  createUidex: () => createUidex,
66
64
  createViewStack: () => createViewStack,
67
- defaultResolveMatch: () => defaultResolveMatch,
68
65
  displayName: () => displayName,
69
66
  entityKey: () => entityKey,
70
67
  featureDetailView: () => featureDetailView,
71
68
  flowDetailView: () => flowDetailView,
72
69
  formatShortcutLabel: () => formatShortcutLabel,
73
- getNativeFetch: () => getNativeFetch,
74
70
  isMetaKind: () => isMetaKind,
75
71
  nativeFetch: () => nativeFetch,
76
72
  pageDetailView: () => pageDetailView,
@@ -155,6 +151,7 @@ function freezeEntity(entity, flows) {
155
151
  function createRegistry() {
156
152
  const store = emptyStore();
157
153
  let flowsCache = null;
154
+ const patternCache = /* @__PURE__ */ new Map();
158
155
  const getFlows = () => {
159
156
  if (flowsCache === null) flowsCache = Array.from(store.flow.values());
160
157
  return flowsCache;
@@ -164,6 +161,7 @@ function createRegistry() {
164
161
  const key = entityKey(entity);
165
162
  store[entity.kind].set(key, entity);
166
163
  flowsCache = null;
164
+ patternCache.delete(entity.kind);
167
165
  };
168
166
  const get = (kind, id) => {
169
167
  assertEntityKind(kind);
@@ -171,6 +169,51 @@ function createRegistry() {
171
169
  if (raw === void 0) return void 0;
172
170
  return freezeEntity(raw, getFlows());
173
171
  };
172
+ const getPatternsForKind = (kind) => {
173
+ const cached = patternCache.get(kind);
174
+ if (cached !== void 0) return cached;
175
+ const patterns = [];
176
+ for (const [key, entity] of store[kind]) {
177
+ if (key.includes("*")) {
178
+ const segments = key.split("*");
179
+ patterns.push({
180
+ segments,
181
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
182
+ entity
183
+ });
184
+ }
185
+ }
186
+ patternCache.set(
187
+ kind,
188
+ patterns
189
+ );
190
+ return patterns;
191
+ };
192
+ const matchesSegments = (segments, id) => {
193
+ const first = segments[0];
194
+ const last = segments[segments.length - 1];
195
+ if (!id.startsWith(first)) return false;
196
+ let pos = first.length;
197
+ for (let i = 1; i < segments.length - 1; i++) {
198
+ const idx = id.indexOf(segments[i], pos);
199
+ if (idx === -1) return false;
200
+ pos = idx + segments[i].length;
201
+ }
202
+ return id.endsWith(last) && id.length - last.length >= pos;
203
+ };
204
+ const matchPattern = (kind, id) => {
205
+ assertEntityKind(kind);
206
+ const patterns = getPatternsForKind(kind);
207
+ if (patterns.length === 0) return void 0;
208
+ let best;
209
+ for (const entry of patterns) {
210
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
211
+ best = entry;
212
+ }
213
+ }
214
+ if (best === void 0) return void 0;
215
+ return freezeEntity(best.entity, getFlows());
216
+ };
174
217
  const list = (kind) => {
175
218
  assertEntityKind(kind);
176
219
  const flows = getFlows();
@@ -221,6 +264,7 @@ function createRegistry() {
221
264
  return {
222
265
  add,
223
266
  get,
267
+ matchPattern,
224
268
  list,
225
269
  query,
226
270
  byScope,
@@ -383,9 +427,6 @@ function createConsoleCapture(options = {}) {
383
427
 
384
428
  // src/browser/ingest/native-fetch.ts
385
429
  var nativeFetch = typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
386
- function getNativeFetch() {
387
- return nativeFetch;
388
- }
389
430
 
390
431
  // src/browser/ingest/network.ts
391
432
  var DEFAULT_LIMIT2 = 20;
@@ -468,7 +509,7 @@ function createNetworkCapture(options = {}) {
468
509
 
469
510
  // src/browser/ingest/index.ts
470
511
  function createIngest(options = {}) {
471
- const { session, ...opts } = options;
512
+ const opts = options;
472
513
  const wantConsole = opts.captureConsole !== false;
473
514
  const wantNetwork = opts.captureNetwork !== false;
474
515
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -487,14 +528,12 @@ function createIngest(options = {}) {
487
528
  consoleCapture?.start();
488
529
  networkCapture?.start();
489
530
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
490
- if (active) session?.setIngest(true);
491
531
  }
492
532
  function stop() {
493
533
  if (!active) return;
494
534
  consoleCapture?.stop();
495
535
  networkCapture?.stop();
496
536
  active = false;
497
- session?.setIngest(false);
498
537
  }
499
538
  return {
500
539
  start,
@@ -616,8 +655,7 @@ var COMMAND_PALETTE_ENTRY = {
616
655
  function createModeStore(options) {
617
656
  const { nav, bindings } = options;
618
657
  const store = (0, import_vanilla.createStore)(() => ({
619
- mode: "idle",
620
- inspectorActive: false
658
+ mode: "idle"
621
659
  }));
622
660
  const transition = {
623
661
  openPalette() {
@@ -626,17 +664,17 @@ function createModeStore(options) {
626
664
  bindings?.destroyInspector?.();
627
665
  }
628
666
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
629
- store.setState({ mode: "palette", inspectorActive: false });
667
+ store.setState({ mode: "palette" });
630
668
  },
631
669
  openInspector() {
632
670
  bindings?.mountInspector?.();
633
671
  nav.nav.clear();
634
- store.setState({ mode: "inspecting", inspectorActive: true });
672
+ store.setState({ mode: "inspecting" });
635
673
  },
636
674
  closeInspector() {
637
675
  bindings?.destroyInspector?.();
638
676
  nav.nav.clear();
639
- store.setState({ mode: "idle", inspectorActive: false });
677
+ store.setState({ mode: "idle" });
640
678
  },
641
679
  toggleInspector() {
642
680
  if (store.getState().mode === "inspecting") {
@@ -651,7 +689,7 @@ function createModeStore(options) {
651
689
  bindings?.destroyInspector?.();
652
690
  }
653
691
  nav.nav.reset(initialStack);
654
- store.setState({ mode: "viewing", inspectorActive: false });
692
+ store.setState({ mode: "viewing" });
655
693
  },
656
694
  dismiss() {
657
695
  const prev = store.getState();
@@ -659,7 +697,7 @@ function createModeStore(options) {
659
697
  bindings?.destroyInspector?.();
660
698
  }
661
699
  nav.nav.clear();
662
- store.setState({ mode: "idle", inspectorActive: false });
700
+ store.setState({ mode: "idle" });
663
701
  },
664
702
  popOrTransition() {
665
703
  const { stack } = nav.getState();
@@ -667,12 +705,12 @@ function createModeStore(options) {
667
705
  nav.nav.pop();
668
706
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
669
707
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
670
- store.setState({ mode: "palette", inspectorActive: false });
708
+ store.setState({ mode: "palette" });
671
709
  } else if (stack.length === 2) {
672
710
  nav.nav.pop();
673
711
  } else {
674
712
  nav.nav.clear();
675
- store.setState({ mode: "idle", inspectorActive: false });
713
+ store.setState({ mode: "idle" });
676
714
  }
677
715
  },
678
716
  pushView(entry) {
@@ -689,7 +727,7 @@ function createModeStore(options) {
689
727
  case "inspecting":
690
728
  bindings?.destroyInspector?.();
691
729
  nav.nav.reset([entry]);
692
- store.setState({ mode: "viewing", inspectorActive: false });
730
+ store.setState({ mode: "viewing" });
693
731
  break;
694
732
  case "palette":
695
733
  case "viewing":
@@ -724,14 +762,6 @@ function createNavigationStore() {
724
762
  store.setState({ stack: s.slice(0, -1) });
725
763
  }
726
764
  },
727
- replace(entry) {
728
- const s = store.getState().stack;
729
- if (s.length === 0) {
730
- store.setState({ stack: [entry] });
731
- } else {
732
- store.setState({ stack: [...s.slice(0, -1), entry] });
733
- }
734
- },
735
765
  clear() {
736
766
  store.setState({ stack: [] });
737
767
  },
@@ -746,14 +776,11 @@ function createNavigationStore() {
746
776
 
747
777
  // src/browser/session/store.ts
748
778
  var defaultSnapshot = {
749
- hover: null,
750
- selection: null,
751
779
  stack: [],
752
780
  pinnedHighlight: null,
753
- inspectorActive: false,
781
+ mode: "idle",
754
782
  theme: "auto",
755
783
  resolvedTheme: "light",
756
- ingestActive: false,
757
784
  user: null
758
785
  };
759
786
  function resolveTheme(preference, detect) {
@@ -804,7 +831,6 @@ function createSession(options = {}) {
804
831
  } else if (highlightMode === "transient") {
805
832
  onUpdateOverlay?.(hlCtx);
806
833
  }
807
- store.setState({ hover: ref2 });
808
834
  },
809
835
  unhover() {
810
836
  if (highlightMode === "transient") {
@@ -814,7 +840,6 @@ function createSession(options = {}) {
814
840
  hlCtx.color = null;
815
841
  onHideOverlay?.();
816
842
  }
817
- store.setState({ hover: null });
818
843
  },
819
844
  pin(ref2) {
820
845
  const pinRef = ref2 ?? hlCtx.ref;
@@ -842,14 +867,11 @@ function createSession(options = {}) {
842
867
  };
843
868
  const store = (0, import_vanilla3.createStore)(() => ({
844
869
  ...defaultSnapshot,
845
- hover: overrides.hover ?? null,
846
- selection: overrides.selection ?? null,
847
870
  stack: [],
848
871
  pinnedHighlight: null,
849
- inspectorActive: false,
872
+ mode: "idle",
850
873
  theme: initialPref,
851
874
  resolvedTheme: initialResolved,
852
- ingestActive: overrides.ingestActive ?? false,
853
875
  user: overrides.user ?? null
854
876
  }));
855
877
  nav.subscribe(() => {
@@ -859,29 +881,21 @@ function createSession(options = {}) {
859
881
  }
860
882
  });
861
883
  modeStore.subscribe(() => {
862
- const { inspectorActive } = modeStore.getState();
863
- if (store.getState().inspectorActive !== inspectorActive) {
864
- store.setState({ inspectorActive });
884
+ const { mode } = modeStore.getState();
885
+ if (store.getState().mode !== mode) {
886
+ store.setState({ mode });
865
887
  }
866
888
  });
867
889
  const session = store;
868
890
  session.nav = nav;
869
891
  session.mode = modeStore;
870
892
  session.highlight = highlightActions;
871
- session.select = (ref2) => {
872
- if (sameRef(store.getState().selection, ref2)) return;
873
- store.setState({ selection: ref2 });
874
- };
875
893
  session.setTheme = (theme, resolved) => {
876
894
  const state = store.getState();
877
895
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
878
896
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
879
897
  store.setState({ theme, resolvedTheme: nextResolved });
880
898
  };
881
- session.setIngest = (active) => {
882
- if (store.getState().ingestActive === active) return;
883
- store.setState({ ingestActive: active });
884
- };
885
899
  if (initialStack.length > 0) {
886
900
  modeStore.transition.openPalette();
887
901
  for (let i = 1; i < initialStack.length; i++) {
@@ -1173,6 +1187,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1173
1187
  .relative {
1174
1188
  position: relative;
1175
1189
  }
1190
+ .static {
1191
+ position: static;
1192
+ }
1176
1193
  .inset-0 {
1177
1194
  inset: calc(var(--spacing) * 0);
1178
1195
  }
@@ -1191,9 +1208,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1191
1208
  .right-0 {
1192
1209
  right: calc(var(--spacing) * 0);
1193
1210
  }
1194
- .right-2 {
1195
- right: calc(var(--spacing) * 2);
1196
- }
1197
1211
  .bottom-full {
1198
1212
  bottom: 100%;
1199
1213
  }
@@ -1236,9 +1250,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1236
1250
  .mx-2 {
1237
1251
  margin-inline: calc(var(--spacing) * 2);
1238
1252
  }
1239
- .my-1 {
1240
- margin-block: calc(var(--spacing) * 1);
1241
- }
1242
1253
  .ms-auto {
1243
1254
  margin-inline-start: auto;
1244
1255
  }
@@ -1275,9 +1286,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1275
1286
  .inline-flex {
1276
1287
  display: inline-flex;
1277
1288
  }
1278
- .list-item {
1279
- display: list-item;
1280
- }
1281
1289
  .table {
1282
1290
  display: table;
1283
1291
  }
@@ -1285,10 +1293,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1285
1293
  width: calc(var(--spacing) * 2);
1286
1294
  height: calc(var(--spacing) * 2);
1287
1295
  }
1288
- .size-3 {
1289
- width: calc(var(--spacing) * 3);
1290
- height: calc(var(--spacing) * 3);
1291
- }
1292
1296
  .size-3\\.5 {
1293
1297
  width: calc(var(--spacing) * 3.5);
1294
1298
  height: calc(var(--spacing) * 3.5);
@@ -1346,15 +1350,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1346
1350
  .h-\\[26rem\\] {
1347
1351
  height: 26rem;
1348
1352
  }
1349
- .h-auto {
1350
- height: auto;
1351
- }
1352
1353
  .h-full {
1353
1354
  height: 100%;
1354
1355
  }
1355
- .h-px {
1356
- height: 1px;
1357
- }
1358
1356
  .max-h-32 {
1359
1357
  max-height: calc(var(--spacing) * 32);
1360
1358
  }
@@ -1364,9 +1362,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1364
1362
  .min-h-0 {
1365
1363
  min-height: calc(var(--spacing) * 0);
1366
1364
  }
1367
- .min-h-7 {
1368
- min-height: calc(var(--spacing) * 7);
1369
- }
1370
1365
  .min-h-8 {
1371
1366
  min-height: calc(var(--spacing) * 8);
1372
1367
  }
@@ -1391,9 +1386,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1391
1386
  .w-56 {
1392
1387
  width: calc(var(--spacing) * 56);
1393
1388
  }
1394
- .w-auto {
1395
- width: auto;
1396
- }
1397
1389
  .w-full {
1398
1390
  width: 100%;
1399
1391
  }
@@ -1468,9 +1460,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1468
1460
  .animate-spin {
1469
1461
  animation: var(--animate-spin);
1470
1462
  }
1471
- .cursor-default {
1472
- cursor: default;
1473
- }
1474
1463
  .cursor-pointer {
1475
1464
  cursor: pointer;
1476
1465
  }
@@ -1480,9 +1469,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1480
1469
  .scroll-py-2 {
1481
1470
  scroll-padding-block: calc(var(--spacing) * 2);
1482
1471
  }
1483
- .list-none {
1484
- list-style-type: none;
1485
- }
1486
1472
  .appearance-none {
1487
1473
  appearance: none;
1488
1474
  }
@@ -1504,9 +1490,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1504
1490
  .justify-center {
1505
1491
  justify-content: center;
1506
1492
  }
1507
- .justify-start {
1508
- justify-content: flex-start;
1509
- }
1510
1493
  .gap-0 {
1511
1494
  gap: calc(var(--spacing) * 0);
1512
1495
  }
@@ -1531,9 +1514,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1531
1514
  .gap-6 {
1532
1515
  gap: calc(var(--spacing) * 6);
1533
1516
  }
1534
- .self-start {
1535
- align-self: flex-start;
1536
- }
1537
1517
  .truncate {
1538
1518
  overflow: hidden;
1539
1519
  text-overflow: ellipsis;
@@ -1748,9 +1728,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1748
1728
  .p-6 {
1749
1729
  padding: calc(var(--spacing) * 6);
1750
1730
  }
1751
- .px-0 {
1752
- padding-inline: calc(var(--spacing) * 0);
1753
- }
1754
1731
  .px-1 {
1755
1732
  padding-inline: calc(var(--spacing) * 1);
1756
1733
  }
@@ -1775,9 +1752,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1775
1752
  .px-6 {
1776
1753
  padding-inline: calc(var(--spacing) * 6);
1777
1754
  }
1778
- .py-0 {
1779
- padding-block: calc(var(--spacing) * 0);
1780
- }
1781
1755
  .py-1 {
1782
1756
  padding-block: calc(var(--spacing) * 1);
1783
1757
  }
@@ -1850,9 +1824,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1850
1824
  .text-\\[9px\\] {
1851
1825
  font-size: 9px;
1852
1826
  }
1853
- .text-\\[10px\\] {
1854
- font-size: 10px;
1855
- }
1856
1827
  .text-\\[11px\\] {
1857
1828
  font-size: 11px;
1858
1829
  }
@@ -2092,10 +2063,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2092
2063
  outline-style: var(--tw-outline-style);
2093
2064
  outline-width: 1px;
2094
2065
  }
2095
- .blur {
2096
- --tw-blur: blur(8px);
2097
- 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,);
2098
- }
2099
2066
  .filter {
2100
2067
  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,);
2101
2068
  }
@@ -2261,25 +2228,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2261
2228
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2262
2229
  }
2263
2230
  }
2264
- .focus-within\\:border-ring {
2265
- &:focus-within {
2266
- border-color: var(--ring);
2267
- }
2268
- }
2269
- .focus-within\\:ring-\\[3px\\] {
2270
- &:focus-within {
2271
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2272
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2273
- }
2274
- }
2275
- .focus-within\\:ring-ring\\/30 {
2276
- &:focus-within {
2277
- --tw-ring-color: var(--ring);
2278
- @supports (color: color-mix(in lab, red, red)) {
2279
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2280
- }
2281
- }
2282
- }
2283
2231
  .hover\\:border-destructive\\/30 {
2284
2232
  &:hover {
2285
2233
  @media (hover: hover) {
@@ -2423,11 +2371,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2423
2371
  pointer-events: none;
2424
2372
  }
2425
2373
  }
2426
- .disabled\\:cursor-not-allowed {
2427
- &:disabled {
2428
- cursor: not-allowed;
2429
- }
2430
- }
2431
2374
  .disabled\\:opacity-50 {
2432
2375
  &:disabled {
2433
2376
  opacity: 50%;
@@ -2438,11 +2381,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2438
2381
  opacity: 60%;
2439
2382
  }
2440
2383
  }
2441
- .has-disabled\\:opacity-60 {
2442
- &:has(*:disabled) {
2443
- opacity: 60%;
2444
- }
2445
- }
2446
2384
  .aria-invalid\\:border-destructive\\/40 {
2447
2385
  &[aria-invalid="true"] {
2448
2386
  border-color: var(--destructive);
@@ -2849,32 +2787,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2849
2787
  height: calc(var(--spacing) * 4.5);
2850
2788
  }
2851
2789
  }
2852
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2853
- &>svg {
2854
- pointer-events: none;
2855
- }
2856
- }
2857
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2858
- &>svg {
2859
- margin-inline: calc(var(--spacing) * -0.5);
2860
- }
2861
- }
2862
- .\\[\\&\\>svg\\]\\:shrink-0 {
2863
- &>svg {
2864
- flex-shrink: 0;
2865
- }
2866
- }
2867
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2868
- &>svg:not([class*='opacity-']) {
2869
- opacity: 80%;
2870
- }
2871
- }
2872
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2873
- &>svg:not([class*='size-']) {
2874
- width: calc(var(--spacing) * 4);
2875
- height: calc(var(--spacing) * 4);
2876
- }
2877
- }
2878
2790
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2879
2791
  [data-kbd-nav] & {
2880
2792
  &:focus-within {
@@ -3436,18 +3348,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3436
3348
 
3437
3349
  // src/browser/surface/constants.ts
3438
3350
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3439
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3440
3351
  var Z_BASE = 2147483630;
3441
3352
  var Z_OVERLAY = 2147483635;
3442
3353
  var Z_PIN_LAYER = 2147483640;
3443
3354
  var Z_CHROME = 2147483645;
3444
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3355
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3445
3356
  var UIDEX_ATTR_TO_KIND = [
3446
3357
  ["data-uidex", "element"],
3447
3358
  ["data-uidex-region", "region"],
3448
3359
  ["data-uidex-widget", "widget"],
3449
3360
  ["data-uidex-primitive", "primitive"]
3450
3361
  ];
3362
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3363
+ "element",
3364
+ "region",
3365
+ "widget",
3366
+ "primitive"
3367
+ ]);
3451
3368
 
3452
3369
  // src/browser/surface/host.ts
3453
3370
  function createSurfaceHost(options) {
@@ -3625,7 +3542,7 @@ function createCursorTooltip(deps) {
3625
3542
  // src/browser/surface/inspector.ts
3626
3543
  function entityForRef(ref2, registry) {
3627
3544
  if (registry) {
3628
- const found = registry.get(ref2.kind, ref2.id);
3545
+ const found = registry.get(ref2.kind, ref2.id) ?? registry.matchPattern?.(ref2.kind, ref2.id);
3629
3546
  if (found) return found;
3630
3547
  }
3631
3548
  if (ref2.kind === "route") return { kind: "route", path: ref2.id, page: ref2.id };
@@ -3640,29 +3557,6 @@ function entityForRef(ref2, registry) {
3640
3557
  }
3641
3558
  return { kind: ref2.kind, id: ref2.id };
3642
3559
  }
3643
- function defaultResolveMatch(target, registry) {
3644
- if (target.closest(SURFACE_IGNORE_SELECTOR)) return null;
3645
- let node = target;
3646
- while (node) {
3647
- if (node instanceof HTMLElement) {
3648
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
3649
- const id = node.getAttribute(attr);
3650
- if (id) {
3651
- const ref2 = { kind, id };
3652
- const entity = entityForRef(ref2, registry);
3653
- return {
3654
- element: node,
3655
- ref: ref2,
3656
- entity,
3657
- label: displayName(entity, node)
3658
- };
3659
- }
3660
- }
3661
- }
3662
- node = node.parentElement;
3663
- }
3664
- return null;
3665
- }
3666
3560
  function defaultResolveAllMatches(target, registry) {
3667
3561
  if (target.closest(SURFACE_IGNORE_SELECTOR)) return [];
3668
3562
  const semantic = [];
@@ -3707,27 +3601,6 @@ function resolveEntityElement(ref2) {
3707
3601
  if (el2 instanceof HTMLElement && el2.isConnected) return el2;
3708
3602
  return null;
3709
3603
  }
3710
- function createHighlightController(overlay) {
3711
- let lastKey = null;
3712
- return {
3713
- show(ref2, opts) {
3714
- const el2 = resolveEntityElement(ref2);
3715
- if (!el2) {
3716
- lastKey = null;
3717
- overlay.hide();
3718
- return;
3719
- }
3720
- const key = `${ref2.kind}:${ref2.id}`;
3721
- if (key === lastKey && overlay.isVisible) return;
3722
- lastKey = key;
3723
- overlay.show(el2, opts);
3724
- },
3725
- hide() {
3726
- lastKey = null;
3727
- overlay.hide();
3728
- }
3729
- };
3730
- }
3731
3604
  function createInspector(options) {
3732
3605
  const {
3733
3606
  session,
@@ -3781,7 +3654,6 @@ function createInspector(options) {
3781
3654
  e.preventDefault();
3782
3655
  e.stopPropagation();
3783
3656
  const match = stack[layerIndex];
3784
- session.select(match.ref);
3785
3657
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3786
3658
  };
3787
3659
  const onContextMenu = (e) => {
@@ -4100,49 +3972,12 @@ function createMenuBar(options) {
4100
3972
  },
4101
3973
  pinIcon
4102
3974
  );
4103
- const commitCycler = el("div", {
4104
- class: "relative z-1 inline-flex items-center gap-0.5",
4105
- attrs: { "data-uidex-menubar-commit-cycler": "" }
4106
- });
4107
- commitCycler.hidden = true;
4108
- const prevIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronLeft);
4109
- prevIcon.setAttribute("class", "size-3");
4110
- prevIcon.setAttribute("aria-hidden", "true");
4111
- const prevBtn = el(
4112
- "button",
4113
- {
4114
- class: BUTTON_CLASS,
4115
- attrs: { type: "button", "aria-label": "Previous commit" },
4116
- style: { width: "18px", height: "18px" }
4117
- },
4118
- prevIcon
4119
- );
4120
- const commitLabel = el("span", {
4121
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4122
- attrs: { "data-uidex-menubar-commit-label": "" }
4123
- });
4124
- const nextIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronRight);
4125
- nextIcon.setAttribute("class", "size-3");
4126
- nextIcon.setAttribute("aria-hidden", "true");
4127
- const nextBtn = el(
4128
- "button",
4129
- {
4130
- class: BUTTON_CLASS,
4131
- attrs: { type: "button", "aria-label": "Next commit" },
4132
- style: { width: "18px", height: "18px" }
4133
- },
4134
- nextIcon
4135
- );
4136
- commitCycler.appendChild(prevBtn);
4137
- commitCycler.appendChild(commitLabel);
4138
- commitCycler.appendChild(nextBtn);
4139
3975
  const pinWrapper = el("div", {
4140
3976
  class: "relative z-1 inline-flex items-center gap-0.5",
4141
3977
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4142
3978
  });
4143
3979
  pinWrapper.hidden = true;
4144
3980
  pinWrapper.appendChild(pinBtn);
4145
- pinWrapper.appendChild(commitCycler);
4146
3981
  root.appendChild(pinWrapper);
4147
3982
  const updatePinUI = () => {
4148
3983
  if (!activePinLayer) {
@@ -4150,16 +3985,7 @@ function createMenuBar(options) {
4150
3985
  return;
4151
3986
  }
4152
3987
  const pinsVisible = activePinLayer.visible;
4153
- const state = activePinLayer.filterState;
4154
- const hasCommits = state.commits.length > 0;
4155
3988
  pinWrapper.hidden = false;
4156
- commitCycler.hidden = !pinsVisible || !hasCommits;
4157
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4158
- commitLabel.textContent = `all (${state.commits.length})`;
4159
- } else {
4160
- const sha = state.commits[state.commitIndex] ?? "";
4161
- commitLabel.textContent = sha.slice(0, 7);
4162
- }
4163
3989
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4164
3990
  };
4165
3991
  pinBtn.addEventListener("click", (e) => {
@@ -4168,14 +3994,6 @@ function createMenuBar(options) {
4168
3994
  activePinLayer.setVisible(!activePinLayer.visible);
4169
3995
  }
4170
3996
  });
4171
- prevBtn.addEventListener("click", (e) => {
4172
- e.stopPropagation();
4173
- activePinLayer?.prevCommit();
4174
- });
4175
- nextBtn.addEventListener("click", (e) => {
4176
- e.stopPropagation();
4177
- activePinLayer?.nextCommit();
4178
- });
4179
3997
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4180
3998
  const presenceIcon = (0, import_lucide3.createElement)(import_lucide3.Users);
4181
3999
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4269,7 +4087,7 @@ function createMenuBar(options) {
4269
4087
  container.appendChild(root);
4270
4088
  const syncButtonStates = () => {
4271
4089
  const state = session.getState();
4272
- const inspectActive = state.inspectorActive;
4090
+ const inspectActive = state.mode === "inspecting";
4273
4091
  inspectBtn.setAttribute(
4274
4092
  "data-uidex-menubar-inspect-active",
4275
4093
  inspectActive ? "true" : "false"
@@ -4388,6 +4206,49 @@ function createMenuBar(options) {
4388
4206
  };
4389
4207
  }
4390
4208
 
4209
+ // src/browser/internal/repositioner.ts
4210
+ function createRepositioner(onReflow) {
4211
+ let rafId = null;
4212
+ let attached = false;
4213
+ const schedule = () => {
4214
+ if (rafId !== null) return;
4215
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4216
+ rafId = null;
4217
+ onReflow();
4218
+ }) : setTimeout(() => {
4219
+ rafId = null;
4220
+ onReflow();
4221
+ }, 0);
4222
+ };
4223
+ const cancel = () => {
4224
+ if (rafId === null) return;
4225
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4226
+ else clearTimeout(rafId);
4227
+ rafId = null;
4228
+ };
4229
+ const onScroll = () => schedule();
4230
+ const onResize = () => schedule();
4231
+ const attach = () => {
4232
+ if (attached) return;
4233
+ attached = true;
4234
+ window.addEventListener("resize", onResize);
4235
+ window.addEventListener("scroll", onScroll, {
4236
+ capture: true,
4237
+ passive: true
4238
+ });
4239
+ };
4240
+ const detach = () => {
4241
+ if (!attached) return;
4242
+ attached = false;
4243
+ window.removeEventListener("resize", onResize);
4244
+ window.removeEventListener("scroll", onScroll, {
4245
+ capture: true
4246
+ });
4247
+ cancel();
4248
+ };
4249
+ return { schedule, cancel, attach, detach };
4250
+ }
4251
+
4391
4252
  // src/browser/surface/overlay.ts
4392
4253
  var DEFAULT_COLOR = "#34d399";
4393
4254
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4455,44 +4316,7 @@ function createOverlay(deps) {
4455
4316
  fillOpacity: DEFAULT_FILL_OPACITY,
4456
4317
  backdrop: false
4457
4318
  };
4458
- let rafId = null;
4459
- let attached = false;
4460
- const schedule = () => {
4461
- if (rafId !== null) return;
4462
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4463
- rafId = null;
4464
- updatePosition();
4465
- }) : setTimeout(() => {
4466
- rafId = null;
4467
- updatePosition();
4468
- }, 0);
4469
- };
4470
- const cancelSchedule = () => {
4471
- if (rafId === null) return;
4472
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4473
- else clearTimeout(rafId);
4474
- rafId = null;
4475
- };
4476
- const onScroll = () => schedule();
4477
- const onResize = () => schedule();
4478
- const attach = () => {
4479
- if (attached) return;
4480
- attached = true;
4481
- window.addEventListener("resize", onResize);
4482
- window.addEventListener("scroll", onScroll, {
4483
- capture: true,
4484
- passive: true
4485
- });
4486
- };
4487
- const detach = () => {
4488
- if (!attached) return;
4489
- attached = false;
4490
- window.removeEventListener("resize", onResize);
4491
- window.removeEventListener("scroll", onScroll, {
4492
- capture: true
4493
- });
4494
- cancelSchedule();
4495
- };
4319
+ const repositioner = createRepositioner(() => updatePosition());
4496
4320
  function updatePosition() {
4497
4321
  if (!target) return;
4498
4322
  const rect = target.getBoundingClientRect();
@@ -4556,16 +4380,16 @@ function createOverlay(deps) {
4556
4380
  box.offsetHeight;
4557
4381
  }
4558
4382
  box.style.opacity = "1";
4559
- attach();
4383
+ repositioner.attach();
4560
4384
  },
4561
4385
  hide() {
4562
4386
  target = null;
4563
4387
  box.style.opacity = "0";
4564
4388
  backdrop.style.opacity = "0";
4565
- detach();
4389
+ repositioner.detach();
4566
4390
  },
4567
4391
  destroy() {
4568
- detach();
4392
+ repositioner.detach();
4569
4393
  box.remove();
4570
4394
  backdrop.remove();
4571
4395
  target = null;
@@ -4682,8 +4506,7 @@ function createSurfaceShell(options) {
4682
4506
  const overlay = createOverlay({ container: host.shadowRoot });
4683
4507
  cleanup.add(overlay);
4684
4508
  const tooltip = createCursorTooltip({
4685
- container: host.chromeEl,
4686
- session: options.session
4509
+ container: host.chromeEl
4687
4510
  });
4688
4511
  cleanup.add(tooltip);
4689
4512
  const afterHover = options.inspector?.onAfterHover;
@@ -4867,9 +4690,6 @@ function createPinLayer(options) {
4867
4690
  const seenIds = /* @__PURE__ */ new Set();
4868
4691
  const byComp = /* @__PURE__ */ new Map();
4869
4692
  const indicators = /* @__PURE__ */ new Map();
4870
- let filter = { branch: null, commit: null };
4871
- let commits = [];
4872
- let commitIndex = -1;
4873
4693
  const filterCbs = /* @__PURE__ */ new Set();
4874
4694
  const notifyFilter = () => {
4875
4695
  for (const cb of filterCbs) cb();
@@ -4884,8 +4704,6 @@ function createPinLayer(options) {
4884
4704
  }
4885
4705
  };
4886
4706
  const rerender = () => {
4887
- commits = [];
4888
- commitIndex = -1;
4889
4707
  rebuildFiltered();
4890
4708
  for (const id of Array.from(indicators.keys())) {
4891
4709
  if (!byComp.has(id)) removeIndicator(id);
@@ -4894,45 +4712,8 @@ function createPinLayer(options) {
4894
4712
  notifyFilter();
4895
4713
  onPinsChanged?.();
4896
4714
  };
4897
- let rafId = null;
4898
- let winAttached = false;
4899
4715
  let obs = null;
4900
- const schedulePos = () => {
4901
- if (rafId !== null) return;
4902
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4903
- rafId = null;
4904
- posAll();
4905
- }) : setTimeout(() => {
4906
- rafId = null;
4907
- posAll();
4908
- }, 0);
4909
- };
4910
- const cancelPos = () => {
4911
- if (rafId === null) return;
4912
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4913
- else clearTimeout(rafId);
4914
- rafId = null;
4915
- };
4916
- const onScroll = () => schedulePos();
4917
- const onResize = () => schedulePos();
4918
- const attachWin = () => {
4919
- if (winAttached) return;
4920
- winAttached = true;
4921
- window.addEventListener("scroll", onScroll, {
4922
- capture: true,
4923
- passive: true
4924
- });
4925
- window.addEventListener("resize", onResize);
4926
- };
4927
- const detachWin = () => {
4928
- if (!winAttached) return;
4929
- winAttached = false;
4930
- window.removeEventListener("scroll", onScroll, {
4931
- capture: true
4932
- });
4933
- window.removeEventListener("resize", onResize);
4934
- cancelPos();
4935
- };
4716
+ const repositioner = createRepositioner(() => posAll());
4936
4717
  const attachObs = () => {
4937
4718
  if (obs || typeof MutationObserver === "undefined") return;
4938
4719
  obs = new MutationObserver((recs) => {
@@ -4956,7 +4737,7 @@ function createPinLayer(options) {
4956
4737
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4957
4738
  changed = true;
4958
4739
  }
4959
- if (changed) schedulePos();
4740
+ if (changed) repositioner.schedule();
4960
4741
  };
4961
4742
  const expand = (st) => {
4962
4743
  if (st.expanded) return;
@@ -5198,7 +4979,7 @@ function createPinLayer(options) {
5198
4979
  if (!st) {
5199
4980
  st = buildIndicator(componentId);
5200
4981
  indicators.set(componentId, st);
5201
- attachWin();
4982
+ repositioner.attach();
5202
4983
  attachObs();
5203
4984
  }
5204
4985
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5212,7 +4993,7 @@ function createPinLayer(options) {
5212
4993
  st.wrap.remove();
5213
4994
  indicators.delete(componentId);
5214
4995
  if (indicators.size === 0) {
5215
- detachWin();
4996
+ repositioner.detach();
5216
4997
  detachObs();
5217
4998
  }
5218
4999
  };
@@ -5247,17 +5028,22 @@ function createPinLayer(options) {
5247
5028
  seenIds.clear();
5248
5029
  byComp.clear();
5249
5030
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5250
- commits = [];
5251
- commitIndex = -1;
5252
5031
  notifyFilter();
5253
5032
  },
5254
5033
  getPinsForElement: (id) => byComp.get(id) ?? [],
5255
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5256
5034
  getAllPins: () => allPins.slice(),
5257
5035
  attachChannel(channel) {
5258
- return channel.onPin((pin) => {
5036
+ const offPin = channel.onPin((pin) => {
5259
5037
  layer.addPin(pin);
5260
5038
  });
5039
+ const offArchived = channel.onPinArchived?.((reportId) => {
5040
+ layer.removePin(reportId);
5041
+ }) ?? (() => {
5042
+ });
5043
+ return () => {
5044
+ offPin();
5045
+ offArchived();
5046
+ };
5261
5047
  },
5262
5048
  attachCloud(opts) {
5263
5049
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5288,39 +5074,6 @@ function createPinLayer(options) {
5288
5074
  async refresh() {
5289
5075
  if (activeRefresh) await activeRefresh();
5290
5076
  },
5291
- get filterState() {
5292
- return {
5293
- branch: filter.branch,
5294
- commit: filter.commit,
5295
- commits,
5296
- commitIndex
5297
- };
5298
- },
5299
- setFilter(next) {
5300
- filter = {
5301
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5302
- commit: next.commit !== void 0 ? next.commit : filter.commit
5303
- };
5304
- rerender();
5305
- },
5306
- nextCommit() {
5307
- if (!commits.length) return;
5308
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5309
- filter = {
5310
- ...filter,
5311
- commit: commitIndex === -1 ? null : commits[commitIndex]
5312
- };
5313
- rerender();
5314
- },
5315
- prevCommit() {
5316
- if (!commits.length) return;
5317
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5318
- filter = {
5319
- ...filter,
5320
- commit: commitIndex === -1 ? null : commits[commitIndex]
5321
- };
5322
- rerender();
5323
- },
5324
5077
  onFilterChange(cb) {
5325
5078
  filterCbs.add(cb);
5326
5079
  return () => {
@@ -5339,7 +5092,7 @@ function createPinLayer(options) {
5339
5092
  },
5340
5093
  destroy() {
5341
5094
  layer.clear();
5342
- detachWin();
5095
+ repositioner.detach();
5343
5096
  detachObs();
5344
5097
  layerEl.remove();
5345
5098
  activeRefresh = null;
@@ -5382,9 +5135,11 @@ function createRouter(options) {
5382
5135
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5383
5136
  throw new ViewValidationError("View must have a non-empty string id");
5384
5137
  }
5385
- if (typeof view.surface !== "function") {
5138
+ const hasSurface = typeof view.surface === "function";
5139
+ const hasRender = typeof view.render === "function";
5140
+ if (!hasSurface && !hasRender) {
5386
5141
  throw new ViewValidationError(
5387
- `View ${view.id}: 'surface' must be a function`
5142
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5388
5143
  );
5389
5144
  }
5390
5145
  if (!view.matches && !view.palette) {
@@ -5442,7 +5197,6 @@ function createRouter(options) {
5442
5197
  if (idx >= 0) recentRefs.splice(idx, 1);
5443
5198
  recentRefs.unshift(ref2);
5444
5199
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5445
- options.session.select(ref2);
5446
5200
  const entry = { id: match.view.id, ref: ref2 };
5447
5201
  const { mode } = options.session.mode.getState();
5448
5202
  if (mode === "idle" || mode === "inspecting") {
@@ -5497,57 +5251,7 @@ function detectDev() {
5497
5251
 
5498
5252
  // src/browser/internal/lit.ts
5499
5253
  var import_lit_html2 = require("lit-html");
5500
- var import_repeat = require("lit-html/directives/repeat.js");
5501
5254
  var import_ref = require("lit-html/directives/ref.js");
5502
- var import_class_map = require("lit-html/directives/class-map.js");
5503
-
5504
- // src/browser/internal/apply-props.ts
5505
- function applyProps(node, props) {
5506
- const removers = [];
5507
- for (const [key, rawValue] of Object.entries(props)) {
5508
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5509
- if (key.startsWith("on") && typeof rawValue === "function") {
5510
- const eventName = key.slice(2).toLowerCase();
5511
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5512
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5513
- const listener = rawValue;
5514
- node.addEventListener(effectiveEvent, listener);
5515
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5516
- continue;
5517
- }
5518
- if (rawValue === void 0 || rawValue === null) {
5519
- node.removeAttribute(key);
5520
- continue;
5521
- }
5522
- if (typeof rawValue === "boolean") {
5523
- if (rawValue) node.setAttribute(key, "");
5524
- else node.removeAttribute(key);
5525
- continue;
5526
- }
5527
- if (key === "style" && typeof rawValue === "object") {
5528
- Object.assign(
5529
- node.style,
5530
- rawValue
5531
- );
5532
- continue;
5533
- }
5534
- if (key === "className" || key === "class") {
5535
- node.setAttribute("class", String(rawValue));
5536
- continue;
5537
- }
5538
- if (key === "htmlFor") {
5539
- node.setAttribute("for", String(rawValue));
5540
- continue;
5541
- }
5542
- if (key === "tabIndex") {
5543
- ;
5544
- node.tabIndex = Number(rawValue);
5545
- continue;
5546
- }
5547
- node.setAttribute(key, String(rawValue));
5548
- }
5549
- return () => removers.forEach((fn) => fn());
5550
- }
5551
5255
 
5552
5256
  // src/browser/internal/lit-icon.ts
5553
5257
  var import_lit_html = require("lit-html");
@@ -5581,6 +5285,16 @@ var LucideIconDirective = class extends import_directive.Directive {
5581
5285
  };
5582
5286
  var icon = (0, import_directive.directive)(LucideIconDirective);
5583
5287
 
5288
+ // src/browser/views/primitives/chip.ts
5289
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5290
+ function createChip(options = {}, children = []) {
5291
+ const { class: extra, ...rest } = options;
5292
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5293
+ }
5294
+
5295
+ // src/browser/views/primitives/kind-icon.ts
5296
+ var import_lucide6 = require("lucide");
5297
+
5584
5298
  // src/browser/ui/cva.ts
5585
5299
  function cva(base, config = {}) {
5586
5300
  return (props = {}) => {
@@ -5619,17 +5333,15 @@ var badgeVariants = cva(badgeBase, {
5619
5333
  }
5620
5334
  }
5621
5335
  });
5622
-
5623
- // src/browser/views/primitives/chip.ts
5624
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5625
- function createChip(options = {}, children = []) {
5626
- const { class: extra, ...rest } = options;
5627
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5336
+ function badgeTpl(content, options = {}) {
5337
+ const { variant, size, class: extra } = options;
5338
+ return import_lit_html2.html`
5339
+ <span class=${cn(badgeVariants({ variant, size }), extra)} data-slot="badge"
5340
+ >${content}</span
5341
+ >
5342
+ `;
5628
5343
  }
5629
5344
 
5630
- // src/browser/views/primitives/kind-icon.ts
5631
- var import_lucide6 = require("lucide");
5632
-
5633
5345
  // src/browser/views/primitives/icon-tile.ts
5634
5346
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5635
5347
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6121,6 +5833,54 @@ function createScrollArea(props = {}) {
6121
5833
  );
6122
5834
  }
6123
5835
 
5836
+ // src/browser/internal/apply-props.ts
5837
+ function applyProps(node, props) {
5838
+ const removers = [];
5839
+ for (const [key, rawValue] of Object.entries(props)) {
5840
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5841
+ if (key.startsWith("on") && typeof rawValue === "function") {
5842
+ const eventName = key.slice(2).toLowerCase();
5843
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5844
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5845
+ const listener = rawValue;
5846
+ node.addEventListener(effectiveEvent, listener);
5847
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5848
+ continue;
5849
+ }
5850
+ if (rawValue === void 0 || rawValue === null) {
5851
+ node.removeAttribute(key);
5852
+ continue;
5853
+ }
5854
+ if (typeof rawValue === "boolean") {
5855
+ if (rawValue) node.setAttribute(key, "");
5856
+ else node.removeAttribute(key);
5857
+ continue;
5858
+ }
5859
+ if (key === "style" && typeof rawValue === "object") {
5860
+ Object.assign(
5861
+ node.style,
5862
+ rawValue
5863
+ );
5864
+ continue;
5865
+ }
5866
+ if (key === "className" || key === "class") {
5867
+ node.setAttribute("class", String(rawValue));
5868
+ continue;
5869
+ }
5870
+ if (key === "htmlFor") {
5871
+ node.setAttribute("for", String(rawValue));
5872
+ continue;
5873
+ }
5874
+ if (key === "tabIndex") {
5875
+ ;
5876
+ node.tabIndex = Number(rawValue);
5877
+ continue;
5878
+ }
5879
+ node.setAttribute(key, String(rawValue));
5880
+ }
5881
+ return () => removers.forEach((fn) => fn());
5882
+ }
5883
+
6124
5884
  // src/browser/views/builder/spread-props.ts
6125
5885
  function spreadProps(node, props) {
6126
5886
  return applyProps(node, props);
@@ -6140,9 +5900,6 @@ function createPersistentSpreads() {
6140
5900
  };
6141
5901
  }
6142
5902
 
6143
- // src/browser/views/render/detail.ts
6144
- var import_lucide7 = require("lucide");
6145
-
6146
5903
  // src/browser/internal/arrow-nav.ts
6147
5904
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6148
5905
  function bindArrowNav(options) {
@@ -6214,30 +5971,6 @@ function focusItem(items, idx) {
6214
5971
  items[idx].focus();
6215
5972
  }
6216
5973
 
6217
- // src/browser/views/builder/filter.ts
6218
- function normalizeQuery(query) {
6219
- return query.trim().toLowerCase();
6220
- }
6221
- function matchesQuery(haystack, query) {
6222
- const q = normalizeQuery(query);
6223
- if (!q) return true;
6224
- return haystack.toLowerCase().includes(q);
6225
- }
6226
- function filterEntities(entities, query) {
6227
- const q = normalizeQuery(query);
6228
- if (!q) return entities;
6229
- return entities.filter((e) => {
6230
- const name = displayName(e).toLowerCase();
6231
- const id = entityKey(e).toLowerCase();
6232
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6233
- });
6234
- }
6235
- function filterIds(ids, query) {
6236
- const q = normalizeQuery(query);
6237
- if (!q) return ids;
6238
- return ids.filter((id) => id.toLowerCase().includes(q));
6239
- }
6240
-
6241
5974
  // src/browser/views/labels.ts
6242
5975
  var SECTION_LABELS = {
6243
5976
  acceptance: "Acceptance criteria",
@@ -6262,171 +5995,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6262
5995
  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";
6263
5996
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6264
5997
 
6265
- // src/browser/ui/button.ts
6266
- 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";
6267
- var buttonVariants = cva(buttonBase, {
6268
- defaultVariants: { size: "default", variant: "default" },
6269
- variants: {
6270
- size: {
6271
- default: "h-8 px-3",
6272
- sm: "h-7 gap-1.5 px-2.5",
6273
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6274
- lg: "h-9 px-3.5",
6275
- xl: "h-10 px-4 text-base",
6276
- icon: "size-8",
6277
- "icon-sm": "size-7",
6278
- "icon-lg": "size-9",
6279
- "icon-xs": "size-6 rounded-md"
6280
- },
6281
- variant: {
6282
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6283
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6284
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6285
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6286
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6287
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6288
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6289
- }
6290
- }
6291
- });
6292
-
6293
- // src/browser/views/primitives/entity-presence.ts
6294
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6295
- "element",
6296
- "region",
6297
- "widget",
6298
- "primitive"
6299
- ]);
6300
- var TOUCH_RESOLVE_KINDS = [
6301
- "element",
6302
- "widget",
6303
- "region",
6304
- "primitive"
6305
- ];
6306
- function isAbsentFromPage(ref2, registry) {
6307
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6308
- return !resolveEntityElement(ref2);
6309
- }
6310
- if (ref2.kind === "flow" && registry) {
6311
- const flow = registry.get("flow", ref2.id);
6312
- if (!flow) return true;
6313
- for (const touchId of flow.touches) {
6314
- for (const kind of TOUCH_RESOLVE_KINDS) {
6315
- const entity = registry.get(kind, touchId);
6316
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6317
- }
6318
- }
6319
- return true;
6320
- }
6321
- return false;
6322
- }
6323
-
6324
- // src/browser/ui/kbd.ts
6325
- 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";
6326
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6327
- function createCommandShortcut(options = {}, children = []) {
6328
- const { class: extra, attrs, ...rest } = options;
6329
- return el(
6330
- "kbd",
6331
- {
6332
- ...rest,
6333
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6334
- attrs: { "data-slot": "command-shortcut", ...attrs }
6335
- },
6336
- children
6337
- );
6338
- }
6339
- function kbdTpl(text, className) {
6340
- return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6341
- >${text}</kbd
6342
- >`;
6343
- }
6344
- function commandShortcutTpl(text, className) {
6345
- return import_lit_html2.html`<kbd
6346
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6347
- data-slot="command-shortcut"
6348
- >${text}</kbd
6349
- >`;
6350
- }
6351
-
6352
- // src/browser/views/primitives/row.ts
6353
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6354
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6355
- function fillRowWithHandle(host, content) {
6356
- if (content.leading) host.append(content.leading);
6357
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6358
- host.append(label);
6359
- if (content.subtitle) {
6360
- host.append(
6361
- el("span", {
6362
- class: SUBTITLE_CLASS,
6363
- attrs: { "data-uidex-row-subtitle": "" },
6364
- text: content.subtitle
6365
- })
6366
- );
6367
- }
6368
- if (content.trailing != null) {
6369
- host.append(
6370
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6371
- );
6372
- }
6373
- return { label };
6374
- }
6375
- function rowTpl(content) {
6376
- return import_lit_html2.html`
6377
- ${content.leading ?? import_lit_html2.nothing}
6378
- <span class=${LABEL_CLASS}>${content.label}</span>
6379
- ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6380
- >${content.subtitle}</span
6381
- >` : import_lit_html2.nothing}
6382
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6383
- `;
6384
- }
6385
-
6386
- // src/browser/views/primitives/entity-link.ts
6387
- var ACTION_CLASS = cn(
6388
- LIST_ITEM_CLASS,
6389
- LIST_ITEM_INTERACTIVE_CLASS,
6390
- "w-full cursor-pointer text-left font-normal"
6391
- );
6392
- function entityLinkTpl(options) {
6393
- const { ctx, target, label, leading, class: extraClass } = options;
6394
- const absent = isAbsentFromPage(target);
6395
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6396
- const onClick = () => ctx.views.navigate(target);
6397
- const preview = () => {
6398
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6399
- };
6400
- const restoreParent = () => {
6401
- if (ctx.ref) {
6402
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6403
- } else {
6404
- ctx.highlight.hide();
6405
- }
6406
- };
6407
- return import_lit_html2.html`
6408
- <button
6409
- type="button"
6410
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6411
- data-slot="list-item-button"
6412
- data-uidex-entity-link
6413
- data-uidex-ref-kind=${target.kind}
6414
- data-uidex-ref-id=${target.id}
6415
- title=${absent ? "Not on this page" : ""}
6416
- @click=${onClick}
6417
- @mouseenter=${preview}
6418
- @mouseleave=${restoreParent}
6419
- @focus=${preview}
6420
- @blur=${restoreParent}
6421
- >
6422
- ${rowTpl({
6423
- leading,
6424
- label: resolvedLabel
6425
- })}
6426
- </button>
6427
- `;
6428
- }
6429
-
6430
5998
  // src/browser/views/primitives/text.ts
6431
5999
  var MUTED_CLASS = "text-sm text-muted-foreground";
6432
6000
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6438,7 +6006,8 @@ function headingTpl(text, className) {
6438
6006
  return import_lit_html2.html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6439
6007
  }
6440
6008
 
6441
- // src/browser/views/render/detail.ts
6009
+ // src/browser/views/render/detail-actions.ts
6010
+ var import_lucide7 = require("lucide");
6442
6011
  var ICON_MAP = {
6443
6012
  "archive-x": import_lucide7.ArchiveX,
6444
6013
  copy: import_lucide7.Copy,
@@ -6454,23 +6023,6 @@ var ICON_MAP = {
6454
6023
  function iconFor(icon2) {
6455
6024
  return icon2 ? ICON_MAP[icon2] : null;
6456
6025
  }
6457
- function subtitleTpl(subtitle) {
6458
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6459
- return import_lit_html2.html`<p
6460
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6461
- data-uidex-detail-subtitle
6462
- >
6463
- ${text}
6464
- </p>`;
6465
- }
6466
- function notFoundTpl(ref2) {
6467
- return import_lit_html2.html`<p
6468
- class=${cn("text-muted-foreground text-sm", "p-4")}
6469
- data-uidex-detail-missing
6470
- >
6471
- ${ref2.kind}: ${ref2.id} not found in registry
6472
- </p>`;
6473
- }
6474
6026
  function renderActions(actions, ctx, root, heading) {
6475
6027
  const cleanups = [];
6476
6028
  const buttons = [];
@@ -6581,6 +6133,166 @@ function renderActions(actions, ctx, root, heading) {
6581
6133
  }
6582
6134
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6583
6135
  }
6136
+
6137
+ // src/browser/views/render/detail-sections.ts
6138
+ var import_static = require("lit-html/static.js");
6139
+
6140
+ // src/browser/views/builder/filter.ts
6141
+ function normalizeQuery(query) {
6142
+ return query.trim().toLowerCase();
6143
+ }
6144
+ function matchesQuery(haystack, query) {
6145
+ const q = normalizeQuery(query);
6146
+ if (!q) return true;
6147
+ return haystack.toLowerCase().includes(q);
6148
+ }
6149
+ function filterEntities(entities, query) {
6150
+ const q = normalizeQuery(query);
6151
+ if (!q) return entities;
6152
+ return entities.filter((e) => {
6153
+ const name = displayName(e).toLowerCase();
6154
+ const id = entityKey(e).toLowerCase();
6155
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6156
+ });
6157
+ }
6158
+ function filterIds(ids, query) {
6159
+ const q = normalizeQuery(query);
6160
+ if (!q) return ids;
6161
+ return ids.filter((id) => id.toLowerCase().includes(q));
6162
+ }
6163
+
6164
+ // src/browser/views/primitives/entity-presence.ts
6165
+ var TOUCH_RESOLVE_KINDS = [
6166
+ "element",
6167
+ "widget",
6168
+ "region",
6169
+ "primitive"
6170
+ ];
6171
+ function isAbsentFromPage(ref2, registry) {
6172
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6173
+ return !resolveEntityElement(ref2);
6174
+ }
6175
+ if (ref2.kind === "flow" && registry) {
6176
+ const flow = registry.get("flow", ref2.id);
6177
+ if (!flow) return true;
6178
+ for (const touchId of flow.touches) {
6179
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6180
+ const entity = registry.get(kind, touchId);
6181
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6182
+ }
6183
+ }
6184
+ return true;
6185
+ }
6186
+ return false;
6187
+ }
6188
+
6189
+ // src/browser/ui/kbd.ts
6190
+ 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";
6191
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6192
+ function createCommandShortcut(options = {}, children = []) {
6193
+ const { class: extra, attrs, ...rest } = options;
6194
+ return el(
6195
+ "kbd",
6196
+ {
6197
+ ...rest,
6198
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6199
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6200
+ },
6201
+ children
6202
+ );
6203
+ }
6204
+ function kbdTpl(text, className) {
6205
+ return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6206
+ >${text}</kbd
6207
+ >`;
6208
+ }
6209
+ function commandShortcutTpl(text, className) {
6210
+ return import_lit_html2.html`<kbd
6211
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6212
+ data-slot="command-shortcut"
6213
+ >${text}</kbd
6214
+ >`;
6215
+ }
6216
+
6217
+ // src/browser/views/primitives/row.ts
6218
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6219
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6220
+ function fillRowWithHandle(host, content) {
6221
+ if (content.leading) host.append(content.leading);
6222
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6223
+ host.append(label);
6224
+ if (content.subtitle) {
6225
+ host.append(
6226
+ el("span", {
6227
+ class: SUBTITLE_CLASS,
6228
+ attrs: { "data-uidex-row-subtitle": "" },
6229
+ text: content.subtitle
6230
+ })
6231
+ );
6232
+ }
6233
+ if (content.trailing != null) {
6234
+ host.append(
6235
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6236
+ );
6237
+ }
6238
+ return { label };
6239
+ }
6240
+ function rowTpl(content) {
6241
+ return import_lit_html2.html`
6242
+ ${content.leading ?? import_lit_html2.nothing}
6243
+ <span class=${LABEL_CLASS}>${content.label}</span>
6244
+ ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6245
+ >${content.subtitle}</span
6246
+ >` : import_lit_html2.nothing}
6247
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6248
+ `;
6249
+ }
6250
+
6251
+ // src/browser/views/primitives/entity-link.ts
6252
+ var ACTION_CLASS = cn(
6253
+ LIST_ITEM_CLASS,
6254
+ LIST_ITEM_INTERACTIVE_CLASS,
6255
+ "w-full cursor-pointer text-left font-normal"
6256
+ );
6257
+ function entityLinkTpl(options) {
6258
+ const { ctx, target, label, leading, class: extraClass } = options;
6259
+ const absent = isAbsentFromPage(target);
6260
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6261
+ const onClick = () => ctx.views.navigate(target);
6262
+ const preview = () => {
6263
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6264
+ };
6265
+ const restoreParent = () => {
6266
+ if (ctx.ref) {
6267
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6268
+ } else {
6269
+ ctx.highlight.hide();
6270
+ }
6271
+ };
6272
+ return import_lit_html2.html`
6273
+ <button
6274
+ type="button"
6275
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6276
+ data-slot="list-item-button"
6277
+ data-uidex-entity-link
6278
+ data-uidex-ref-kind=${target.kind}
6279
+ data-uidex-ref-id=${target.id}
6280
+ title=${absent ? "Not on this page" : ""}
6281
+ @click=${onClick}
6282
+ @mouseenter=${preview}
6283
+ @mouseleave=${restoreParent}
6284
+ @focus=${preview}
6285
+ @blur=${restoreParent}
6286
+ >
6287
+ ${rowTpl({
6288
+ leading,
6289
+ label: resolvedLabel
6290
+ })}
6291
+ </button>
6292
+ `;
6293
+ }
6294
+
6295
+ // src/browser/views/render/detail-sections.ts
6584
6296
  function entityListItems(ctx, entities) {
6585
6297
  return import_lit_html2.html`${entities.map((entity) => {
6586
6298
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6594,71 +6306,29 @@ function entityListItems(ctx, entities) {
6594
6306
  </li>`;
6595
6307
  })}`;
6596
6308
  }
6597
- function composesListTpl(ctx, label, entities) {
6598
- if (entities.length === 0) return null;
6599
- return import_lit_html2.html`
6600
- <section class="flex flex-col" data-uidex-detail-composes>
6601
- ${headingTpl(label)}
6602
- <ul class="flex flex-col">
6603
- ${entityListItems(ctx, entities)}
6604
- </ul>
6605
- </section>
6606
- `;
6607
- }
6608
- function usedByListTpl(ctx, label, entities) {
6609
- if (entities.length === 0) return null;
6610
- return import_lit_html2.html`
6611
- <section class="flex flex-col" data-uidex-detail-used-by>
6612
- ${headingTpl(label)}
6613
- <ul class="flex flex-col">
6614
- ${entityListItems(ctx, entities)}
6615
- </ul>
6616
- </section>
6617
- `;
6618
- }
6619
- function flowListTpl(ctx, flows) {
6620
- if (flows.length === 0) return null;
6621
- return import_lit_html2.html`
6622
- <section class="flex flex-col" data-uidex-detail-flows>
6623
- ${headingTpl(SECTION_LABELS.flows)}
6624
- <ul class="flex flex-col">
6625
- ${flows.map(
6626
- (flow) => import_lit_html2.html`<li>
6627
- ${entityLinkTpl({
6628
- ctx,
6629
- target: { kind: "flow", id: flow.id },
6630
- label: displayName(flow),
6631
- leading: kindIconTileTpl("flow")
6632
- })}
6633
- </li>`
6634
- )}
6635
- </ul>
6636
- </section>
6637
- `;
6638
- }
6639
- function touchesTpl(ctx, entities, query) {
6309
+ var DETAIL_SECTION_ATTRS = {
6310
+ composes: import_static.literal`data-uidex-detail-composes`,
6311
+ "used-by": import_static.literal`data-uidex-detail-used-by`,
6312
+ flows: import_static.literal`data-uidex-detail-flows`,
6313
+ touches: import_static.literal`data-uidex-detail-touches`
6314
+ };
6315
+ function entitySectionTpl(ctx, opts) {
6316
+ const { label, entities, dataAttr, emptyText } = opts;
6317
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6640
6318
  if (entities.length === 0) {
6641
- return import_lit_html2.html`
6642
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6643
- ${headingTpl(SECTION_LABELS.touches)}
6644
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6319
+ if (emptyText === void 0) return null;
6320
+ return import_static.html`
6321
+ <section class="flex flex-col gap-2" ${attr}>
6322
+ ${headingTpl(label)}
6323
+ ${mutedTextTpl(emptyText)}
6645
6324
  </section>
6646
6325
  `;
6647
6326
  }
6648
- return import_lit_html2.html`
6649
- <section class="flex flex-col" data-uidex-detail-touches>
6650
- ${headingTpl(SECTION_LABELS.touches)}
6327
+ return import_static.html`
6328
+ <section class="flex flex-col" ${attr}>
6329
+ ${headingTpl(label)}
6651
6330
  <ul class="flex flex-col">
6652
- ${entities.map(
6653
- (entity) => import_lit_html2.html`<li>
6654
- ${entityLinkTpl({
6655
- ctx,
6656
- target: { kind: entity.kind, id: entityKey(entity) },
6657
- label: displayName(entity),
6658
- leading: kindIconTileTpl(entity.kind)
6659
- })}
6660
- </li>`
6661
- )}
6331
+ ${entityListItems(ctx, entities)}
6662
6332
  </ul>
6663
6333
  </section>
6664
6334
  `;
@@ -6815,6 +6485,20 @@ function screenshotTpl(url) {
6815
6485
  </section>
6816
6486
  `;
6817
6487
  }
6488
+ function lazyScreenshotEl(load) {
6489
+ const holder = document.createElement("div");
6490
+ holder.hidden = true;
6491
+ void load().then(
6492
+ (url) => {
6493
+ if (!url) return;
6494
+ (0, import_lit_html2.render)(screenshotTpl(url), holder);
6495
+ holder.hidden = false;
6496
+ },
6497
+ () => {
6498
+ }
6499
+ );
6500
+ return holder;
6501
+ }
6818
6502
  function metadataListTpl(entries) {
6819
6503
  return import_lit_html2.html`
6820
6504
  <div
@@ -6845,28 +6529,30 @@ function sectionTpl(section, ctx, query) {
6845
6529
  return acceptanceChecklistTpl(section.items);
6846
6530
  }
6847
6531
  case "composes":
6848
- return composesListTpl(
6849
- ctx,
6850
- section.label,
6851
- section.filterable ? filterEntities(section.entities, query) : section.entities
6852
- );
6532
+ return entitySectionTpl(ctx, {
6533
+ label: section.label,
6534
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6535
+ dataAttr: "composes"
6536
+ });
6853
6537
  case "used-by":
6854
- return usedByListTpl(
6855
- ctx,
6856
- section.label,
6857
- section.filterable ? filterEntities(section.entities, query) : section.entities
6858
- );
6538
+ return entitySectionTpl(ctx, {
6539
+ label: section.label,
6540
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6541
+ dataAttr: "used-by"
6542
+ });
6859
6543
  case "flows":
6860
- return flowListTpl(
6861
- ctx,
6862
- section.filterable ? filterEntities(section.flows, query) : section.flows
6863
- );
6544
+ return entitySectionTpl(ctx, {
6545
+ label: SECTION_LABELS.flows,
6546
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6547
+ dataAttr: "flows"
6548
+ });
6864
6549
  case "touches":
6865
- return touchesTpl(
6866
- ctx,
6867
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6868
- query
6869
- );
6550
+ return entitySectionTpl(ctx, {
6551
+ label: SECTION_LABELS.touches,
6552
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6553
+ dataAttr: "touches",
6554
+ emptyText: query ? "No matches" : "No entities touched"
6555
+ });
6870
6556
  case "steps":
6871
6557
  return stepsTpl(
6872
6558
  ctx,
@@ -6878,11 +6564,32 @@ function sectionTpl(section, ctx, query) {
6878
6564
  section.filterable ? filterIds(section.paths, query) : section.paths
6879
6565
  );
6880
6566
  case "screenshot":
6881
- return screenshotTpl(section.url);
6567
+ if (section.url) return screenshotTpl(section.url);
6568
+ if (section.load) return import_lit_html2.html`${lazyScreenshotEl(section.load)}`;
6569
+ return null;
6882
6570
  case "metadata":
6883
6571
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6884
6572
  }
6885
6573
  }
6574
+
6575
+ // src/browser/views/render/detail.ts
6576
+ function subtitleTpl(subtitle) {
6577
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6578
+ return import_lit_html2.html`<p
6579
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6580
+ data-uidex-detail-subtitle
6581
+ >
6582
+ ${text}
6583
+ </p>`;
6584
+ }
6585
+ function notFoundTpl(ref2) {
6586
+ return import_lit_html2.html`<p
6587
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6588
+ data-uidex-detail-missing
6589
+ >
6590
+ ${ref2.kind}: ${ref2.id} not found in registry
6591
+ </p>`;
6592
+ }
6886
6593
  function hasFilterableSections(sections) {
6887
6594
  return sections.some((s) => "filterable" in s && s.filterable === true);
6888
6595
  }
@@ -6947,7 +6654,10 @@ function renderDetailSurface(surface, ctx, root) {
6947
6654
  >
6948
6655
  ${surface.title}
6949
6656
  </h2>` : import_lit_html2.nothing}
6950
- <span class="ml-auto">${kindBadgeTpl(surface.entityKind)}</span>
6657
+ <span class="ml-auto flex items-center gap-1">
6658
+ ${surface.unregistered ? badgeTpl("Unregistered", { variant: "warning", size: "sm" }) : import_lit_html2.nothing}
6659
+ ${kindBadgeTpl(surface.entityKind)}
6660
+ </span>
6951
6661
  </div>
6952
6662
  ${surface.subtitle ? subtitleTpl(surface.subtitle) : import_lit_html2.nothing}
6953
6663
  <div
@@ -7240,6 +6950,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7240
6950
  };
7241
6951
  }
7242
6952
 
6953
+ // src/browser/ui/button.ts
6954
+ 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";
6955
+ var buttonVariants = cva(buttonBase, {
6956
+ defaultVariants: { size: "default", variant: "default" },
6957
+ variants: {
6958
+ size: {
6959
+ default: "h-8 px-3",
6960
+ sm: "h-7 gap-1.5 px-2.5",
6961
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6962
+ lg: "h-9 px-3.5",
6963
+ xl: "h-10 px-4 text-base",
6964
+ icon: "size-8",
6965
+ "icon-sm": "size-7",
6966
+ "icon-lg": "size-9",
6967
+ "icon-xs": "size-6 rounded-md"
6968
+ },
6969
+ variant: {
6970
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6971
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6972
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6973
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6974
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6975
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6976
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6977
+ }
6978
+ }
6979
+ });
6980
+
7243
6981
  // src/browser/views/render/form.ts
7244
6982
  var fieldSeq = 0;
7245
6983
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7436,9 +7174,9 @@ function iconMediaTpl(iconTpl) {
7436
7174
  </div>
7437
7175
  `;
7438
7176
  }
7439
- function loadingViewTpl() {
7177
+ function loadingViewTpl(rootRef) {
7440
7178
  return import_lit_html2.html`
7441
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7179
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${(0, import_ref.ref)(rootRef)}>
7442
7180
  ${iconMediaTpl(icon(import_lucide8.Loader2, "animate-spin"))}
7443
7181
  <div class="flex max-w-sm flex-col items-center text-center">
7444
7182
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7451,42 +7189,47 @@ function loadingViewTpl() {
7451
7189
  </div>
7452
7190
  `;
7453
7191
  }
7454
- function successViewTpl() {
7192
+ function successViewTpl(refs) {
7455
7193
  return import_lit_html2.html`
7456
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7194
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${(0, import_ref.ref)(refs.root)}>
7457
7195
  ${iconMediaTpl(icon(import_lucide8.CircleCheck))}
7458
7196
  <div class="flex max-w-sm flex-col items-center text-center">
7459
7197
  <div
7460
7198
  class="font-heading text-xl font-semibold"
7461
7199
  data-uidex-success-title
7200
+ ${(0, import_ref.ref)(refs.title)}
7462
7201
  ></div>
7463
7202
  </div>
7464
7203
  <div
7465
7204
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7466
7205
  data-uidex-success-actions
7206
+ ${(0, import_ref.ref)(refs.actions)}
7467
7207
  >
7468
7208
  <a
7469
7209
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7470
7210
  target="_blank"
7471
7211
  rel="noreferrer"
7472
7212
  data-uidex-success-link
7213
+ ${(0, import_ref.ref)(refs.link)}
7473
7214
  ></a>
7474
7215
  </div>
7475
7216
  </div>
7476
7217
  `;
7477
7218
  }
7478
- function errorViewTpl() {
7219
+ function errorViewTpl(refs) {
7479
7220
  return import_lit_html2.html`
7480
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7221
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${(0, import_ref.ref)(refs.root)}>
7481
7222
  ${iconMediaTpl(icon(import_lucide8.CircleX))}
7482
7223
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7483
7224
  <div
7484
7225
  class="font-heading text-xl font-semibold"
7485
7226
  data-uidex-error-title
7227
+ ${(0, import_ref.ref)(refs.title)}
7486
7228
  ></div>
7487
7229
  <p
7488
7230
  class="text-muted-foreground text-sm"
7489
7231
  data-uidex-error-description
7232
+ ${(0, import_ref.ref)(refs.description)}
7490
7233
  ></p>
7491
7234
  </div>
7492
7235
  <div
@@ -7497,6 +7240,7 @@ function errorViewTpl() {
7497
7240
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7498
7241
  data-slot="button"
7499
7242
  data-uidex-retry-button
7243
+ ${(0, import_ref.ref)(refs.retry)}
7500
7244
  >
7501
7245
  Try Again
7502
7246
  </button>
@@ -7538,8 +7282,15 @@ function renderFormSurface(surface, ctx, root) {
7538
7282
  });
7539
7283
  }
7540
7284
  const formRef = (0, import_ref.createRef)();
7541
- const statusRef = (0, import_ref.createRef)();
7542
- const submitRef = (0, import_ref.createRef)();
7285
+ const loadingRef = (0, import_ref.createRef)();
7286
+ const successRef = (0, import_ref.createRef)();
7287
+ const successTitleRef = (0, import_ref.createRef)();
7288
+ const successLinkRef = (0, import_ref.createRef)();
7289
+ const successActionsRef = (0, import_ref.createRef)();
7290
+ const errorRef = (0, import_ref.createRef)();
7291
+ const errorTitleRef = (0, import_ref.createRef)();
7292
+ const errorDescriptionRef = (0, import_ref.createRef)();
7293
+ const retryRef = (0, import_ref.createRef)();
7543
7294
  (0, import_lit_html2.render)(
7544
7295
  import_lit_html2.html`
7545
7296
  <section
@@ -7554,7 +7305,19 @@ function renderFormSurface(surface, ctx, root) {
7554
7305
  novalidate
7555
7306
  data-uidex-form=${surface.id}
7556
7307
  ></form>
7557
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7308
+ ${loadingViewTpl(loadingRef)}
7309
+ ${successViewTpl({
7310
+ root: successRef,
7311
+ title: successTitleRef,
7312
+ link: successLinkRef,
7313
+ actions: successActionsRef
7314
+ })}
7315
+ ${errorViewTpl({
7316
+ root: errorRef,
7317
+ title: errorTitleRef,
7318
+ description: errorDescriptionRef,
7319
+ retry: retryRef
7320
+ })}
7558
7321
  </section>
7559
7322
  `,
7560
7323
  root
@@ -7584,32 +7347,15 @@ function renderFormSurface(surface, ctx, root) {
7584
7347
  }
7585
7348
  });
7586
7349
  form.append(status);
7587
- const sectionEl = root.querySelector(
7588
- "[data-uidex-form-surface]"
7589
- );
7590
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7591
- const successView = sectionEl.querySelector(
7592
- "[data-uidex-success-view]"
7593
- );
7594
- const successTitle = sectionEl.querySelector(
7595
- "[data-uidex-success-title]"
7596
- );
7597
- const successLink = sectionEl.querySelector(
7598
- "[data-uidex-success-link]"
7599
- );
7600
- const successActions = successLink.parentElement;
7601
- const errorView = sectionEl.querySelector(
7602
- "[data-uidex-error-view]"
7603
- );
7604
- const errorTitle = sectionEl.querySelector(
7605
- "[data-uidex-error-title]"
7606
- );
7607
- const errorDescription = sectionEl.querySelector(
7608
- "[data-uidex-error-description]"
7609
- );
7610
- const retryButton = sectionEl.querySelector(
7611
- "[data-uidex-retry-button]"
7612
- );
7350
+ const loadingView = loadingRef.value;
7351
+ const successView = successRef.value;
7352
+ const successTitle = successTitleRef.value;
7353
+ const successLink = successLinkRef.value;
7354
+ const successActions = successActionsRef.value;
7355
+ const errorView = errorRef.value;
7356
+ const errorTitle = errorTitleRef.value;
7357
+ const errorDescription = errorDescriptionRef.value;
7358
+ const retryButton = retryRef.value;
7613
7359
  function clearAllFieldErrors() {
7614
7360
  for (const f of fields) f.setError(null);
7615
7361
  }
@@ -8010,7 +7756,7 @@ function defaultFilter(item, query) {
8010
7756
  function rowTag(item) {
8011
7757
  return item.tag ?? item.value;
8012
7758
  }
8013
- function resolveLeadingTpl(item, ctx) {
7759
+ function resolveLeadingTpl(item) {
8014
7760
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
8015
7761
  if (item.leading) {
8016
7762
  const node = item.leading();
@@ -8115,7 +7861,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8115
7861
  data-uidex-item-value=${item.value}
8116
7862
  >
8117
7863
  ${rowTpl({
8118
- leading: resolveLeadingTpl(item, ctx),
7864
+ leading: resolveLeadingTpl(item),
8119
7865
  label: item.label,
8120
7866
  subtitle: item.subtitle,
8121
7867
  trailing: item.trailing ?? item.shortcut
@@ -8160,7 +7906,7 @@ function renderListSurface(surface, ctx, root) {
8160
7906
  return root.ownerDocument ?? document;
8161
7907
  };
8162
7908
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8163
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7909
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8164
7910
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8165
7911
  const controller = createListController({
8166
7912
  surfaceId: surface.id,
@@ -8856,52 +8602,16 @@ function sameRef2(a, b) {
8856
8602
  if (a === null || b === null) return false;
8857
8603
  return a.kind === b.kind && a.id === b.id;
8858
8604
  }
8859
- function resolveHints(view, ctx) {
8860
- const h = view.hints;
8861
- if (!h) return [];
8862
- if (typeof h === "function") {
8863
- try {
8864
- return h(ctx) ?? [];
8865
- } catch (err) {
8866
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8867
- return [];
8868
- }
8869
- }
8870
- return h;
8871
- }
8872
- function resolveTitle(view, ctx) {
8873
- const t = view.title;
8874
- if (!t) return "";
8875
- if (typeof t === "function") {
8876
- try {
8877
- return t(ctx) ?? "";
8878
- } catch (err) {
8879
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8880
- return "";
8881
- }
8882
- }
8883
- return t;
8884
- }
8885
- function resolveActions(view, ctx, globalActions) {
8886
- const result = [];
8887
- const fn = view.actions;
8888
- if (fn) {
8889
- try {
8890
- const viewActions2 = fn(ctx) ?? [];
8891
- result.push(...viewActions2);
8892
- } catch (err) {
8893
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8894
- }
8895
- }
8896
- if (globalActions) {
8605
+ function resolveProp(view, ctx, value, propName, fallback) {
8606
+ if (typeof value === "function") {
8897
8607
  try {
8898
- const globals = globalActions(ctx) ?? [];
8899
- result.push(...globals);
8608
+ return value(ctx) ?? fallback;
8900
8609
  } catch (err) {
8901
- console.error(`[uidex] globalActions() threw`, err);
8610
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8611
+ return fallback;
8902
8612
  }
8903
8613
  }
8904
- return result;
8614
+ return value ?? fallback;
8905
8615
  }
8906
8616
  function createViewStack(options) {
8907
8617
  const { container, views, session, registry, highlight } = options;
@@ -8969,39 +8679,40 @@ function createViewStack(options) {
8969
8679
  color: KIND_STYLE[top.ctx.ref.kind].color
8970
8680
  });
8971
8681
  }
8972
- function updateChrome() {
8973
- if (!shell) return;
8974
- const top = mounted[mounted.length - 1];
8975
- if (!top) return;
8682
+ function updateNavButtons(shell2, top) {
8976
8683
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8977
- shell.backBtn.hidden = atRoot;
8978
- shell.searchIcon.hidden = !atRoot;
8684
+ shell2.backBtn.hidden = atRoot;
8685
+ shell2.searchIcon.hidden = !atRoot;
8686
+ }
8687
+ function updateTitle(shell2, top) {
8979
8688
  const searchable = top.view.searchable !== false;
8980
- shell.searchInput.hidden = !searchable;
8981
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8982
- shell.headerTitle.textContent = titleText;
8983
- shell.headerTitle.hidden = searchable || !titleText;
8984
- shell.footerLeft.replaceChildren();
8689
+ shell2.searchInput.hidden = !searchable;
8690
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8691
+ shell2.headerTitle.textContent = titleText;
8692
+ shell2.headerTitle.hidden = searchable || !titleText;
8693
+ }
8694
+ function updateFooterChip(shell2, top) {
8695
+ shell2.footerLeft.replaceChildren();
8985
8696
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8986
8697
  if (refEntity) {
8987
- shell.footerLeft.append(
8698
+ shell2.footerLeft.append(
8988
8699
  renderKindChip({
8989
8700
  entity: refEntity,
8990
8701
  withKindName: true
8991
8702
  })
8992
8703
  );
8993
8704
  } else {
8994
- shell.footerLeft.append(shell.logo);
8705
+ shell2.footerLeft.append(shell2.logo);
8995
8706
  }
8996
- hintChangeSub?.();
8997
- hintChangeSub = null;
8998
- highlightActionsSub?.();
8999
- highlightActionsSub = null;
9000
- shell.footerRight.replaceChildren();
9001
- const footerItems = [];
9002
- const enterHint = resolveHints(top.view, top.ctx).find(
9003
- (h) => h.key.includes("\u21B5")
9004
- );
8707
+ }
8708
+ function buildEnterHint(top, footerItems) {
8709
+ const enterHint = resolveProp(
8710
+ top.view,
8711
+ top.ctx,
8712
+ top.view.hints,
8713
+ "hints",
8714
+ []
8715
+ ).find((h) => h.key.includes("\u21B5"));
9005
8716
  const src = top.mounted.submitIntent;
9006
8717
  if (src) {
9007
8718
  const key = enterHint?.key ?? "\u21B5";
@@ -9022,28 +8733,52 @@ function createViewStack(options) {
9022
8733
  } else if (enterHint) {
9023
8734
  footerItems.push(createHint(enterHint.key, enterHint.label));
9024
8735
  }
8736
+ }
8737
+ function buildActions(shell2, top, footerItems) {
9025
8738
  const actionsDivider = createFooterDivider();
9026
- const viewActions2 = resolveActions(top.view, top.ctx);
8739
+ const viewActions2 = resolveProp(
8740
+ top.view,
8741
+ top.ctx,
8742
+ top.view.actions,
8743
+ "actions",
8744
+ []
8745
+ );
9027
8746
  const hlSrc = top.mounted.highlightActions;
9028
8747
  const syncActions = () => {
9029
8748
  const hlActions = hlSrc?.get() ?? [];
9030
8749
  const combined = [...hlActions, ...viewActions2];
9031
- shell.actionsPopup.setActions(combined);
8750
+ shell2.actionsPopup.setActions(combined);
9032
8751
  const visible = combined.length > 0;
9033
- shell.actionsPopup.trigger.hidden = !visible;
8752
+ shell2.actionsPopup.trigger.hidden = !visible;
9034
8753
  actionsDivider.hidden = !visible || footerItems.length === 0;
9035
8754
  };
9036
8755
  if (hlSrc) {
9037
8756
  highlightActionsSub = hlSrc.subscribe(syncActions);
9038
8757
  }
9039
8758
  for (let i = 0; i < footerItems.length; i++) {
9040
- if (i > 0) shell.footerRight.append(createFooterDivider());
9041
- shell.footerRight.append(footerItems[i]);
8759
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8760
+ shell2.footerRight.append(footerItems[i]);
9042
8761
  }
9043
- shell.footerRight.append(actionsDivider);
9044
- shell.footerRight.append(shell.actionsPopup.trigger);
8762
+ shell2.footerRight.append(actionsDivider);
8763
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
9045
8764
  syncActions();
9046
8765
  }
8766
+ function updateChrome() {
8767
+ if (!shell) return;
8768
+ const top = mounted[mounted.length - 1];
8769
+ if (!top) return;
8770
+ updateNavButtons(shell, top);
8771
+ updateTitle(shell, top);
8772
+ updateFooterChip(shell, top);
8773
+ hintChangeSub?.();
8774
+ hintChangeSub = null;
8775
+ highlightActionsSub?.();
8776
+ highlightActionsSub = null;
8777
+ shell.footerRight.replaceChildren();
8778
+ const footerItems = [];
8779
+ buildEnterHint(top, footerItems);
8780
+ buildActions(shell, top, footerItems);
8781
+ }
9047
8782
  function render2() {
9048
8783
  if (!container.isConnected) return;
9049
8784
  const stack = session.getState().stack;
@@ -9140,7 +8875,7 @@ function createViewStack(options) {
9140
8875
 
9141
8876
  // src/browser/views/built-in/ids.ts
9142
8877
  var BUILT_IN_VIEW_IDS = {
9143
- archiveReason: "archive-reason",
8878
+ closeReason: "close-reason",
9144
8879
  commandPalette: "command-palette",
9145
8880
  elements: "elements",
9146
8881
  entityReports: "entity-reports",
@@ -9191,13 +8926,13 @@ function parentDetail(ref2) {
9191
8926
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9192
8927
  }
9193
8928
 
9194
- // src/browser/views/built-in/archive-reason.ts
8929
+ // src/browser/views/built-in/close-reason.ts
9195
8930
  var import_lucide11 = require("lucide");
9196
8931
  var pendingReportId = null;
9197
- var afterArchive = null;
9198
- function setArchiveTarget(reportId, onDone) {
8932
+ var afterClose = null;
8933
+ function setCloseTarget(reportId, onDone) {
9199
8934
  pendingReportId = reportId;
9200
- afterArchive = onDone;
8935
+ afterClose = onDone;
9201
8936
  }
9202
8937
  function leadingIcon(iconNode) {
9203
8938
  return () => {
@@ -9212,16 +8947,16 @@ function spinnerTile() {
9212
8947
  return createIconTile(spinner);
9213
8948
  }
9214
8949
  var REASONS = [
9215
- { value: "fixed", label: "Fixed", icon: import_lucide11.CircleCheck },
8950
+ { value: "fixed", label: "Resolved", icon: import_lucide11.CircleCheck },
9216
8951
  { value: "not_a_bug", label: "Not a bug", icon: import_lucide11.BugOff },
9217
8952
  { value: "duplicate", label: "Duplicate", icon: import_lucide11.Copy },
9218
8953
  { value: "wont_fix", label: "Won't fix", icon: import_lucide11.Ban }
9219
8954
  ];
9220
- var archiveReasonView = {
9221
- id: BUILT_IN_VIEW_IDS.archiveReason,
8955
+ var closeReasonView = {
8956
+ id: BUILT_IN_VIEW_IDS.closeReason,
9222
8957
  matches: () => false,
9223
8958
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9224
- title: "Archive reason",
8959
+ title: "Close reason",
9225
8960
  searchable: false,
9226
8961
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9227
8962
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9234,12 +8969,12 @@ var archiveReasonView = {
9234
8969
  }));
9235
8970
  const surface = {
9236
8971
  kind: "list",
9237
- id: "uidex-archive-reason",
8972
+ id: "uidex-close-reason",
9238
8973
  searchable: false,
9239
8974
  items,
9240
8975
  emptyLabel: "No reasons available",
9241
8976
  onSelect: async (item) => {
9242
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8977
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9243
8978
  busy = true;
9244
8979
  const row = root.querySelector(
9245
8980
  `[data-uidex-item-value="${item.value}"]`
@@ -9251,13 +8986,10 @@ var archiveReasonView = {
9251
8986
  row.style.pointerEvents = "none";
9252
8987
  }
9253
8988
  try {
9254
- await ctx.registry.archiveReport(
9255
- pendingReportId,
9256
- item.value
9257
- );
9258
- const done = afterArchive;
8989
+ await ctx.registry.closeReport(pendingReportId, item.value);
8990
+ const done = afterClose;
9259
8991
  pendingReportId = null;
9260
- afterArchive = null;
8992
+ afterClose = null;
9261
8993
  done?.();
9262
8994
  } catch {
9263
8995
  busy = false;
@@ -9275,7 +9007,7 @@ var archiveReasonView = {
9275
9007
  }
9276
9008
  const error = document.createElement("p");
9277
9009
  error.className = "text-destructive px-4 py-2 text-xs";
9278
- error.textContent = "Archive failed \u2014 try again";
9010
+ error.textContent = "Close failed \u2014 try again";
9279
9011
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9280
9012
  setTimeout(() => error.remove(), 3e3);
9281
9013
  }
@@ -9594,6 +9326,7 @@ function createCommandPaletteView(shortcut) {
9594
9326
 
9595
9327
  // src/browser/internal/screenshot.ts
9596
9328
  var import_modern_screenshot = require("modern-screenshot");
9329
+ var WEBP_QUALITY = 0.85;
9597
9330
  function resolveBackgroundColor(el2) {
9598
9331
  let node = el2;
9599
9332
  while (node) {
@@ -9607,8 +9340,7 @@ function isUidexChrome(node) {
9607
9340
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9608
9341
  const el2 = node;
9609
9342
  if (el2.shadowRoot !== null) return true;
9610
- const cls = el2.classList;
9611
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9343
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9612
9344
  }
9613
9345
  async function captureScreenshot(options = {}) {
9614
9346
  if (typeof document === "undefined") {
@@ -9620,8 +9352,10 @@ async function captureScreenshot(options = {}) {
9620
9352
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9621
9353
  const padding = 16;
9622
9354
  const height = target.scrollHeight || target.clientHeight;
9623
- return (0, import_modern_screenshot.domToPng)(target, {
9355
+ const encode = options.format === "png" ? import_modern_screenshot.domToPng : import_modern_screenshot.domToWebp;
9356
+ return encode(target, {
9624
9357
  scale,
9358
+ quality: WEBP_QUALITY,
9625
9359
  width: width + padding * 2,
9626
9360
  height: height + padding * 2,
9627
9361
  backgroundColor: resolveBackgroundColor(target),
@@ -9677,12 +9411,6 @@ function collectFlowsTouching(ctx, targetId) {
9677
9411
  }
9678
9412
 
9679
9413
  // src/browser/views/builder/detail-builder.ts
9680
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9681
- "element",
9682
- "region",
9683
- "widget",
9684
- "primitive"
9685
- ]);
9686
9414
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9687
9415
  function copyPathAction(ref2, loc) {
9688
9416
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9784,7 +9512,10 @@ function copyScreenshotAction(ref2) {
9784
9512
  const target = resolveEntityElement(ref2) ?? void 0;
9785
9513
  const dataUrl = await captureScreenshot({
9786
9514
  target,
9787
- maxWidth: 1280
9515
+ maxWidth: 1280,
9516
+ // Clipboard write requires PNG — the async Clipboard API rejects
9517
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9518
+ format: "png"
9788
9519
  });
9789
9520
  const res = await fetch(dataUrl);
9790
9521
  const blob = await res.blob();
@@ -9817,12 +9548,11 @@ function createEntityDetailView(config) {
9817
9548
  if (!ctx.ref || ctx.ref.kind !== kind) {
9818
9549
  return { kind: "detail", entityKind: kind };
9819
9550
  }
9820
- const entity = ctx.registry.get(kind, ctx.ref.id);
9821
- if (!entity) {
9822
- return { kind: "detail", entityKind: kind, notFound: ctx.ref };
9823
- }
9824
- const metaEntity = entity;
9825
- const meta = metaEntity.meta;
9551
+ const exactEntity = ctx.registry.get(kind, ctx.ref.id);
9552
+ const patternEntity = exactEntity ? void 0 : ctx.registry.matchPattern?.(kind, ctx.ref.id);
9553
+ const entity = exactEntity ?? patternEntity;
9554
+ const metaEntity = entity ? entity : null;
9555
+ const meta = metaEntity?.meta;
9826
9556
  const actions = [];
9827
9557
  const cloud = ctx.cloud;
9828
9558
  actions.push({ ...reportAction(ctx.ref), group: "Report" });
@@ -9836,18 +9566,20 @@ function createEntityDetailView(config) {
9836
9566
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9837
9567
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9838
9568
  }
9839
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9569
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9840
9570
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9841
9571
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9842
9572
  }
9843
- actions.push({
9844
- ...copyPathAction(ctx.ref, metaEntity.loc),
9845
- group: "Inspect"
9846
- });
9847
- actions.push({
9848
- ...copySnapshotAction(ctx.ref, metaEntity.loc),
9849
- group: "Inspect"
9850
- });
9573
+ if (metaEntity?.loc) {
9574
+ actions.push({
9575
+ ...copyPathAction(ctx.ref, metaEntity.loc),
9576
+ group: "Inspect"
9577
+ });
9578
+ actions.push({
9579
+ ...copySnapshotAction(ctx.ref, metaEntity.loc),
9580
+ group: "Inspect"
9581
+ });
9582
+ }
9851
9583
  const sections = [];
9852
9584
  if (meta?.description) {
9853
9585
  sections.push({ id: "description", text: meta.description });
@@ -9855,10 +9587,12 @@ function createEntityDetailView(config) {
9855
9587
  if (offerAcceptance && meta?.acceptance?.length) {
9856
9588
  sections.push({ id: "acceptance", items: meta.acceptance });
9857
9589
  }
9858
- for (const s of config.extraSections?.(ctx, entity) ?? []) {
9859
- sections.push(s);
9590
+ if (entity) {
9591
+ for (const s of config.extraSections?.(ctx, entity) ?? []) {
9592
+ sections.push(s);
9593
+ }
9860
9594
  }
9861
- if (!DOM_BACKED_KINDS2.has(kind)) {
9595
+ if (!DOM_BACKED_KINDS.has(kind)) {
9862
9596
  sections.push({
9863
9597
  id: "composes",
9864
9598
  label: SECTION_LABELS.contains,
@@ -9866,7 +9600,7 @@ function createEntityDetailView(config) {
9866
9600
  filterable: true
9867
9601
  });
9868
9602
  }
9869
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9603
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9870
9604
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9871
9605
  if (featureEntities.length > 0) {
9872
9606
  sections.push({
@@ -9885,8 +9619,9 @@ function createEntityDetailView(config) {
9885
9619
  return {
9886
9620
  kind: "detail",
9887
9621
  entityKind: kind,
9888
- title: displayName(metaEntity),
9889
- subtitle: config.subtitle?.(ctx, entity),
9622
+ title: patternEntity ? ctx.ref.id : metaEntity ? displayName(metaEntity) : ctx.ref.id,
9623
+ subtitle: exactEntity ? config.subtitle?.(ctx, exactEntity) : void 0,
9624
+ unregistered: !entity,
9890
9625
  actions,
9891
9626
  sections
9892
9627
  };
@@ -9895,35 +9630,14 @@ function createEntityDetailView(config) {
9895
9630
  }
9896
9631
 
9897
9632
  // src/browser/views/built-in/entity-detail.ts
9898
- function collectDomParents(ctx, ref2) {
9899
- const el2 = resolveEntityElement(ref2);
9900
- if (!el2) return [];
9901
- const parents = [];
9902
- const seen = /* @__PURE__ */ new Set();
9903
- let node = el2.parentElement;
9904
- while (node) {
9905
- if (node instanceof HTMLElement) {
9906
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9907
- const id = node.getAttribute(attr);
9908
- if (id) {
9909
- const key = `${kind}:${id}`;
9910
- if (!seen.has(key)) {
9911
- seen.add(key);
9912
- const entity = ctx.registry.get(kind, id);
9913
- if (entity) parents.push(entity);
9914
- }
9915
- }
9916
- }
9917
- }
9918
- node = node.parentElement;
9919
- }
9920
- return parents;
9633
+ function collectFeatureConsumers(ctx, featureId) {
9634
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9921
9635
  }
9922
- function usedBySection(ctx, ref2) {
9636
+ function usedBySection(ctx, featureId) {
9923
9637
  return {
9924
9638
  id: "used-by",
9925
9639
  label: SECTION_LABELS.usedBy,
9926
- entities: collectDomParents(ctx, ref2),
9640
+ entities: collectFeatureConsumers(ctx, featureId),
9927
9641
  filterable: true
9928
9642
  };
9929
9643
  }
@@ -9944,9 +9658,7 @@ var featureDetailView = createEntityDetailView({
9944
9658
  id: "feature-detail",
9945
9659
  kind: "feature",
9946
9660
  fallbackTitle: "Feature",
9947
- extraSections: (ctx, entity) => [
9948
- usedBySection(ctx, { kind: "feature", id: entity.id })
9949
- ]
9661
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9950
9662
  });
9951
9663
  var regionDetailView = createEntityDetailView({
9952
9664
  id: "region-detail",
@@ -10074,6 +9786,15 @@ var reportDetailView = {
10074
9786
  }
10075
9787
  if (report.screenshot) {
10076
9788
  sections.push({ id: "screenshot", url: report.screenshot });
9789
+ } else {
9790
+ const pins = ctx.cloud?.pins;
9791
+ const fetchScreenshot = pins?.screenshot;
9792
+ if (pins && fetchScreenshot) {
9793
+ sections.push({
9794
+ id: "screenshot",
9795
+ load: () => fetchScreenshot.call(pins, report.id)
9796
+ });
9797
+ }
10077
9798
  }
10078
9799
  const metaEntries = [];
10079
9800
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -10084,14 +9805,14 @@ var reportDetailView = {
10084
9805
  sections.push({ id: "metadata", entries: metaEntries });
10085
9806
  }
10086
9807
  const actions = [];
10087
- if (ctx.registry.archiveReport) {
9808
+ if (ctx.registry.closeReport) {
10088
9809
  actions.push({
10089
- id: "archive",
10090
- label: "Archive",
9810
+ id: "close",
9811
+ label: "Close",
10091
9812
  icon: "archive-x",
10092
9813
  run: () => {
10093
- setArchiveTarget(report.id, () => ctx.pop());
10094
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9814
+ setCloseTarget(report.id, () => ctx.pop());
9815
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10095
9816
  }
10096
9817
  });
10097
9818
  }
@@ -10129,13 +9850,13 @@ function reportToItem(report, ctx) {
10129
9850
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10130
9851
  const kind = ctx.ref?.kind ?? "element";
10131
9852
  const actions = [];
10132
- if (ctx.registry.archiveReport) {
9853
+ if (ctx.registry.closeReport) {
10133
9854
  actions.push({
10134
- id: `archive-${report.id}`,
10135
- label: "Archive",
9855
+ id: `close-${report.id}`,
9856
+ label: "Close",
10136
9857
  perform: () => {
10137
- setArchiveTarget(report.id, () => ctx.pop());
10138
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9858
+ setCloseTarget(report.id, () => ctx.pop());
9859
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10139
9860
  }
10140
9861
  });
10141
9862
  }
@@ -10288,7 +10009,7 @@ function capitalize2(s) {
10288
10009
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10289
10010
  }
10290
10011
  function renderPayloadMarkdown(payload) {
10291
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
10012
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10292
10013
  const ctx = payload.context;
10293
10014
  const lines = [
10294
10015
  `# ${heading}`,
@@ -10357,12 +10078,6 @@ var reportFields = [
10357
10078
  ];
10358
10079
 
10359
10080
  // src/browser/views/built-in/report/view-builder.ts
10360
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10361
- "element",
10362
- "region",
10363
- "widget",
10364
- "primitive"
10365
- ]);
10366
10081
  var KIND_TO_ATTR = new Map(
10367
10082
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10368
10083
  );
@@ -10433,7 +10148,7 @@ function createReportView(config) {
10433
10148
  const cloud = resolveCloud(ctx);
10434
10149
  const extra = extraFields ? extraFields(ctx) : [];
10435
10150
  const fields = [...extra, ...baseFields ?? reportFields];
10436
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10151
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10437
10152
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10438
10153
  const screenshotPromise = isDomBacked ? captureScreenshot({
10439
10154
  target: targetEl ?? void 0,
@@ -11010,7 +10725,7 @@ var pinSettingsView = {
11010
10725
  // src/browser/views/built-in/index.ts
11011
10726
  function buildDefaultViews(shortcut) {
11012
10727
  return [
11013
- archiveReasonView,
10728
+ closeReasonView,
11014
10729
  createCommandPaletteView(shortcut),
11015
10730
  explorePageView,
11016
10731
  componentDetailView,
@@ -11038,6 +10753,27 @@ function buildDefaultViews(shortcut) {
11038
10753
  }
11039
10754
 
11040
10755
  // src/browser/create-uidex.ts
10756
+ function getVisibleEntities() {
10757
+ if (typeof document === "undefined") return [];
10758
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10759
+ const nodes = document.querySelectorAll(selector);
10760
+ const ids = [];
10761
+ const seen = /* @__PURE__ */ new Set();
10762
+ for (const node of nodes) {
10763
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10764
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10765
+ const id = node.getAttribute(attr);
10766
+ if (!id) continue;
10767
+ const key = `${kind}:${id}`;
10768
+ if (!seen.has(key)) {
10769
+ seen.add(key);
10770
+ ids.push(key);
10771
+ }
10772
+ break;
10773
+ }
10774
+ }
10775
+ return ids;
10776
+ }
11041
10777
  function createUidex(options = {}) {
11042
10778
  const registry = createRegistry();
11043
10779
  const inspectorRef = { current: null };
@@ -11072,7 +10808,7 @@ function createUidex(options = {}) {
11072
10808
  const views = createRouter({ session });
11073
10809
  const cloud = options.cloud ?? null;
11074
10810
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
11075
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10811
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
11076
10812
  if (options.defaultViews !== false) {
11077
10813
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
11078
10814
  }
@@ -11082,6 +10818,94 @@ function createUidex(options = {}) {
11082
10818
  let shadowRoot = null;
11083
10819
  const mountCleanup = createCleanupStack();
11084
10820
  let mounted = false;
10821
+ function syncReportsToRegistry() {
10822
+ const layer = pinLayerRef.current;
10823
+ if (!layer) return;
10824
+ const byEntity = /* @__PURE__ */ new Map();
10825
+ for (const pin of layer.getAllPins()) {
10826
+ const cid = pin.entity ?? "";
10827
+ if (!cid) continue;
10828
+ const list = byEntity.get(cid);
10829
+ if (list) list.push(pin);
10830
+ else byEntity.set(cid, [pin]);
10831
+ }
10832
+ for (const [cid, pins] of byEntity) {
10833
+ const ref2 = parseComponentRef(cid);
10834
+ registry.setReports(ref2.kind, ref2.id, pins);
10835
+ }
10836
+ }
10837
+ function setupKeyBindings(root, viewStack) {
10838
+ const bindings = bindShadowKeys({
10839
+ shadowRoot: root,
10840
+ session,
10841
+ getActionsPopup: () => viewStack.getActionsPopup(),
10842
+ getPinLayer: () => pinLayerRef.current,
10843
+ shortcut: options.shortcut
10844
+ });
10845
+ mountCleanup.add(bindings);
10846
+ return bindings;
10847
+ }
10848
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10849
+ if (cloud) {
10850
+ registry.closeReport = async (reportId, status) => {
10851
+ pinLayerRef.current?.removePin(reportId);
10852
+ await cloud.pins.close(
10853
+ reportId,
10854
+ status
10855
+ );
10856
+ syncReportsToRegistry();
10857
+ };
10858
+ }
10859
+ const pinLayer = createPinLayer({
10860
+ container: root,
10861
+ onOpenPinDetail: (componentId, reportId) => {
10862
+ const ref2 = parseComponentRef(componentId);
10863
+ setSelectedReportId(reportId);
10864
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10865
+ session.mode.transition.enterViewing(views.buildStack(entry));
10866
+ },
10867
+ onHoverPin: (anchor, componentId) => {
10868
+ const overlay = overlayRef.current;
10869
+ if (!overlay) return;
10870
+ if (!anchor || !componentId) {
10871
+ overlay.hide();
10872
+ return;
10873
+ }
10874
+ overlay.show(anchor, {
10875
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10876
+ });
10877
+ },
10878
+ onPinsChanged: syncReportsToRegistry
10879
+ });
10880
+ shell.menuBar.setPinLayer(pinLayer);
10881
+ pinLayerRef.current = pinLayer;
10882
+ mountCleanup.add(() => {
10883
+ pinLayer.destroy();
10884
+ pinLayerRef.current = null;
10885
+ registry.closeReport = void 0;
10886
+ });
10887
+ if (cloud) {
10888
+ const detach = pinLayer.attachCloud({
10889
+ cloud,
10890
+ channel,
10891
+ getRoute: getCurrentRoute,
10892
+ getPathname,
10893
+ getVisibleEntities,
10894
+ getMatchMode,
10895
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10896
+ });
10897
+ mountCleanup.add(detach);
10898
+ if (channel && typeof window !== "undefined") {
10899
+ const onRouteChange = () => {
10900
+ const route = getCurrentRoute();
10901
+ channel?.joinRoute(route);
10902
+ void pinLayer.refresh();
10903
+ };
10904
+ const detachRoute = bindRouteChange(onRouteChange);
10905
+ mountCleanup.add(detachRoute);
10906
+ }
10907
+ }
10908
+ }
11085
10909
  function mount(target) {
11086
10910
  if (mounted) return;
11087
10911
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11090,28 +10914,7 @@ function createUidex(options = {}) {
11090
10914
  }
11091
10915
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11092
10916
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11093
- const getVisibleEntities = () => {
11094
- if (typeof document === "undefined") return [];
11095
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11096
- const nodes = document.querySelectorAll(selector);
11097
- const ids = [];
11098
- const seen = /* @__PURE__ */ new Set();
11099
- for (const node of nodes) {
11100
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11101
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11102
- const id = node.getAttribute(attr);
11103
- if (!id) continue;
11104
- const key = `${kind}:${id}`;
11105
- if (!seen.has(key)) {
11106
- seen.add(key);
11107
- ids.push(key);
11108
- }
11109
- break;
11110
- }
11111
- }
11112
- return ids;
11113
- };
11114
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10917
+ const getMatchMode = () => readMode();
11115
10918
  let channel = null;
11116
10919
  if (cloud && options.user) {
11117
10920
  channel = cloud.realtime.connect({
@@ -11132,7 +10935,6 @@ function createUidex(options = {}) {
11132
10935
  onSelect: (match) => {
11133
10936
  const route = views.resolve(match.ref);
11134
10937
  if (!route) return;
11135
- session.select(match.ref);
11136
10938
  const entry = { id: route.view.id, ref: match.ref };
11137
10939
  session.mode.transition.enterViewing(views.buildStack(entry));
11138
10940
  }
@@ -11183,92 +10985,15 @@ function createUidex(options = {}) {
11183
10985
  });
11184
10986
  mountCleanup.add(viewStack);
11185
10987
  if (shadowRoot) {
11186
- keyBindings = bindShadowKeys({
10988
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10989
+ setupPinLayer(
11187
10990
  shadowRoot,
11188
- session,
11189
- getActionsPopup: () => viewStack.getActionsPopup(),
11190
- getPinLayer: () => pinLayerRef.current,
11191
- shortcut: options.shortcut
11192
- });
11193
- mountCleanup.add(keyBindings);
11194
- }
11195
- if (shadowRoot) {
11196
- const syncReportsToRegistry = () => {
11197
- const layer = pinLayerRef.current;
11198
- if (!layer) return;
11199
- const byEntity = /* @__PURE__ */ new Map();
11200
- for (const pin of layer.getAllPins()) {
11201
- const cid = pin.entity ?? "";
11202
- if (!cid) continue;
11203
- const list = byEntity.get(cid);
11204
- if (list) list.push(pin);
11205
- else byEntity.set(cid, [pin]);
11206
- }
11207
- for (const [cid, pins] of byEntity) {
11208
- const ref2 = parseComponentRef(cid);
11209
- registry.setReports(ref2.kind, ref2.id, pins);
11210
- }
11211
- };
11212
- if (cloud) {
11213
- registry.archiveReport = async (reportId, reason) => {
11214
- pinLayerRef.current?.removePin(reportId);
11215
- await cloud.pins.archive(
11216
- reportId,
11217
- reason
11218
- );
11219
- syncReportsToRegistry();
11220
- };
11221
- }
11222
- const pinLayer = createPinLayer({
11223
- container: shadowRoot,
11224
- currentBranch: options.git?.branch ?? null,
11225
- onOpenPinDetail: (componentId, reportId) => {
11226
- const ref2 = parseComponentRef(componentId);
11227
- setSelectedReportId(reportId);
11228
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11229
- session.mode.transition.enterViewing(views.buildStack(entry));
11230
- },
11231
- onHoverPin: (anchor, componentId) => {
11232
- const overlay = overlayRef.current;
11233
- if (!overlay) return;
11234
- if (!anchor || !componentId) {
11235
- overlay.hide();
11236
- return;
11237
- }
11238
- overlay.show(anchor, {
11239
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11240
- });
11241
- },
11242
- onPinsChanged: syncReportsToRegistry
11243
- });
11244
- shell.menuBar.setPinLayer(pinLayer);
11245
- pinLayerRef.current = pinLayer;
11246
- mountCleanup.add(() => {
11247
- pinLayer.destroy();
11248
- pinLayerRef.current = null;
11249
- registry.archiveReport = void 0;
11250
- });
11251
- if (cloud) {
11252
- const detach = pinLayer.attachCloud({
11253
- cloud,
11254
- channel,
11255
- getRoute: getCurrentRoute,
11256
- getPathname,
11257
- getVisibleEntities,
11258
- getMatchMode,
11259
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11260
- });
11261
- mountCleanup.add(detach);
11262
- if (channel && typeof window !== "undefined") {
11263
- const onRouteChange = () => {
11264
- const route = getCurrentRoute();
11265
- channel?.joinRoute(route);
11266
- void pinLayer.refresh();
11267
- };
11268
- const detachRoute = bindRouteChange(onRouteChange);
11269
- mountCleanup.add(detachRoute);
11270
- }
11271
- }
10991
+ shell,
10992
+ channel,
10993
+ getCurrentRoute,
10994
+ getMatchMode,
10995
+ getPathname
10996
+ );
11272
10997
  }
11273
10998
  if (ingest) {
11274
10999
  ingest.start();
@@ -11299,7 +11024,6 @@ function createUidex(options = {}) {
11299
11024
  0 && (module.exports = {
11300
11025
  ENTITY_KINDS,
11301
11026
  KIND_STYLE,
11302
- SURFACE_CONTAINER_CLASS,
11303
11027
  SURFACE_HOST_CLASS,
11304
11028
  SURFACE_IGNORE_SELECTOR,
11305
11029
  UnknownEntityKindError,
@@ -11314,7 +11038,6 @@ function createUidex(options = {}) {
11314
11038
  createCommandPaletteView,
11315
11039
  createConsoleCapture,
11316
11040
  createCursorTooltip,
11317
- createHighlightController,
11318
11041
  createIngest,
11319
11042
  createInspector,
11320
11043
  createMenuBar,
@@ -11331,13 +11054,11 @@ function createUidex(options = {}) {
11331
11054
  createThemeDetector,
11332
11055
  createUidex,
11333
11056
  createViewStack,
11334
- defaultResolveMatch,
11335
11057
  displayName,
11336
11058
  entityKey,
11337
11059
  featureDetailView,
11338
11060
  flowDetailView,
11339
11061
  formatShortcutLabel,
11340
- getNativeFetch,
11341
11062
  isMetaKind,
11342
11063
  nativeFetch,
11343
11064
  pageDetailView,