uidex 0.6.0 → 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 +1510 -1244
  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 +82 -255
  11. package/dist/headless/index.cjs.map +1 -1
  12. package/dist/headless/index.d.cts +5 -11
  13. package/dist/headless/index.d.ts +5 -11
  14. package/dist/headless/index.js +82 -257
  15. package/dist/headless/index.js.map +1 -1
  16. package/dist/index.cjs +721 -1053
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +149 -160
  19. package/dist/index.d.ts +149 -160
  20. package/dist/index.js +741 -1068
  21. package/dist/index.js.map +1 -1
  22. package/dist/react/index.cjs +729 -1000
  23. package/dist/react/index.cjs.map +1 -1
  24. package/dist/react/index.d.cts +99 -86
  25. package/dist/react/index.d.ts +99 -86
  26. package/dist/react/index.js +745 -1015
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/scan/index.cjs +1518 -1237
  29. package/dist/scan/index.cjs.map +1 -1
  30. package/dist/scan/index.d.cts +209 -12
  31. package/dist/scan/index.d.ts +209 -12
  32. package/dist/scan/index.js +1515 -1236
  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
@@ -135,13 +135,14 @@ function createRegistry() {
135
135
  };
136
136
  const getPatternsForKind = (kind) => {
137
137
  const cached = patternCache.get(kind);
138
- if (cached !== void 0)
139
- return cached;
138
+ if (cached !== void 0) return cached;
140
139
  const patterns = [];
141
140
  for (const [key, entity] of store[kind]) {
142
- if (key.endsWith("*")) {
141
+ if (key.includes("*")) {
142
+ const segments = key.split("*");
143
143
  patterns.push({
144
- prefix: key.slice(0, -1),
144
+ segments,
145
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
145
146
  entity
146
147
  });
147
148
  }
@@ -152,13 +153,25 @@ function createRegistry() {
152
153
  );
153
154
  return patterns;
154
155
  };
156
+ const matchesSegments = (segments, id) => {
157
+ const first = segments[0];
158
+ const last = segments[segments.length - 1];
159
+ if (!id.startsWith(first)) return false;
160
+ let pos = first.length;
161
+ for (let i = 1; i < segments.length - 1; i++) {
162
+ const idx = id.indexOf(segments[i], pos);
163
+ if (idx === -1) return false;
164
+ pos = idx + segments[i].length;
165
+ }
166
+ return id.endsWith(last) && id.length - last.length >= pos;
167
+ };
155
168
  const matchPattern = (kind, id) => {
156
169
  assertEntityKind(kind);
157
170
  const patterns = getPatternsForKind(kind);
158
171
  if (patterns.length === 0) return void 0;
159
172
  let best;
160
173
  for (const entry of patterns) {
161
- if (id.startsWith(entry.prefix) && (best === void 0 || entry.prefix.length > best.prefix.length)) {
174
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
162
175
  best = entry;
163
176
  }
164
177
  }
@@ -460,7 +473,7 @@ function createNetworkCapture(options = {}) {
460
473
 
461
474
  // src/browser/ingest/index.ts
462
475
  function createIngest(options = {}) {
463
- const { session, ...opts } = options;
476
+ const opts = options;
464
477
  const wantConsole = opts.captureConsole !== false;
465
478
  const wantNetwork = opts.captureNetwork !== false;
466
479
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -479,14 +492,12 @@ function createIngest(options = {}) {
479
492
  consoleCapture?.start();
480
493
  networkCapture?.start();
481
494
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
482
- if (active) session?.setIngest(true);
483
495
  }
484
496
  function stop() {
485
497
  if (!active) return;
486
498
  consoleCapture?.stop();
487
499
  networkCapture?.stop();
488
500
  active = false;
489
- session?.setIngest(false);
490
501
  }
491
502
  return {
492
503
  start,
@@ -608,8 +619,7 @@ var COMMAND_PALETTE_ENTRY = {
608
619
  function createModeStore(options) {
609
620
  const { nav, bindings } = options;
610
621
  const store = (0, import_vanilla.createStore)(() => ({
611
- mode: "idle",
612
- inspectorActive: false
622
+ mode: "idle"
613
623
  }));
614
624
  const transition = {
615
625
  openPalette() {
@@ -618,17 +628,17 @@ function createModeStore(options) {
618
628
  bindings?.destroyInspector?.();
619
629
  }
620
630
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
621
- store.setState({ mode: "palette", inspectorActive: false });
631
+ store.setState({ mode: "palette" });
622
632
  },
623
633
  openInspector() {
624
634
  bindings?.mountInspector?.();
625
635
  nav.nav.clear();
626
- store.setState({ mode: "inspecting", inspectorActive: true });
636
+ store.setState({ mode: "inspecting" });
627
637
  },
628
638
  closeInspector() {
629
639
  bindings?.destroyInspector?.();
630
640
  nav.nav.clear();
631
- store.setState({ mode: "idle", inspectorActive: false });
641
+ store.setState({ mode: "idle" });
632
642
  },
633
643
  toggleInspector() {
634
644
  if (store.getState().mode === "inspecting") {
@@ -643,7 +653,7 @@ function createModeStore(options) {
643
653
  bindings?.destroyInspector?.();
644
654
  }
645
655
  nav.nav.reset(initialStack);
646
- store.setState({ mode: "viewing", inspectorActive: false });
656
+ store.setState({ mode: "viewing" });
647
657
  },
648
658
  dismiss() {
649
659
  const prev = store.getState();
@@ -651,7 +661,7 @@ function createModeStore(options) {
651
661
  bindings?.destroyInspector?.();
652
662
  }
653
663
  nav.nav.clear();
654
- store.setState({ mode: "idle", inspectorActive: false });
664
+ store.setState({ mode: "idle" });
655
665
  },
656
666
  popOrTransition() {
657
667
  const { stack } = nav.getState();
@@ -659,12 +669,12 @@ function createModeStore(options) {
659
669
  nav.nav.pop();
660
670
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
661
671
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
662
- store.setState({ mode: "palette", inspectorActive: false });
672
+ store.setState({ mode: "palette" });
663
673
  } else if (stack.length === 2) {
664
674
  nav.nav.pop();
665
675
  } else {
666
676
  nav.nav.clear();
667
- store.setState({ mode: "idle", inspectorActive: false });
677
+ store.setState({ mode: "idle" });
668
678
  }
669
679
  },
670
680
  pushView(entry) {
@@ -681,7 +691,7 @@ function createModeStore(options) {
681
691
  case "inspecting":
682
692
  bindings?.destroyInspector?.();
683
693
  nav.nav.reset([entry]);
684
- store.setState({ mode: "viewing", inspectorActive: false });
694
+ store.setState({ mode: "viewing" });
685
695
  break;
686
696
  case "palette":
687
697
  case "viewing":
@@ -716,14 +726,6 @@ function createNavigationStore() {
716
726
  store.setState({ stack: s.slice(0, -1) });
717
727
  }
718
728
  },
719
- replace(entry) {
720
- const s = store.getState().stack;
721
- if (s.length === 0) {
722
- store.setState({ stack: [entry] });
723
- } else {
724
- store.setState({ stack: [...s.slice(0, -1), entry] });
725
- }
726
- },
727
729
  clear() {
728
730
  store.setState({ stack: [] });
729
731
  },
@@ -738,14 +740,11 @@ function createNavigationStore() {
738
740
 
739
741
  // src/browser/session/store.ts
740
742
  var defaultSnapshot = {
741
- hover: null,
742
- selection: null,
743
743
  stack: [],
744
744
  pinnedHighlight: null,
745
- inspectorActive: false,
745
+ mode: "idle",
746
746
  theme: "auto",
747
747
  resolvedTheme: "light",
748
- ingestActive: false,
749
748
  user: null
750
749
  };
751
750
  function resolveTheme(preference, detect) {
@@ -796,7 +795,6 @@ function createSession(options = {}) {
796
795
  } else if (highlightMode === "transient") {
797
796
  onUpdateOverlay?.(hlCtx);
798
797
  }
799
- store.setState({ hover: ref2 });
800
798
  },
801
799
  unhover() {
802
800
  if (highlightMode === "transient") {
@@ -806,7 +804,6 @@ function createSession(options = {}) {
806
804
  hlCtx.color = null;
807
805
  onHideOverlay?.();
808
806
  }
809
- store.setState({ hover: null });
810
807
  },
811
808
  pin(ref2) {
812
809
  const pinRef = ref2 ?? hlCtx.ref;
@@ -834,14 +831,11 @@ function createSession(options = {}) {
834
831
  };
835
832
  const store = (0, import_vanilla3.createStore)(() => ({
836
833
  ...defaultSnapshot,
837
- hover: overrides.hover ?? null,
838
- selection: overrides.selection ?? null,
839
834
  stack: [],
840
835
  pinnedHighlight: null,
841
- inspectorActive: false,
836
+ mode: "idle",
842
837
  theme: initialPref,
843
838
  resolvedTheme: initialResolved,
844
- ingestActive: overrides.ingestActive ?? false,
845
839
  user: overrides.user ?? null
846
840
  }));
847
841
  nav.subscribe(() => {
@@ -851,29 +845,21 @@ function createSession(options = {}) {
851
845
  }
852
846
  });
853
847
  modeStore.subscribe(() => {
854
- const { inspectorActive } = modeStore.getState();
855
- if (store.getState().inspectorActive !== inspectorActive) {
856
- store.setState({ inspectorActive });
848
+ const { mode } = modeStore.getState();
849
+ if (store.getState().mode !== mode) {
850
+ store.setState({ mode });
857
851
  }
858
852
  });
859
853
  const session = store;
860
854
  session.nav = nav;
861
855
  session.mode = modeStore;
862
856
  session.highlight = highlightActions;
863
- session.select = (ref2) => {
864
- if (sameRef(store.getState().selection, ref2)) return;
865
- store.setState({ selection: ref2 });
866
- };
867
857
  session.setTheme = (theme, resolved) => {
868
858
  const state = store.getState();
869
859
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
870
860
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
871
861
  store.setState({ theme, resolvedTheme: nextResolved });
872
862
  };
873
- session.setIngest = (active) => {
874
- if (store.getState().ingestActive === active) return;
875
- store.setState({ ingestActive: active });
876
- };
877
863
  if (initialStack.length > 0) {
878
864
  modeStore.transition.openPalette();
879
865
  for (let i = 1; i < initialStack.length; i++) {
@@ -1186,9 +1172,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1186
1172
  .right-0 {
1187
1173
  right: calc(var(--spacing) * 0);
1188
1174
  }
1189
- .right-2 {
1190
- right: calc(var(--spacing) * 2);
1191
- }
1192
1175
  .bottom-full {
1193
1176
  bottom: 100%;
1194
1177
  }
@@ -1231,9 +1214,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1231
1214
  .mx-2 {
1232
1215
  margin-inline: calc(var(--spacing) * 2);
1233
1216
  }
1234
- .my-1 {
1235
- margin-block: calc(var(--spacing) * 1);
1236
- }
1237
1217
  .ms-auto {
1238
1218
  margin-inline-start: auto;
1239
1219
  }
@@ -1270,9 +1250,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1270
1250
  .inline-flex {
1271
1251
  display: inline-flex;
1272
1252
  }
1273
- .list-item {
1274
- display: list-item;
1275
- }
1276
1253
  .table {
1277
1254
  display: table;
1278
1255
  }
@@ -1280,10 +1257,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1280
1257
  width: calc(var(--spacing) * 2);
1281
1258
  height: calc(var(--spacing) * 2);
1282
1259
  }
1283
- .size-3 {
1284
- width: calc(var(--spacing) * 3);
1285
- height: calc(var(--spacing) * 3);
1286
- }
1287
1260
  .size-3\\.5 {
1288
1261
  width: calc(var(--spacing) * 3.5);
1289
1262
  height: calc(var(--spacing) * 3.5);
@@ -1341,15 +1314,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1341
1314
  .h-\\[26rem\\] {
1342
1315
  height: 26rem;
1343
1316
  }
1344
- .h-auto {
1345
- height: auto;
1346
- }
1347
1317
  .h-full {
1348
1318
  height: 100%;
1349
1319
  }
1350
- .h-px {
1351
- height: 1px;
1352
- }
1353
1320
  .max-h-32 {
1354
1321
  max-height: calc(var(--spacing) * 32);
1355
1322
  }
@@ -1359,9 +1326,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1359
1326
  .min-h-0 {
1360
1327
  min-height: calc(var(--spacing) * 0);
1361
1328
  }
1362
- .min-h-7 {
1363
- min-height: calc(var(--spacing) * 7);
1364
- }
1365
1329
  .min-h-8 {
1366
1330
  min-height: calc(var(--spacing) * 8);
1367
1331
  }
@@ -1386,9 +1350,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1386
1350
  .w-56 {
1387
1351
  width: calc(var(--spacing) * 56);
1388
1352
  }
1389
- .w-auto {
1390
- width: auto;
1391
- }
1392
1353
  .w-full {
1393
1354
  width: 100%;
1394
1355
  }
@@ -1463,9 +1424,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1463
1424
  .animate-spin {
1464
1425
  animation: var(--animate-spin);
1465
1426
  }
1466
- .cursor-default {
1467
- cursor: default;
1468
- }
1469
1427
  .cursor-pointer {
1470
1428
  cursor: pointer;
1471
1429
  }
@@ -1475,9 +1433,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1475
1433
  .scroll-py-2 {
1476
1434
  scroll-padding-block: calc(var(--spacing) * 2);
1477
1435
  }
1478
- .list-none {
1479
- list-style-type: none;
1480
- }
1481
1436
  .appearance-none {
1482
1437
  appearance: none;
1483
1438
  }
@@ -1499,9 +1454,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1499
1454
  .justify-center {
1500
1455
  justify-content: center;
1501
1456
  }
1502
- .justify-start {
1503
- justify-content: flex-start;
1504
- }
1505
1457
  .gap-0 {
1506
1458
  gap: calc(var(--spacing) * 0);
1507
1459
  }
@@ -1526,9 +1478,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1526
1478
  .gap-6 {
1527
1479
  gap: calc(var(--spacing) * 6);
1528
1480
  }
1529
- .self-start {
1530
- align-self: flex-start;
1531
- }
1532
1481
  .truncate {
1533
1482
  overflow: hidden;
1534
1483
  text-overflow: ellipsis;
@@ -1743,9 +1692,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1743
1692
  .p-6 {
1744
1693
  padding: calc(var(--spacing) * 6);
1745
1694
  }
1746
- .px-0 {
1747
- padding-inline: calc(var(--spacing) * 0);
1748
- }
1749
1695
  .px-1 {
1750
1696
  padding-inline: calc(var(--spacing) * 1);
1751
1697
  }
@@ -1770,9 +1716,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1770
1716
  .px-6 {
1771
1717
  padding-inline: calc(var(--spacing) * 6);
1772
1718
  }
1773
- .py-0 {
1774
- padding-block: calc(var(--spacing) * 0);
1775
- }
1776
1719
  .py-1 {
1777
1720
  padding-block: calc(var(--spacing) * 1);
1778
1721
  }
@@ -1845,9 +1788,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1845
1788
  .text-\\[9px\\] {
1846
1789
  font-size: 9px;
1847
1790
  }
1848
- .text-\\[10px\\] {
1849
- font-size: 10px;
1850
- }
1851
1791
  .text-\\[11px\\] {
1852
1792
  font-size: 11px;
1853
1793
  }
@@ -2087,10 +2027,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2087
2027
  outline-style: var(--tw-outline-style);
2088
2028
  outline-width: 1px;
2089
2029
  }
2090
- .blur {
2091
- --tw-blur: blur(8px);
2092
- 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,);
2093
- }
2094
2030
  .filter {
2095
2031
  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,);
2096
2032
  }
@@ -2256,25 +2192,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2256
2192
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2257
2193
  }
2258
2194
  }
2259
- .focus-within\\:border-ring {
2260
- &:focus-within {
2261
- border-color: var(--ring);
2262
- }
2263
- }
2264
- .focus-within\\:ring-\\[3px\\] {
2265
- &:focus-within {
2266
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2267
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2268
- }
2269
- }
2270
- .focus-within\\:ring-ring\\/30 {
2271
- &:focus-within {
2272
- --tw-ring-color: var(--ring);
2273
- @supports (color: color-mix(in lab, red, red)) {
2274
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2275
- }
2276
- }
2277
- }
2278
2195
  .hover\\:border-destructive\\/30 {
2279
2196
  &:hover {
2280
2197
  @media (hover: hover) {
@@ -2418,11 +2335,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2418
2335
  pointer-events: none;
2419
2336
  }
2420
2337
  }
2421
- .disabled\\:cursor-not-allowed {
2422
- &:disabled {
2423
- cursor: not-allowed;
2424
- }
2425
- }
2426
2338
  .disabled\\:opacity-50 {
2427
2339
  &:disabled {
2428
2340
  opacity: 50%;
@@ -2433,11 +2345,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2433
2345
  opacity: 60%;
2434
2346
  }
2435
2347
  }
2436
- .has-disabled\\:opacity-60 {
2437
- &:has(*:disabled) {
2438
- opacity: 60%;
2439
- }
2440
- }
2441
2348
  .aria-invalid\\:border-destructive\\/40 {
2442
2349
  &[aria-invalid="true"] {
2443
2350
  border-color: var(--destructive);
@@ -2844,32 +2751,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2844
2751
  height: calc(var(--spacing) * 4.5);
2845
2752
  }
2846
2753
  }
2847
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2848
- &>svg {
2849
- pointer-events: none;
2850
- }
2851
- }
2852
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2853
- &>svg {
2854
- margin-inline: calc(var(--spacing) * -0.5);
2855
- }
2856
- }
2857
- .\\[\\&\\>svg\\]\\:shrink-0 {
2858
- &>svg {
2859
- flex-shrink: 0;
2860
- }
2861
- }
2862
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2863
- &>svg:not([class*='opacity-']) {
2864
- opacity: 80%;
2865
- }
2866
- }
2867
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2868
- &>svg:not([class*='size-']) {
2869
- width: calc(var(--spacing) * 4);
2870
- height: calc(var(--spacing) * 4);
2871
- }
2872
- }
2873
2754
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2874
2755
  [data-kbd-nav] & {
2875
2756
  &:focus-within {
@@ -3431,18 +3312,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3431
3312
 
3432
3313
  // src/browser/surface/constants.ts
3433
3314
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3434
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3435
3315
  var Z_BASE = 2147483630;
3436
3316
  var Z_OVERLAY = 2147483635;
3437
3317
  var Z_PIN_LAYER = 2147483640;
3438
3318
  var Z_CHROME = 2147483645;
3439
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3319
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3440
3320
  var UIDEX_ATTR_TO_KIND = [
3441
3321
  ["data-uidex", "element"],
3442
3322
  ["data-uidex-region", "region"],
3443
3323
  ["data-uidex-widget", "widget"],
3444
3324
  ["data-uidex-primitive", "primitive"]
3445
3325
  ];
3326
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3327
+ "element",
3328
+ "region",
3329
+ "widget",
3330
+ "primitive"
3331
+ ]);
3446
3332
 
3447
3333
  // src/browser/surface/host.ts
3448
3334
  function createSurfaceHost(options) {
@@ -3732,7 +3618,6 @@ function createInspector(options) {
3732
3618
  e.preventDefault();
3733
3619
  e.stopPropagation();
3734
3620
  const match = stack[layerIndex];
3735
- session.select(match.ref);
3736
3621
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3737
3622
  };
3738
3623
  const onContextMenu = (e) => {
@@ -4051,49 +3936,12 @@ function createMenuBar(options) {
4051
3936
  },
4052
3937
  pinIcon
4053
3938
  );
4054
- const commitCycler = el("div", {
4055
- class: "relative z-1 inline-flex items-center gap-0.5",
4056
- attrs: { "data-uidex-menubar-commit-cycler": "" }
4057
- });
4058
- commitCycler.hidden = true;
4059
- const prevIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronLeft);
4060
- prevIcon.setAttribute("class", "size-3");
4061
- prevIcon.setAttribute("aria-hidden", "true");
4062
- const prevBtn = el(
4063
- "button",
4064
- {
4065
- class: BUTTON_CLASS,
4066
- attrs: { type: "button", "aria-label": "Previous commit" },
4067
- style: { width: "18px", height: "18px" }
4068
- },
4069
- prevIcon
4070
- );
4071
- const commitLabel = el("span", {
4072
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4073
- attrs: { "data-uidex-menubar-commit-label": "" }
4074
- });
4075
- const nextIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronRight);
4076
- nextIcon.setAttribute("class", "size-3");
4077
- nextIcon.setAttribute("aria-hidden", "true");
4078
- const nextBtn = el(
4079
- "button",
4080
- {
4081
- class: BUTTON_CLASS,
4082
- attrs: { type: "button", "aria-label": "Next commit" },
4083
- style: { width: "18px", height: "18px" }
4084
- },
4085
- nextIcon
4086
- );
4087
- commitCycler.appendChild(prevBtn);
4088
- commitCycler.appendChild(commitLabel);
4089
- commitCycler.appendChild(nextBtn);
4090
3939
  const pinWrapper = el("div", {
4091
3940
  class: "relative z-1 inline-flex items-center gap-0.5",
4092
3941
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4093
3942
  });
4094
3943
  pinWrapper.hidden = true;
4095
3944
  pinWrapper.appendChild(pinBtn);
4096
- pinWrapper.appendChild(commitCycler);
4097
3945
  root.appendChild(pinWrapper);
4098
3946
  const updatePinUI = () => {
4099
3947
  if (!activePinLayer) {
@@ -4101,16 +3949,7 @@ function createMenuBar(options) {
4101
3949
  return;
4102
3950
  }
4103
3951
  const pinsVisible = activePinLayer.visible;
4104
- const state = activePinLayer.filterState;
4105
- const hasCommits = state.commits.length > 0;
4106
3952
  pinWrapper.hidden = false;
4107
- commitCycler.hidden = !pinsVisible || !hasCommits;
4108
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4109
- commitLabel.textContent = `all (${state.commits.length})`;
4110
- } else {
4111
- const sha = state.commits[state.commitIndex] ?? "";
4112
- commitLabel.textContent = sha.slice(0, 7);
4113
- }
4114
3953
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4115
3954
  };
4116
3955
  pinBtn.addEventListener("click", (e) => {
@@ -4119,14 +3958,6 @@ function createMenuBar(options) {
4119
3958
  activePinLayer.setVisible(!activePinLayer.visible);
4120
3959
  }
4121
3960
  });
4122
- prevBtn.addEventListener("click", (e) => {
4123
- e.stopPropagation();
4124
- activePinLayer?.prevCommit();
4125
- });
4126
- nextBtn.addEventListener("click", (e) => {
4127
- e.stopPropagation();
4128
- activePinLayer?.nextCommit();
4129
- });
4130
3961
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4131
3962
  const presenceIcon = (0, import_lucide3.createElement)(import_lucide3.Users);
4132
3963
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4220,7 +4051,7 @@ function createMenuBar(options) {
4220
4051
  container.appendChild(root);
4221
4052
  const syncButtonStates = () => {
4222
4053
  const state = session.getState();
4223
- const inspectActive = state.inspectorActive;
4054
+ const inspectActive = state.mode === "inspecting";
4224
4055
  inspectBtn.setAttribute(
4225
4056
  "data-uidex-menubar-inspect-active",
4226
4057
  inspectActive ? "true" : "false"
@@ -4339,6 +4170,49 @@ function createMenuBar(options) {
4339
4170
  };
4340
4171
  }
4341
4172
 
4173
+ // src/browser/internal/repositioner.ts
4174
+ function createRepositioner(onReflow) {
4175
+ let rafId = null;
4176
+ let attached = false;
4177
+ const schedule = () => {
4178
+ if (rafId !== null) return;
4179
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4180
+ rafId = null;
4181
+ onReflow();
4182
+ }) : setTimeout(() => {
4183
+ rafId = null;
4184
+ onReflow();
4185
+ }, 0);
4186
+ };
4187
+ const cancel = () => {
4188
+ if (rafId === null) return;
4189
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4190
+ else clearTimeout(rafId);
4191
+ rafId = null;
4192
+ };
4193
+ const onScroll = () => schedule();
4194
+ const onResize = () => schedule();
4195
+ const attach = () => {
4196
+ if (attached) return;
4197
+ attached = true;
4198
+ window.addEventListener("resize", onResize);
4199
+ window.addEventListener("scroll", onScroll, {
4200
+ capture: true,
4201
+ passive: true
4202
+ });
4203
+ };
4204
+ const detach = () => {
4205
+ if (!attached) return;
4206
+ attached = false;
4207
+ window.removeEventListener("resize", onResize);
4208
+ window.removeEventListener("scroll", onScroll, {
4209
+ capture: true
4210
+ });
4211
+ cancel();
4212
+ };
4213
+ return { schedule, cancel, attach, detach };
4214
+ }
4215
+
4342
4216
  // src/browser/surface/overlay.ts
4343
4217
  var DEFAULT_COLOR = "#34d399";
4344
4218
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4406,44 +4280,7 @@ function createOverlay(deps) {
4406
4280
  fillOpacity: DEFAULT_FILL_OPACITY,
4407
4281
  backdrop: false
4408
4282
  };
4409
- let rafId = null;
4410
- let attached = false;
4411
- const schedule = () => {
4412
- if (rafId !== null) return;
4413
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4414
- rafId = null;
4415
- updatePosition();
4416
- }) : setTimeout(() => {
4417
- rafId = null;
4418
- updatePosition();
4419
- }, 0);
4420
- };
4421
- const cancelSchedule = () => {
4422
- if (rafId === null) return;
4423
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4424
- else clearTimeout(rafId);
4425
- rafId = null;
4426
- };
4427
- const onScroll = () => schedule();
4428
- const onResize = () => schedule();
4429
- const attach = () => {
4430
- if (attached) return;
4431
- attached = true;
4432
- window.addEventListener("resize", onResize);
4433
- window.addEventListener("scroll", onScroll, {
4434
- capture: true,
4435
- passive: true
4436
- });
4437
- };
4438
- const detach = () => {
4439
- if (!attached) return;
4440
- attached = false;
4441
- window.removeEventListener("resize", onResize);
4442
- window.removeEventListener("scroll", onScroll, {
4443
- capture: true
4444
- });
4445
- cancelSchedule();
4446
- };
4283
+ const repositioner = createRepositioner(() => updatePosition());
4447
4284
  function updatePosition() {
4448
4285
  if (!target) return;
4449
4286
  const rect = target.getBoundingClientRect();
@@ -4507,16 +4344,16 @@ function createOverlay(deps) {
4507
4344
  box.offsetHeight;
4508
4345
  }
4509
4346
  box.style.opacity = "1";
4510
- attach();
4347
+ repositioner.attach();
4511
4348
  },
4512
4349
  hide() {
4513
4350
  target = null;
4514
4351
  box.style.opacity = "0";
4515
4352
  backdrop.style.opacity = "0";
4516
- detach();
4353
+ repositioner.detach();
4517
4354
  },
4518
4355
  destroy() {
4519
- detach();
4356
+ repositioner.detach();
4520
4357
  box.remove();
4521
4358
  backdrop.remove();
4522
4359
  target = null;
@@ -4633,8 +4470,7 @@ function createSurfaceShell(options) {
4633
4470
  const overlay = createOverlay({ container: host.shadowRoot });
4634
4471
  cleanup.add(overlay);
4635
4472
  const tooltip = createCursorTooltip({
4636
- container: host.chromeEl,
4637
- session: options.session
4473
+ container: host.chromeEl
4638
4474
  });
4639
4475
  cleanup.add(tooltip);
4640
4476
  const afterHover = options.inspector?.onAfterHover;
@@ -4818,9 +4654,6 @@ function createPinLayer(options) {
4818
4654
  const seenIds = /* @__PURE__ */ new Set();
4819
4655
  const byComp = /* @__PURE__ */ new Map();
4820
4656
  const indicators = /* @__PURE__ */ new Map();
4821
- let filter = { branch: null, commit: null };
4822
- let commits = [];
4823
- let commitIndex = -1;
4824
4657
  const filterCbs = /* @__PURE__ */ new Set();
4825
4658
  const notifyFilter = () => {
4826
4659
  for (const cb of filterCbs) cb();
@@ -4835,8 +4668,6 @@ function createPinLayer(options) {
4835
4668
  }
4836
4669
  };
4837
4670
  const rerender = () => {
4838
- commits = [];
4839
- commitIndex = -1;
4840
4671
  rebuildFiltered();
4841
4672
  for (const id of Array.from(indicators.keys())) {
4842
4673
  if (!byComp.has(id)) removeIndicator(id);
@@ -4845,45 +4676,8 @@ function createPinLayer(options) {
4845
4676
  notifyFilter();
4846
4677
  onPinsChanged?.();
4847
4678
  };
4848
- let rafId = null;
4849
- let winAttached = false;
4850
4679
  let obs = null;
4851
- const schedulePos = () => {
4852
- if (rafId !== null) return;
4853
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4854
- rafId = null;
4855
- posAll();
4856
- }) : setTimeout(() => {
4857
- rafId = null;
4858
- posAll();
4859
- }, 0);
4860
- };
4861
- const cancelPos = () => {
4862
- if (rafId === null) return;
4863
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4864
- else clearTimeout(rafId);
4865
- rafId = null;
4866
- };
4867
- const onScroll = () => schedulePos();
4868
- const onResize = () => schedulePos();
4869
- const attachWin = () => {
4870
- if (winAttached) return;
4871
- winAttached = true;
4872
- window.addEventListener("scroll", onScroll, {
4873
- capture: true,
4874
- passive: true
4875
- });
4876
- window.addEventListener("resize", onResize);
4877
- };
4878
- const detachWin = () => {
4879
- if (!winAttached) return;
4880
- winAttached = false;
4881
- window.removeEventListener("scroll", onScroll, {
4882
- capture: true
4883
- });
4884
- window.removeEventListener("resize", onResize);
4885
- cancelPos();
4886
- };
4680
+ const repositioner = createRepositioner(() => posAll());
4887
4681
  const attachObs = () => {
4888
4682
  if (obs || typeof MutationObserver === "undefined") return;
4889
4683
  obs = new MutationObserver((recs) => {
@@ -4907,7 +4701,7 @@ function createPinLayer(options) {
4907
4701
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4908
4702
  changed = true;
4909
4703
  }
4910
- if (changed) schedulePos();
4704
+ if (changed) repositioner.schedule();
4911
4705
  };
4912
4706
  const expand = (st) => {
4913
4707
  if (st.expanded) return;
@@ -5149,7 +4943,7 @@ function createPinLayer(options) {
5149
4943
  if (!st) {
5150
4944
  st = buildIndicator(componentId);
5151
4945
  indicators.set(componentId, st);
5152
- attachWin();
4946
+ repositioner.attach();
5153
4947
  attachObs();
5154
4948
  }
5155
4949
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5163,7 +4957,7 @@ function createPinLayer(options) {
5163
4957
  st.wrap.remove();
5164
4958
  indicators.delete(componentId);
5165
4959
  if (indicators.size === 0) {
5166
- detachWin();
4960
+ repositioner.detach();
5167
4961
  detachObs();
5168
4962
  }
5169
4963
  };
@@ -5198,17 +4992,22 @@ function createPinLayer(options) {
5198
4992
  seenIds.clear();
5199
4993
  byComp.clear();
5200
4994
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5201
- commits = [];
5202
- commitIndex = -1;
5203
4995
  notifyFilter();
5204
4996
  },
5205
4997
  getPinsForElement: (id) => byComp.get(id) ?? [],
5206
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5207
4998
  getAllPins: () => allPins.slice(),
5208
4999
  attachChannel(channel) {
5209
- return channel.onPin((pin) => {
5000
+ const offPin = channel.onPin((pin) => {
5210
5001
  layer.addPin(pin);
5211
5002
  });
5003
+ const offArchived = channel.onPinArchived?.((reportId) => {
5004
+ layer.removePin(reportId);
5005
+ }) ?? (() => {
5006
+ });
5007
+ return () => {
5008
+ offPin();
5009
+ offArchived();
5010
+ };
5212
5011
  },
5213
5012
  attachCloud(opts) {
5214
5013
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5239,39 +5038,6 @@ function createPinLayer(options) {
5239
5038
  async refresh() {
5240
5039
  if (activeRefresh) await activeRefresh();
5241
5040
  },
5242
- get filterState() {
5243
- return {
5244
- branch: filter.branch,
5245
- commit: filter.commit,
5246
- commits,
5247
- commitIndex
5248
- };
5249
- },
5250
- setFilter(next) {
5251
- filter = {
5252
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5253
- commit: next.commit !== void 0 ? next.commit : filter.commit
5254
- };
5255
- rerender();
5256
- },
5257
- nextCommit() {
5258
- if (!commits.length) return;
5259
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5260
- filter = {
5261
- ...filter,
5262
- commit: commitIndex === -1 ? null : commits[commitIndex]
5263
- };
5264
- rerender();
5265
- },
5266
- prevCommit() {
5267
- if (!commits.length) return;
5268
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5269
- filter = {
5270
- ...filter,
5271
- commit: commitIndex === -1 ? null : commits[commitIndex]
5272
- };
5273
- rerender();
5274
- },
5275
5041
  onFilterChange(cb) {
5276
5042
  filterCbs.add(cb);
5277
5043
  return () => {
@@ -5290,7 +5056,7 @@ function createPinLayer(options) {
5290
5056
  },
5291
5057
  destroy() {
5292
5058
  layer.clear();
5293
- detachWin();
5059
+ repositioner.detach();
5294
5060
  detachObs();
5295
5061
  layerEl.remove();
5296
5062
  activeRefresh = null;
@@ -5333,9 +5099,11 @@ function createRouter(options) {
5333
5099
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5334
5100
  throw new ViewValidationError("View must have a non-empty string id");
5335
5101
  }
5336
- if (typeof view.surface !== "function") {
5102
+ const hasSurface = typeof view.surface === "function";
5103
+ const hasRender = typeof view.render === "function";
5104
+ if (!hasSurface && !hasRender) {
5337
5105
  throw new ViewValidationError(
5338
- `View ${view.id}: 'surface' must be a function`
5106
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5339
5107
  );
5340
5108
  }
5341
5109
  if (!view.matches && !view.palette) {
@@ -5393,7 +5161,6 @@ function createRouter(options) {
5393
5161
  if (idx >= 0) recentRefs.splice(idx, 1);
5394
5162
  recentRefs.unshift(ref2);
5395
5163
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5396
- options.session.select(ref2);
5397
5164
  const entry = { id: match.view.id, ref: ref2 };
5398
5165
  const { mode } = options.session.mode.getState();
5399
5166
  if (mode === "idle" || mode === "inspecting") {
@@ -5448,57 +5215,7 @@ function detectDev() {
5448
5215
 
5449
5216
  // src/browser/internal/lit.ts
5450
5217
  var import_lit_html2 = require("lit-html");
5451
- var import_repeat = require("lit-html/directives/repeat.js");
5452
5218
  var import_ref = require("lit-html/directives/ref.js");
5453
- var import_class_map = require("lit-html/directives/class-map.js");
5454
-
5455
- // src/browser/internal/apply-props.ts
5456
- function applyProps(node, props) {
5457
- const removers = [];
5458
- for (const [key, rawValue] of Object.entries(props)) {
5459
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5460
- if (key.startsWith("on") && typeof rawValue === "function") {
5461
- const eventName = key.slice(2).toLowerCase();
5462
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5463
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5464
- const listener = rawValue;
5465
- node.addEventListener(effectiveEvent, listener);
5466
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5467
- continue;
5468
- }
5469
- if (rawValue === void 0 || rawValue === null) {
5470
- node.removeAttribute(key);
5471
- continue;
5472
- }
5473
- if (typeof rawValue === "boolean") {
5474
- if (rawValue) node.setAttribute(key, "");
5475
- else node.removeAttribute(key);
5476
- continue;
5477
- }
5478
- if (key === "style" && typeof rawValue === "object") {
5479
- Object.assign(
5480
- node.style,
5481
- rawValue
5482
- );
5483
- continue;
5484
- }
5485
- if (key === "className" || key === "class") {
5486
- node.setAttribute("class", String(rawValue));
5487
- continue;
5488
- }
5489
- if (key === "htmlFor") {
5490
- node.setAttribute("for", String(rawValue));
5491
- continue;
5492
- }
5493
- if (key === "tabIndex") {
5494
- ;
5495
- node.tabIndex = Number(rawValue);
5496
- continue;
5497
- }
5498
- node.setAttribute(key, String(rawValue));
5499
- }
5500
- return () => removers.forEach((fn) => fn());
5501
- }
5502
5219
 
5503
5220
  // src/browser/internal/lit-icon.ts
5504
5221
  var import_lit_html = require("lit-html");
@@ -5532,13 +5249,23 @@ var LucideIconDirective = class extends import_directive.Directive {
5532
5249
  };
5533
5250
  var icon = (0, import_directive.directive)(LucideIconDirective);
5534
5251
 
5535
- // src/browser/ui/cva.ts
5536
- function cva(base, config = {}) {
5537
- return (props = {}) => {
5538
- const out = [base];
5539
- const variants = config.variants;
5540
- if (!variants) return out;
5541
- for (const key of Object.keys(variants)) {
5252
+ // src/browser/views/primitives/chip.ts
5253
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5254
+ function createChip(options = {}, children = []) {
5255
+ const { class: extra, ...rest } = options;
5256
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5257
+ }
5258
+
5259
+ // src/browser/views/primitives/kind-icon.ts
5260
+ var import_lucide6 = require("lucide");
5261
+
5262
+ // src/browser/ui/cva.ts
5263
+ function cva(base, config = {}) {
5264
+ return (props = {}) => {
5265
+ const out = [base];
5266
+ const variants = config.variants;
5267
+ if (!variants) return out;
5268
+ for (const key of Object.keys(variants)) {
5542
5269
  const picked = props[key] ?? config.defaultVariants?.[key];
5543
5270
  if (picked === void 0) continue;
5544
5271
  const cls = variants[key][picked];
@@ -5579,16 +5306,6 @@ function badgeTpl(content, options = {}) {
5579
5306
  `;
5580
5307
  }
5581
5308
 
5582
- // src/browser/views/primitives/chip.ts
5583
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5584
- function createChip(options = {}, children = []) {
5585
- const { class: extra, ...rest } = options;
5586
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5587
- }
5588
-
5589
- // src/browser/views/primitives/kind-icon.ts
5590
- var import_lucide6 = require("lucide");
5591
-
5592
5309
  // src/browser/views/primitives/icon-tile.ts
5593
5310
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5594
5311
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6080,6 +5797,54 @@ function createScrollArea(props = {}) {
6080
5797
  );
6081
5798
  }
6082
5799
 
5800
+ // src/browser/internal/apply-props.ts
5801
+ function applyProps(node, props) {
5802
+ const removers = [];
5803
+ for (const [key, rawValue] of Object.entries(props)) {
5804
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5805
+ if (key.startsWith("on") && typeof rawValue === "function") {
5806
+ const eventName = key.slice(2).toLowerCase();
5807
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5808
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5809
+ const listener = rawValue;
5810
+ node.addEventListener(effectiveEvent, listener);
5811
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5812
+ continue;
5813
+ }
5814
+ if (rawValue === void 0 || rawValue === null) {
5815
+ node.removeAttribute(key);
5816
+ continue;
5817
+ }
5818
+ if (typeof rawValue === "boolean") {
5819
+ if (rawValue) node.setAttribute(key, "");
5820
+ else node.removeAttribute(key);
5821
+ continue;
5822
+ }
5823
+ if (key === "style" && typeof rawValue === "object") {
5824
+ Object.assign(
5825
+ node.style,
5826
+ rawValue
5827
+ );
5828
+ continue;
5829
+ }
5830
+ if (key === "className" || key === "class") {
5831
+ node.setAttribute("class", String(rawValue));
5832
+ continue;
5833
+ }
5834
+ if (key === "htmlFor") {
5835
+ node.setAttribute("for", String(rawValue));
5836
+ continue;
5837
+ }
5838
+ if (key === "tabIndex") {
5839
+ ;
5840
+ node.tabIndex = Number(rawValue);
5841
+ continue;
5842
+ }
5843
+ node.setAttribute(key, String(rawValue));
5844
+ }
5845
+ return () => removers.forEach((fn) => fn());
5846
+ }
5847
+
6083
5848
  // src/browser/views/builder/spread-props.ts
6084
5849
  function spreadProps(node, props) {
6085
5850
  return applyProps(node, props);
@@ -6099,9 +5864,6 @@ function createPersistentSpreads() {
6099
5864
  };
6100
5865
  }
6101
5866
 
6102
- // src/browser/views/render/detail.ts
6103
- var import_lucide7 = require("lucide");
6104
-
6105
5867
  // src/browser/internal/arrow-nav.ts
6106
5868
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6107
5869
  function bindArrowNav(options) {
@@ -6173,30 +5935,6 @@ function focusItem(items, idx) {
6173
5935
  items[idx].focus();
6174
5936
  }
6175
5937
 
6176
- // src/browser/views/builder/filter.ts
6177
- function normalizeQuery(query) {
6178
- return query.trim().toLowerCase();
6179
- }
6180
- function matchesQuery(haystack, query) {
6181
- const q = normalizeQuery(query);
6182
- if (!q) return true;
6183
- return haystack.toLowerCase().includes(q);
6184
- }
6185
- function filterEntities(entities, query) {
6186
- const q = normalizeQuery(query);
6187
- if (!q) return entities;
6188
- return entities.filter((e) => {
6189
- const name = displayName(e).toLowerCase();
6190
- const id = entityKey(e).toLowerCase();
6191
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6192
- });
6193
- }
6194
- function filterIds(ids, query) {
6195
- const q = normalizeQuery(query);
6196
- if (!q) return ids;
6197
- return ids.filter((id) => id.toLowerCase().includes(q));
6198
- }
6199
-
6200
5938
  // src/browser/views/labels.ts
6201
5939
  var SECTION_LABELS = {
6202
5940
  acceptance: "Acceptance criteria",
@@ -6221,171 +5959,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6221
5959
  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";
6222
5960
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6223
5961
 
6224
- // src/browser/ui/button.ts
6225
- 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";
6226
- var buttonVariants = cva(buttonBase, {
6227
- defaultVariants: { size: "default", variant: "default" },
6228
- variants: {
6229
- size: {
6230
- default: "h-8 px-3",
6231
- sm: "h-7 gap-1.5 px-2.5",
6232
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6233
- lg: "h-9 px-3.5",
6234
- xl: "h-10 px-4 text-base",
6235
- icon: "size-8",
6236
- "icon-sm": "size-7",
6237
- "icon-lg": "size-9",
6238
- "icon-xs": "size-6 rounded-md"
6239
- },
6240
- variant: {
6241
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6242
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6243
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6244
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6245
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6246
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6247
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6248
- }
6249
- }
6250
- });
6251
-
6252
- // src/browser/views/primitives/entity-presence.ts
6253
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6254
- "element",
6255
- "region",
6256
- "widget",
6257
- "primitive"
6258
- ]);
6259
- var TOUCH_RESOLVE_KINDS = [
6260
- "element",
6261
- "widget",
6262
- "region",
6263
- "primitive"
6264
- ];
6265
- function isAbsentFromPage(ref2, registry) {
6266
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6267
- return !resolveEntityElement(ref2);
6268
- }
6269
- if (ref2.kind === "flow" && registry) {
6270
- const flow = registry.get("flow", ref2.id);
6271
- if (!flow) return true;
6272
- for (const touchId of flow.touches) {
6273
- for (const kind of TOUCH_RESOLVE_KINDS) {
6274
- const entity = registry.get(kind, touchId);
6275
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6276
- }
6277
- }
6278
- return true;
6279
- }
6280
- return false;
6281
- }
6282
-
6283
- // src/browser/ui/kbd.ts
6284
- 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";
6285
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6286
- function createCommandShortcut(options = {}, children = []) {
6287
- const { class: extra, attrs, ...rest } = options;
6288
- return el(
6289
- "kbd",
6290
- {
6291
- ...rest,
6292
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6293
- attrs: { "data-slot": "command-shortcut", ...attrs }
6294
- },
6295
- children
6296
- );
6297
- }
6298
- function kbdTpl(text, className) {
6299
- return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6300
- >${text}</kbd
6301
- >`;
6302
- }
6303
- function commandShortcutTpl(text, className) {
6304
- return import_lit_html2.html`<kbd
6305
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6306
- data-slot="command-shortcut"
6307
- >${text}</kbd
6308
- >`;
6309
- }
6310
-
6311
- // src/browser/views/primitives/row.ts
6312
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6313
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6314
- function fillRowWithHandle(host, content) {
6315
- if (content.leading) host.append(content.leading);
6316
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6317
- host.append(label);
6318
- if (content.subtitle) {
6319
- host.append(
6320
- el("span", {
6321
- class: SUBTITLE_CLASS,
6322
- attrs: { "data-uidex-row-subtitle": "" },
6323
- text: content.subtitle
6324
- })
6325
- );
6326
- }
6327
- if (content.trailing != null) {
6328
- host.append(
6329
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6330
- );
6331
- }
6332
- return { label };
6333
- }
6334
- function rowTpl(content) {
6335
- return import_lit_html2.html`
6336
- ${content.leading ?? import_lit_html2.nothing}
6337
- <span class=${LABEL_CLASS}>${content.label}</span>
6338
- ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6339
- >${content.subtitle}</span
6340
- >` : import_lit_html2.nothing}
6341
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6342
- `;
6343
- }
6344
-
6345
- // src/browser/views/primitives/entity-link.ts
6346
- var ACTION_CLASS = cn(
6347
- LIST_ITEM_CLASS,
6348
- LIST_ITEM_INTERACTIVE_CLASS,
6349
- "w-full cursor-pointer text-left font-normal"
6350
- );
6351
- function entityLinkTpl(options) {
6352
- const { ctx, target, label, leading, class: extraClass } = options;
6353
- const absent = isAbsentFromPage(target);
6354
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6355
- const onClick = () => ctx.views.navigate(target);
6356
- const preview = () => {
6357
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6358
- };
6359
- const restoreParent = () => {
6360
- if (ctx.ref) {
6361
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6362
- } else {
6363
- ctx.highlight.hide();
6364
- }
6365
- };
6366
- return import_lit_html2.html`
6367
- <button
6368
- type="button"
6369
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6370
- data-slot="list-item-button"
6371
- data-uidex-entity-link
6372
- data-uidex-ref-kind=${target.kind}
6373
- data-uidex-ref-id=${target.id}
6374
- title=${absent ? "Not on this page" : ""}
6375
- @click=${onClick}
6376
- @mouseenter=${preview}
6377
- @mouseleave=${restoreParent}
6378
- @focus=${preview}
6379
- @blur=${restoreParent}
6380
- >
6381
- ${rowTpl({
6382
- leading,
6383
- label: resolvedLabel
6384
- })}
6385
- </button>
6386
- `;
6387
- }
6388
-
6389
5962
  // src/browser/views/primitives/text.ts
6390
5963
  var MUTED_CLASS = "text-sm text-muted-foreground";
6391
5964
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6397,7 +5970,8 @@ function headingTpl(text, className) {
6397
5970
  return import_lit_html2.html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6398
5971
  }
6399
5972
 
6400
- // src/browser/views/render/detail.ts
5973
+ // src/browser/views/render/detail-actions.ts
5974
+ var import_lucide7 = require("lucide");
6401
5975
  var ICON_MAP = {
6402
5976
  "archive-x": import_lucide7.ArchiveX,
6403
5977
  copy: import_lucide7.Copy,
@@ -6413,23 +5987,6 @@ var ICON_MAP = {
6413
5987
  function iconFor(icon2) {
6414
5988
  return icon2 ? ICON_MAP[icon2] : null;
6415
5989
  }
6416
- function subtitleTpl(subtitle) {
6417
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6418
- return import_lit_html2.html`<p
6419
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6420
- data-uidex-detail-subtitle
6421
- >
6422
- ${text}
6423
- </p>`;
6424
- }
6425
- function notFoundTpl(ref2) {
6426
- return import_lit_html2.html`<p
6427
- class=${cn("text-muted-foreground text-sm", "p-4")}
6428
- data-uidex-detail-missing
6429
- >
6430
- ${ref2.kind}: ${ref2.id} not found in registry
6431
- </p>`;
6432
- }
6433
5990
  function renderActions(actions, ctx, root, heading) {
6434
5991
  const cleanups = [];
6435
5992
  const buttons = [];
@@ -6540,6 +6097,166 @@ function renderActions(actions, ctx, root, heading) {
6540
6097
  }
6541
6098
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6542
6099
  }
6100
+
6101
+ // src/browser/views/render/detail-sections.ts
6102
+ var import_static = require("lit-html/static.js");
6103
+
6104
+ // src/browser/views/builder/filter.ts
6105
+ function normalizeQuery(query) {
6106
+ return query.trim().toLowerCase();
6107
+ }
6108
+ function matchesQuery(haystack, query) {
6109
+ const q = normalizeQuery(query);
6110
+ if (!q) return true;
6111
+ return haystack.toLowerCase().includes(q);
6112
+ }
6113
+ function filterEntities(entities, query) {
6114
+ const q = normalizeQuery(query);
6115
+ if (!q) return entities;
6116
+ return entities.filter((e) => {
6117
+ const name = displayName(e).toLowerCase();
6118
+ const id = entityKey(e).toLowerCase();
6119
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6120
+ });
6121
+ }
6122
+ function filterIds(ids, query) {
6123
+ const q = normalizeQuery(query);
6124
+ if (!q) return ids;
6125
+ return ids.filter((id) => id.toLowerCase().includes(q));
6126
+ }
6127
+
6128
+ // src/browser/views/primitives/entity-presence.ts
6129
+ var TOUCH_RESOLVE_KINDS = [
6130
+ "element",
6131
+ "widget",
6132
+ "region",
6133
+ "primitive"
6134
+ ];
6135
+ function isAbsentFromPage(ref2, registry) {
6136
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6137
+ return !resolveEntityElement(ref2);
6138
+ }
6139
+ if (ref2.kind === "flow" && registry) {
6140
+ const flow = registry.get("flow", ref2.id);
6141
+ if (!flow) return true;
6142
+ for (const touchId of flow.touches) {
6143
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6144
+ const entity = registry.get(kind, touchId);
6145
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6146
+ }
6147
+ }
6148
+ return true;
6149
+ }
6150
+ return false;
6151
+ }
6152
+
6153
+ // src/browser/ui/kbd.ts
6154
+ 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";
6155
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6156
+ function createCommandShortcut(options = {}, children = []) {
6157
+ const { class: extra, attrs, ...rest } = options;
6158
+ return el(
6159
+ "kbd",
6160
+ {
6161
+ ...rest,
6162
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6163
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6164
+ },
6165
+ children
6166
+ );
6167
+ }
6168
+ function kbdTpl(text, className) {
6169
+ return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6170
+ >${text}</kbd
6171
+ >`;
6172
+ }
6173
+ function commandShortcutTpl(text, className) {
6174
+ return import_lit_html2.html`<kbd
6175
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6176
+ data-slot="command-shortcut"
6177
+ >${text}</kbd
6178
+ >`;
6179
+ }
6180
+
6181
+ // src/browser/views/primitives/row.ts
6182
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6183
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6184
+ function fillRowWithHandle(host, content) {
6185
+ if (content.leading) host.append(content.leading);
6186
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6187
+ host.append(label);
6188
+ if (content.subtitle) {
6189
+ host.append(
6190
+ el("span", {
6191
+ class: SUBTITLE_CLASS,
6192
+ attrs: { "data-uidex-row-subtitle": "" },
6193
+ text: content.subtitle
6194
+ })
6195
+ );
6196
+ }
6197
+ if (content.trailing != null) {
6198
+ host.append(
6199
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6200
+ );
6201
+ }
6202
+ return { label };
6203
+ }
6204
+ function rowTpl(content) {
6205
+ return import_lit_html2.html`
6206
+ ${content.leading ?? import_lit_html2.nothing}
6207
+ <span class=${LABEL_CLASS}>${content.label}</span>
6208
+ ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6209
+ >${content.subtitle}</span
6210
+ >` : import_lit_html2.nothing}
6211
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6212
+ `;
6213
+ }
6214
+
6215
+ // src/browser/views/primitives/entity-link.ts
6216
+ var ACTION_CLASS = cn(
6217
+ LIST_ITEM_CLASS,
6218
+ LIST_ITEM_INTERACTIVE_CLASS,
6219
+ "w-full cursor-pointer text-left font-normal"
6220
+ );
6221
+ function entityLinkTpl(options) {
6222
+ const { ctx, target, label, leading, class: extraClass } = options;
6223
+ const absent = isAbsentFromPage(target);
6224
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6225
+ const onClick = () => ctx.views.navigate(target);
6226
+ const preview = () => {
6227
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6228
+ };
6229
+ const restoreParent = () => {
6230
+ if (ctx.ref) {
6231
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6232
+ } else {
6233
+ ctx.highlight.hide();
6234
+ }
6235
+ };
6236
+ return import_lit_html2.html`
6237
+ <button
6238
+ type="button"
6239
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6240
+ data-slot="list-item-button"
6241
+ data-uidex-entity-link
6242
+ data-uidex-ref-kind=${target.kind}
6243
+ data-uidex-ref-id=${target.id}
6244
+ title=${absent ? "Not on this page" : ""}
6245
+ @click=${onClick}
6246
+ @mouseenter=${preview}
6247
+ @mouseleave=${restoreParent}
6248
+ @focus=${preview}
6249
+ @blur=${restoreParent}
6250
+ >
6251
+ ${rowTpl({
6252
+ leading,
6253
+ label: resolvedLabel
6254
+ })}
6255
+ </button>
6256
+ `;
6257
+ }
6258
+
6259
+ // src/browser/views/render/detail-sections.ts
6543
6260
  function entityListItems(ctx, entities) {
6544
6261
  return import_lit_html2.html`${entities.map((entity) => {
6545
6262
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6553,71 +6270,29 @@ function entityListItems(ctx, entities) {
6553
6270
  </li>`;
6554
6271
  })}`;
6555
6272
  }
6556
- function composesListTpl(ctx, label, entities) {
6557
- if (entities.length === 0) return null;
6558
- return import_lit_html2.html`
6559
- <section class="flex flex-col" data-uidex-detail-composes>
6560
- ${headingTpl(label)}
6561
- <ul class="flex flex-col">
6562
- ${entityListItems(ctx, entities)}
6563
- </ul>
6564
- </section>
6565
- `;
6566
- }
6567
- function usedByListTpl(ctx, label, entities) {
6568
- if (entities.length === 0) return null;
6569
- return import_lit_html2.html`
6570
- <section class="flex flex-col" data-uidex-detail-used-by>
6571
- ${headingTpl(label)}
6572
- <ul class="flex flex-col">
6573
- ${entityListItems(ctx, entities)}
6574
- </ul>
6575
- </section>
6576
- `;
6577
- }
6578
- function flowListTpl(ctx, flows) {
6579
- if (flows.length === 0) return null;
6580
- return import_lit_html2.html`
6581
- <section class="flex flex-col" data-uidex-detail-flows>
6582
- ${headingTpl(SECTION_LABELS.flows)}
6583
- <ul class="flex flex-col">
6584
- ${flows.map(
6585
- (flow) => import_lit_html2.html`<li>
6586
- ${entityLinkTpl({
6587
- ctx,
6588
- target: { kind: "flow", id: flow.id },
6589
- label: displayName(flow),
6590
- leading: kindIconTileTpl("flow")
6591
- })}
6592
- </li>`
6593
- )}
6594
- </ul>
6595
- </section>
6596
- `;
6597
- }
6598
- function touchesTpl(ctx, entities, query) {
6273
+ var DETAIL_SECTION_ATTRS = {
6274
+ composes: import_static.literal`data-uidex-detail-composes`,
6275
+ "used-by": import_static.literal`data-uidex-detail-used-by`,
6276
+ flows: import_static.literal`data-uidex-detail-flows`,
6277
+ touches: import_static.literal`data-uidex-detail-touches`
6278
+ };
6279
+ function entitySectionTpl(ctx, opts) {
6280
+ const { label, entities, dataAttr, emptyText } = opts;
6281
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6599
6282
  if (entities.length === 0) {
6600
- return import_lit_html2.html`
6601
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6602
- ${headingTpl(SECTION_LABELS.touches)}
6603
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6283
+ if (emptyText === void 0) return null;
6284
+ return import_static.html`
6285
+ <section class="flex flex-col gap-2" ${attr}>
6286
+ ${headingTpl(label)}
6287
+ ${mutedTextTpl(emptyText)}
6604
6288
  </section>
6605
6289
  `;
6606
6290
  }
6607
- return import_lit_html2.html`
6608
- <section class="flex flex-col" data-uidex-detail-touches>
6609
- ${headingTpl(SECTION_LABELS.touches)}
6291
+ return import_static.html`
6292
+ <section class="flex flex-col" ${attr}>
6293
+ ${headingTpl(label)}
6610
6294
  <ul class="flex flex-col">
6611
- ${entities.map(
6612
- (entity) => import_lit_html2.html`<li>
6613
- ${entityLinkTpl({
6614
- ctx,
6615
- target: { kind: entity.kind, id: entityKey(entity) },
6616
- label: displayName(entity),
6617
- leading: kindIconTileTpl(entity.kind)
6618
- })}
6619
- </li>`
6620
- )}
6295
+ ${entityListItems(ctx, entities)}
6621
6296
  </ul>
6622
6297
  </section>
6623
6298
  `;
@@ -6774,6 +6449,20 @@ function screenshotTpl(url) {
6774
6449
  </section>
6775
6450
  `;
6776
6451
  }
6452
+ function lazyScreenshotEl(load) {
6453
+ const holder = document.createElement("div");
6454
+ holder.hidden = true;
6455
+ void load().then(
6456
+ (url) => {
6457
+ if (!url) return;
6458
+ (0, import_lit_html2.render)(screenshotTpl(url), holder);
6459
+ holder.hidden = false;
6460
+ },
6461
+ () => {
6462
+ }
6463
+ );
6464
+ return holder;
6465
+ }
6777
6466
  function metadataListTpl(entries) {
6778
6467
  return import_lit_html2.html`
6779
6468
  <div
@@ -6804,28 +6493,30 @@ function sectionTpl(section, ctx, query) {
6804
6493
  return acceptanceChecklistTpl(section.items);
6805
6494
  }
6806
6495
  case "composes":
6807
- return composesListTpl(
6808
- ctx,
6809
- section.label,
6810
- section.filterable ? filterEntities(section.entities, query) : section.entities
6811
- );
6496
+ return entitySectionTpl(ctx, {
6497
+ label: section.label,
6498
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6499
+ dataAttr: "composes"
6500
+ });
6812
6501
  case "used-by":
6813
- return usedByListTpl(
6814
- ctx,
6815
- section.label,
6816
- section.filterable ? filterEntities(section.entities, query) : section.entities
6817
- );
6502
+ return entitySectionTpl(ctx, {
6503
+ label: section.label,
6504
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6505
+ dataAttr: "used-by"
6506
+ });
6818
6507
  case "flows":
6819
- return flowListTpl(
6820
- ctx,
6821
- section.filterable ? filterEntities(section.flows, query) : section.flows
6822
- );
6508
+ return entitySectionTpl(ctx, {
6509
+ label: SECTION_LABELS.flows,
6510
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6511
+ dataAttr: "flows"
6512
+ });
6823
6513
  case "touches":
6824
- return touchesTpl(
6825
- ctx,
6826
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6827
- query
6828
- );
6514
+ return entitySectionTpl(ctx, {
6515
+ label: SECTION_LABELS.touches,
6516
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6517
+ dataAttr: "touches",
6518
+ emptyText: query ? "No matches" : "No entities touched"
6519
+ });
6829
6520
  case "steps":
6830
6521
  return stepsTpl(
6831
6522
  ctx,
@@ -6837,11 +6528,32 @@ function sectionTpl(section, ctx, query) {
6837
6528
  section.filterable ? filterIds(section.paths, query) : section.paths
6838
6529
  );
6839
6530
  case "screenshot":
6840
- return screenshotTpl(section.url);
6531
+ if (section.url) return screenshotTpl(section.url);
6532
+ if (section.load) return import_lit_html2.html`${lazyScreenshotEl(section.load)}`;
6533
+ return null;
6841
6534
  case "metadata":
6842
6535
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6843
6536
  }
6844
6537
  }
6538
+
6539
+ // src/browser/views/render/detail.ts
6540
+ function subtitleTpl(subtitle) {
6541
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6542
+ return import_lit_html2.html`<p
6543
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6544
+ data-uidex-detail-subtitle
6545
+ >
6546
+ ${text}
6547
+ </p>`;
6548
+ }
6549
+ function notFoundTpl(ref2) {
6550
+ return import_lit_html2.html`<p
6551
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6552
+ data-uidex-detail-missing
6553
+ >
6554
+ ${ref2.kind}: ${ref2.id} not found in registry
6555
+ </p>`;
6556
+ }
6845
6557
  function hasFilterableSections(sections) {
6846
6558
  return sections.some((s) => "filterable" in s && s.filterable === true);
6847
6559
  }
@@ -7202,6 +6914,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7202
6914
  };
7203
6915
  }
7204
6916
 
6917
+ // src/browser/ui/button.ts
6918
+ 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";
6919
+ var buttonVariants = cva(buttonBase, {
6920
+ defaultVariants: { size: "default", variant: "default" },
6921
+ variants: {
6922
+ size: {
6923
+ default: "h-8 px-3",
6924
+ sm: "h-7 gap-1.5 px-2.5",
6925
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6926
+ lg: "h-9 px-3.5",
6927
+ xl: "h-10 px-4 text-base",
6928
+ icon: "size-8",
6929
+ "icon-sm": "size-7",
6930
+ "icon-lg": "size-9",
6931
+ "icon-xs": "size-6 rounded-md"
6932
+ },
6933
+ variant: {
6934
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6935
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6936
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6937
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6938
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6939
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6940
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6941
+ }
6942
+ }
6943
+ });
6944
+
7205
6945
  // src/browser/views/render/form.ts
7206
6946
  var fieldSeq = 0;
7207
6947
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7398,9 +7138,9 @@ function iconMediaTpl(iconTpl) {
7398
7138
  </div>
7399
7139
  `;
7400
7140
  }
7401
- function loadingViewTpl() {
7141
+ function loadingViewTpl(rootRef) {
7402
7142
  return import_lit_html2.html`
7403
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7143
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${(0, import_ref.ref)(rootRef)}>
7404
7144
  ${iconMediaTpl(icon(import_lucide8.Loader2, "animate-spin"))}
7405
7145
  <div class="flex max-w-sm flex-col items-center text-center">
7406
7146
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7413,42 +7153,47 @@ function loadingViewTpl() {
7413
7153
  </div>
7414
7154
  `;
7415
7155
  }
7416
- function successViewTpl() {
7156
+ function successViewTpl(refs) {
7417
7157
  return import_lit_html2.html`
7418
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7158
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${(0, import_ref.ref)(refs.root)}>
7419
7159
  ${iconMediaTpl(icon(import_lucide8.CircleCheck))}
7420
7160
  <div class="flex max-w-sm flex-col items-center text-center">
7421
7161
  <div
7422
7162
  class="font-heading text-xl font-semibold"
7423
7163
  data-uidex-success-title
7164
+ ${(0, import_ref.ref)(refs.title)}
7424
7165
  ></div>
7425
7166
  </div>
7426
7167
  <div
7427
7168
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7428
7169
  data-uidex-success-actions
7170
+ ${(0, import_ref.ref)(refs.actions)}
7429
7171
  >
7430
7172
  <a
7431
7173
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7432
7174
  target="_blank"
7433
7175
  rel="noreferrer"
7434
7176
  data-uidex-success-link
7177
+ ${(0, import_ref.ref)(refs.link)}
7435
7178
  ></a>
7436
7179
  </div>
7437
7180
  </div>
7438
7181
  `;
7439
7182
  }
7440
- function errorViewTpl() {
7183
+ function errorViewTpl(refs) {
7441
7184
  return import_lit_html2.html`
7442
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7185
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${(0, import_ref.ref)(refs.root)}>
7443
7186
  ${iconMediaTpl(icon(import_lucide8.CircleX))}
7444
7187
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7445
7188
  <div
7446
7189
  class="font-heading text-xl font-semibold"
7447
7190
  data-uidex-error-title
7191
+ ${(0, import_ref.ref)(refs.title)}
7448
7192
  ></div>
7449
7193
  <p
7450
7194
  class="text-muted-foreground text-sm"
7451
7195
  data-uidex-error-description
7196
+ ${(0, import_ref.ref)(refs.description)}
7452
7197
  ></p>
7453
7198
  </div>
7454
7199
  <div
@@ -7459,6 +7204,7 @@ function errorViewTpl() {
7459
7204
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7460
7205
  data-slot="button"
7461
7206
  data-uidex-retry-button
7207
+ ${(0, import_ref.ref)(refs.retry)}
7462
7208
  >
7463
7209
  Try Again
7464
7210
  </button>
@@ -7500,8 +7246,15 @@ function renderFormSurface(surface, ctx, root) {
7500
7246
  });
7501
7247
  }
7502
7248
  const formRef = (0, import_ref.createRef)();
7503
- const statusRef = (0, import_ref.createRef)();
7504
- const submitRef = (0, import_ref.createRef)();
7249
+ const loadingRef = (0, import_ref.createRef)();
7250
+ const successRef = (0, import_ref.createRef)();
7251
+ const successTitleRef = (0, import_ref.createRef)();
7252
+ const successLinkRef = (0, import_ref.createRef)();
7253
+ const successActionsRef = (0, import_ref.createRef)();
7254
+ const errorRef = (0, import_ref.createRef)();
7255
+ const errorTitleRef = (0, import_ref.createRef)();
7256
+ const errorDescriptionRef = (0, import_ref.createRef)();
7257
+ const retryRef = (0, import_ref.createRef)();
7505
7258
  (0, import_lit_html2.render)(
7506
7259
  import_lit_html2.html`
7507
7260
  <section
@@ -7516,7 +7269,19 @@ function renderFormSurface(surface, ctx, root) {
7516
7269
  novalidate
7517
7270
  data-uidex-form=${surface.id}
7518
7271
  ></form>
7519
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7272
+ ${loadingViewTpl(loadingRef)}
7273
+ ${successViewTpl({
7274
+ root: successRef,
7275
+ title: successTitleRef,
7276
+ link: successLinkRef,
7277
+ actions: successActionsRef
7278
+ })}
7279
+ ${errorViewTpl({
7280
+ root: errorRef,
7281
+ title: errorTitleRef,
7282
+ description: errorDescriptionRef,
7283
+ retry: retryRef
7284
+ })}
7520
7285
  </section>
7521
7286
  `,
7522
7287
  root
@@ -7546,32 +7311,15 @@ function renderFormSurface(surface, ctx, root) {
7546
7311
  }
7547
7312
  });
7548
7313
  form.append(status);
7549
- const sectionEl = root.querySelector(
7550
- "[data-uidex-form-surface]"
7551
- );
7552
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7553
- const successView = sectionEl.querySelector(
7554
- "[data-uidex-success-view]"
7555
- );
7556
- const successTitle = sectionEl.querySelector(
7557
- "[data-uidex-success-title]"
7558
- );
7559
- const successLink = sectionEl.querySelector(
7560
- "[data-uidex-success-link]"
7561
- );
7562
- const successActions = successLink.parentElement;
7563
- const errorView = sectionEl.querySelector(
7564
- "[data-uidex-error-view]"
7565
- );
7566
- const errorTitle = sectionEl.querySelector(
7567
- "[data-uidex-error-title]"
7568
- );
7569
- const errorDescription = sectionEl.querySelector(
7570
- "[data-uidex-error-description]"
7571
- );
7572
- const retryButton = sectionEl.querySelector(
7573
- "[data-uidex-retry-button]"
7574
- );
7314
+ const loadingView = loadingRef.value;
7315
+ const successView = successRef.value;
7316
+ const successTitle = successTitleRef.value;
7317
+ const successLink = successLinkRef.value;
7318
+ const successActions = successActionsRef.value;
7319
+ const errorView = errorRef.value;
7320
+ const errorTitle = errorTitleRef.value;
7321
+ const errorDescription = errorDescriptionRef.value;
7322
+ const retryButton = retryRef.value;
7575
7323
  function clearAllFieldErrors() {
7576
7324
  for (const f of fields) f.setError(null);
7577
7325
  }
@@ -7972,7 +7720,7 @@ function defaultFilter(item, query) {
7972
7720
  function rowTag(item) {
7973
7721
  return item.tag ?? item.value;
7974
7722
  }
7975
- function resolveLeadingTpl(item, ctx) {
7723
+ function resolveLeadingTpl(item) {
7976
7724
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
7977
7725
  if (item.leading) {
7978
7726
  const node = item.leading();
@@ -8077,7 +7825,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8077
7825
  data-uidex-item-value=${item.value}
8078
7826
  >
8079
7827
  ${rowTpl({
8080
- leading: resolveLeadingTpl(item, ctx),
7828
+ leading: resolveLeadingTpl(item),
8081
7829
  label: item.label,
8082
7830
  subtitle: item.subtitle,
8083
7831
  trailing: item.trailing ?? item.shortcut
@@ -8122,7 +7870,7 @@ function renderListSurface(surface, ctx, root) {
8122
7870
  return root.ownerDocument ?? document;
8123
7871
  };
8124
7872
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8125
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7873
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8126
7874
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8127
7875
  const controller = createListController({
8128
7876
  surfaceId: surface.id,
@@ -8818,52 +8566,16 @@ function sameRef2(a, b) {
8818
8566
  if (a === null || b === null) return false;
8819
8567
  return a.kind === b.kind && a.id === b.id;
8820
8568
  }
8821
- function resolveHints(view, ctx) {
8822
- const h = view.hints;
8823
- if (!h) return [];
8824
- if (typeof h === "function") {
8825
- try {
8826
- return h(ctx) ?? [];
8827
- } catch (err) {
8828
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8829
- return [];
8830
- }
8831
- }
8832
- return h;
8833
- }
8834
- function resolveTitle(view, ctx) {
8835
- const t = view.title;
8836
- if (!t) return "";
8837
- if (typeof t === "function") {
8838
- try {
8839
- return t(ctx) ?? "";
8840
- } catch (err) {
8841
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8842
- return "";
8843
- }
8844
- }
8845
- return t;
8846
- }
8847
- function resolveActions(view, ctx, globalActions) {
8848
- const result = [];
8849
- const fn = view.actions;
8850
- if (fn) {
8851
- try {
8852
- const viewActions2 = fn(ctx) ?? [];
8853
- result.push(...viewActions2);
8854
- } catch (err) {
8855
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8856
- }
8857
- }
8858
- if (globalActions) {
8569
+ function resolveProp(view, ctx, value, propName, fallback) {
8570
+ if (typeof value === "function") {
8859
8571
  try {
8860
- const globals = globalActions(ctx) ?? [];
8861
- result.push(...globals);
8572
+ return value(ctx) ?? fallback;
8862
8573
  } catch (err) {
8863
- console.error(`[uidex] globalActions() threw`, err);
8574
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8575
+ return fallback;
8864
8576
  }
8865
8577
  }
8866
- return result;
8578
+ return value ?? fallback;
8867
8579
  }
8868
8580
  function createViewStack(options) {
8869
8581
  const { container, views, session, registry, highlight } = options;
@@ -8931,39 +8643,40 @@ function createViewStack(options) {
8931
8643
  color: KIND_STYLE[top.ctx.ref.kind].color
8932
8644
  });
8933
8645
  }
8934
- function updateChrome() {
8935
- if (!shell) return;
8936
- const top = mounted[mounted.length - 1];
8937
- if (!top) return;
8646
+ function updateNavButtons(shell2, top) {
8938
8647
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8939
- shell.backBtn.hidden = atRoot;
8940
- shell.searchIcon.hidden = !atRoot;
8648
+ shell2.backBtn.hidden = atRoot;
8649
+ shell2.searchIcon.hidden = !atRoot;
8650
+ }
8651
+ function updateTitle(shell2, top) {
8941
8652
  const searchable = top.view.searchable !== false;
8942
- shell.searchInput.hidden = !searchable;
8943
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8944
- shell.headerTitle.textContent = titleText;
8945
- shell.headerTitle.hidden = searchable || !titleText;
8946
- shell.footerLeft.replaceChildren();
8653
+ shell2.searchInput.hidden = !searchable;
8654
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8655
+ shell2.headerTitle.textContent = titleText;
8656
+ shell2.headerTitle.hidden = searchable || !titleText;
8657
+ }
8658
+ function updateFooterChip(shell2, top) {
8659
+ shell2.footerLeft.replaceChildren();
8947
8660
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8948
8661
  if (refEntity) {
8949
- shell.footerLeft.append(
8662
+ shell2.footerLeft.append(
8950
8663
  renderKindChip({
8951
8664
  entity: refEntity,
8952
8665
  withKindName: true
8953
8666
  })
8954
8667
  );
8955
8668
  } else {
8956
- shell.footerLeft.append(shell.logo);
8669
+ shell2.footerLeft.append(shell2.logo);
8957
8670
  }
8958
- hintChangeSub?.();
8959
- hintChangeSub = null;
8960
- highlightActionsSub?.();
8961
- highlightActionsSub = null;
8962
- shell.footerRight.replaceChildren();
8963
- const footerItems = [];
8964
- const enterHint = resolveHints(top.view, top.ctx).find(
8965
- (h) => h.key.includes("\u21B5")
8966
- );
8671
+ }
8672
+ function buildEnterHint(top, footerItems) {
8673
+ const enterHint = resolveProp(
8674
+ top.view,
8675
+ top.ctx,
8676
+ top.view.hints,
8677
+ "hints",
8678
+ []
8679
+ ).find((h) => h.key.includes("\u21B5"));
8967
8680
  const src = top.mounted.submitIntent;
8968
8681
  if (src) {
8969
8682
  const key = enterHint?.key ?? "\u21B5";
@@ -8984,28 +8697,52 @@ function createViewStack(options) {
8984
8697
  } else if (enterHint) {
8985
8698
  footerItems.push(createHint(enterHint.key, enterHint.label));
8986
8699
  }
8700
+ }
8701
+ function buildActions(shell2, top, footerItems) {
8987
8702
  const actionsDivider = createFooterDivider();
8988
- const viewActions2 = resolveActions(top.view, top.ctx);
8703
+ const viewActions2 = resolveProp(
8704
+ top.view,
8705
+ top.ctx,
8706
+ top.view.actions,
8707
+ "actions",
8708
+ []
8709
+ );
8989
8710
  const hlSrc = top.mounted.highlightActions;
8990
8711
  const syncActions = () => {
8991
8712
  const hlActions = hlSrc?.get() ?? [];
8992
8713
  const combined = [...hlActions, ...viewActions2];
8993
- shell.actionsPopup.setActions(combined);
8714
+ shell2.actionsPopup.setActions(combined);
8994
8715
  const visible = combined.length > 0;
8995
- shell.actionsPopup.trigger.hidden = !visible;
8716
+ shell2.actionsPopup.trigger.hidden = !visible;
8996
8717
  actionsDivider.hidden = !visible || footerItems.length === 0;
8997
8718
  };
8998
8719
  if (hlSrc) {
8999
8720
  highlightActionsSub = hlSrc.subscribe(syncActions);
9000
8721
  }
9001
8722
  for (let i = 0; i < footerItems.length; i++) {
9002
- if (i > 0) shell.footerRight.append(createFooterDivider());
9003
- shell.footerRight.append(footerItems[i]);
8723
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8724
+ shell2.footerRight.append(footerItems[i]);
9004
8725
  }
9005
- shell.footerRight.append(actionsDivider);
9006
- shell.footerRight.append(shell.actionsPopup.trigger);
8726
+ shell2.footerRight.append(actionsDivider);
8727
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
9007
8728
  syncActions();
9008
8729
  }
8730
+ function updateChrome() {
8731
+ if (!shell) return;
8732
+ const top = mounted[mounted.length - 1];
8733
+ if (!top) return;
8734
+ updateNavButtons(shell, top);
8735
+ updateTitle(shell, top);
8736
+ updateFooterChip(shell, top);
8737
+ hintChangeSub?.();
8738
+ hintChangeSub = null;
8739
+ highlightActionsSub?.();
8740
+ highlightActionsSub = null;
8741
+ shell.footerRight.replaceChildren();
8742
+ const footerItems = [];
8743
+ buildEnterHint(top, footerItems);
8744
+ buildActions(shell, top, footerItems);
8745
+ }
9009
8746
  function render2() {
9010
8747
  if (!container.isConnected) return;
9011
8748
  const stack = session.getState().stack;
@@ -9102,7 +8839,7 @@ function createViewStack(options) {
9102
8839
 
9103
8840
  // src/browser/views/built-in/ids.ts
9104
8841
  var BUILT_IN_VIEW_IDS = {
9105
- archiveReason: "archive-reason",
8842
+ closeReason: "close-reason",
9106
8843
  commandPalette: "command-palette",
9107
8844
  elements: "elements",
9108
8845
  entityReports: "entity-reports",
@@ -9153,13 +8890,13 @@ function parentDetail(ref2) {
9153
8890
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9154
8891
  }
9155
8892
 
9156
- // src/browser/views/built-in/archive-reason.ts
8893
+ // src/browser/views/built-in/close-reason.ts
9157
8894
  var import_lucide11 = require("lucide");
9158
8895
  var pendingReportId = null;
9159
- var afterArchive = null;
9160
- function setArchiveTarget(reportId, onDone) {
8896
+ var afterClose = null;
8897
+ function setCloseTarget(reportId, onDone) {
9161
8898
  pendingReportId = reportId;
9162
- afterArchive = onDone;
8899
+ afterClose = onDone;
9163
8900
  }
9164
8901
  function leadingIcon(iconNode) {
9165
8902
  return () => {
@@ -9174,16 +8911,16 @@ function spinnerTile() {
9174
8911
  return createIconTile(spinner);
9175
8912
  }
9176
8913
  var REASONS = [
9177
- { value: "fixed", label: "Fixed", icon: import_lucide11.CircleCheck },
8914
+ { value: "fixed", label: "Resolved", icon: import_lucide11.CircleCheck },
9178
8915
  { value: "not_a_bug", label: "Not a bug", icon: import_lucide11.BugOff },
9179
8916
  { value: "duplicate", label: "Duplicate", icon: import_lucide11.Copy },
9180
8917
  { value: "wont_fix", label: "Won't fix", icon: import_lucide11.Ban }
9181
8918
  ];
9182
- var archiveReasonView = {
9183
- id: BUILT_IN_VIEW_IDS.archiveReason,
8919
+ var closeReasonView = {
8920
+ id: BUILT_IN_VIEW_IDS.closeReason,
9184
8921
  matches: () => false,
9185
8922
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9186
- title: "Archive reason",
8923
+ title: "Close reason",
9187
8924
  searchable: false,
9188
8925
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9189
8926
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9196,12 +8933,12 @@ var archiveReasonView = {
9196
8933
  }));
9197
8934
  const surface = {
9198
8935
  kind: "list",
9199
- id: "uidex-archive-reason",
8936
+ id: "uidex-close-reason",
9200
8937
  searchable: false,
9201
8938
  items,
9202
8939
  emptyLabel: "No reasons available",
9203
8940
  onSelect: async (item) => {
9204
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8941
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9205
8942
  busy = true;
9206
8943
  const row = root.querySelector(
9207
8944
  `[data-uidex-item-value="${item.value}"]`
@@ -9213,13 +8950,10 @@ var archiveReasonView = {
9213
8950
  row.style.pointerEvents = "none";
9214
8951
  }
9215
8952
  try {
9216
- await ctx.registry.archiveReport(
9217
- pendingReportId,
9218
- item.value
9219
- );
9220
- const done = afterArchive;
8953
+ await ctx.registry.closeReport(pendingReportId, item.value);
8954
+ const done = afterClose;
9221
8955
  pendingReportId = null;
9222
- afterArchive = null;
8956
+ afterClose = null;
9223
8957
  done?.();
9224
8958
  } catch {
9225
8959
  busy = false;
@@ -9237,7 +8971,7 @@ var archiveReasonView = {
9237
8971
  }
9238
8972
  const error = document.createElement("p");
9239
8973
  error.className = "text-destructive px-4 py-2 text-xs";
9240
- error.textContent = "Archive failed \u2014 try again";
8974
+ error.textContent = "Close failed \u2014 try again";
9241
8975
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9242
8976
  setTimeout(() => error.remove(), 3e3);
9243
8977
  }
@@ -9556,6 +9290,7 @@ function createCommandPaletteView(shortcut) {
9556
9290
 
9557
9291
  // src/browser/internal/screenshot.ts
9558
9292
  var import_modern_screenshot = require("modern-screenshot");
9293
+ var WEBP_QUALITY = 0.85;
9559
9294
  function resolveBackgroundColor(el2) {
9560
9295
  let node = el2;
9561
9296
  while (node) {
@@ -9569,8 +9304,7 @@ function isUidexChrome(node) {
9569
9304
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9570
9305
  const el2 = node;
9571
9306
  if (el2.shadowRoot !== null) return true;
9572
- const cls = el2.classList;
9573
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9307
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9574
9308
  }
9575
9309
  async function captureScreenshot(options = {}) {
9576
9310
  if (typeof document === "undefined") {
@@ -9582,8 +9316,10 @@ async function captureScreenshot(options = {}) {
9582
9316
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9583
9317
  const padding = 16;
9584
9318
  const height = target.scrollHeight || target.clientHeight;
9585
- return (0, import_modern_screenshot.domToPng)(target, {
9319
+ const encode = options.format === "png" ? import_modern_screenshot.domToPng : import_modern_screenshot.domToWebp;
9320
+ return encode(target, {
9586
9321
  scale,
9322
+ quality: WEBP_QUALITY,
9587
9323
  width: width + padding * 2,
9588
9324
  height: height + padding * 2,
9589
9325
  backgroundColor: resolveBackgroundColor(target),
@@ -9639,12 +9375,6 @@ function collectFlowsTouching(ctx, targetId) {
9639
9375
  }
9640
9376
 
9641
9377
  // src/browser/views/builder/detail-builder.ts
9642
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9643
- "element",
9644
- "region",
9645
- "widget",
9646
- "primitive"
9647
- ]);
9648
9378
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9649
9379
  function copyPathAction(ref2, loc) {
9650
9380
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9746,7 +9476,10 @@ function copyScreenshotAction(ref2) {
9746
9476
  const target = resolveEntityElement(ref2) ?? void 0;
9747
9477
  const dataUrl = await captureScreenshot({
9748
9478
  target,
9749
- maxWidth: 1280
9479
+ maxWidth: 1280,
9480
+ // Clipboard write requires PNG — the async Clipboard API rejects
9481
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9482
+ format: "png"
9750
9483
  });
9751
9484
  const res = await fetch(dataUrl);
9752
9485
  const blob = await res.blob();
@@ -9797,7 +9530,7 @@ function createEntityDetailView(config) {
9797
9530
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9798
9531
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9799
9532
  }
9800
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9533
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9801
9534
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9802
9535
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9803
9536
  }
@@ -9823,7 +9556,7 @@ function createEntityDetailView(config) {
9823
9556
  sections.push(s);
9824
9557
  }
9825
9558
  }
9826
- if (!DOM_BACKED_KINDS2.has(kind)) {
9559
+ if (!DOM_BACKED_KINDS.has(kind)) {
9827
9560
  sections.push({
9828
9561
  id: "composes",
9829
9562
  label: SECTION_LABELS.contains,
@@ -9831,7 +9564,7 @@ function createEntityDetailView(config) {
9831
9564
  filterable: true
9832
9565
  });
9833
9566
  }
9834
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9567
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9835
9568
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9836
9569
  if (featureEntities.length > 0) {
9837
9570
  sections.push({
@@ -9861,35 +9594,14 @@ function createEntityDetailView(config) {
9861
9594
  }
9862
9595
 
9863
9596
  // src/browser/views/built-in/entity-detail.ts
9864
- function collectDomParents(ctx, ref2) {
9865
- const el2 = resolveEntityElement(ref2);
9866
- if (!el2) return [];
9867
- const parents = [];
9868
- const seen = /* @__PURE__ */ new Set();
9869
- let node = el2.parentElement;
9870
- while (node) {
9871
- if (node instanceof HTMLElement) {
9872
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9873
- const id = node.getAttribute(attr);
9874
- if (id) {
9875
- const key = `${kind}:${id}`;
9876
- if (!seen.has(key)) {
9877
- seen.add(key);
9878
- const entity = ctx.registry.get(kind, id);
9879
- if (entity) parents.push(entity);
9880
- }
9881
- }
9882
- }
9883
- }
9884
- node = node.parentElement;
9885
- }
9886
- return parents;
9597
+ function collectFeatureConsumers(ctx, featureId) {
9598
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9887
9599
  }
9888
- function usedBySection(ctx, ref2) {
9600
+ function usedBySection(ctx, featureId) {
9889
9601
  return {
9890
9602
  id: "used-by",
9891
9603
  label: SECTION_LABELS.usedBy,
9892
- entities: collectDomParents(ctx, ref2),
9604
+ entities: collectFeatureConsumers(ctx, featureId),
9893
9605
  filterable: true
9894
9606
  };
9895
9607
  }
@@ -9910,9 +9622,7 @@ var featureDetailView = createEntityDetailView({
9910
9622
  id: "feature-detail",
9911
9623
  kind: "feature",
9912
9624
  fallbackTitle: "Feature",
9913
- extraSections: (ctx, entity) => [
9914
- usedBySection(ctx, { kind: "feature", id: entity.id })
9915
- ]
9625
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9916
9626
  });
9917
9627
  var regionDetailView = createEntityDetailView({
9918
9628
  id: "region-detail",
@@ -10040,6 +9750,15 @@ var reportDetailView = {
10040
9750
  }
10041
9751
  if (report.screenshot) {
10042
9752
  sections.push({ id: "screenshot", url: report.screenshot });
9753
+ } else {
9754
+ const pins = ctx.cloud?.pins;
9755
+ const fetchScreenshot = pins?.screenshot;
9756
+ if (pins && fetchScreenshot) {
9757
+ sections.push({
9758
+ id: "screenshot",
9759
+ load: () => fetchScreenshot.call(pins, report.id)
9760
+ });
9761
+ }
10043
9762
  }
10044
9763
  const metaEntries = [];
10045
9764
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -10050,14 +9769,14 @@ var reportDetailView = {
10050
9769
  sections.push({ id: "metadata", entries: metaEntries });
10051
9770
  }
10052
9771
  const actions = [];
10053
- if (ctx.registry.archiveReport) {
9772
+ if (ctx.registry.closeReport) {
10054
9773
  actions.push({
10055
- id: "archive",
10056
- label: "Archive",
9774
+ id: "close",
9775
+ label: "Close",
10057
9776
  icon: "archive-x",
10058
9777
  run: () => {
10059
- setArchiveTarget(report.id, () => ctx.pop());
10060
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9778
+ setCloseTarget(report.id, () => ctx.pop());
9779
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10061
9780
  }
10062
9781
  });
10063
9782
  }
@@ -10095,13 +9814,13 @@ function reportToItem(report, ctx) {
10095
9814
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10096
9815
  const kind = ctx.ref?.kind ?? "element";
10097
9816
  const actions = [];
10098
- if (ctx.registry.archiveReport) {
9817
+ if (ctx.registry.closeReport) {
10099
9818
  actions.push({
10100
- id: `archive-${report.id}`,
10101
- label: "Archive",
9819
+ id: `close-${report.id}`,
9820
+ label: "Close",
10102
9821
  perform: () => {
10103
- setArchiveTarget(report.id, () => ctx.pop());
10104
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9822
+ setCloseTarget(report.id, () => ctx.pop());
9823
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10105
9824
  }
10106
9825
  });
10107
9826
  }
@@ -10254,7 +9973,7 @@ function capitalize2(s) {
10254
9973
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10255
9974
  }
10256
9975
  function renderPayloadMarkdown(payload) {
10257
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
9976
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10258
9977
  const ctx = payload.context;
10259
9978
  const lines = [
10260
9979
  `# ${heading}`,
@@ -10323,12 +10042,6 @@ var reportFields = [
10323
10042
  ];
10324
10043
 
10325
10044
  // src/browser/views/built-in/report/view-builder.ts
10326
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10327
- "element",
10328
- "region",
10329
- "widget",
10330
- "primitive"
10331
- ]);
10332
10045
  var KIND_TO_ATTR = new Map(
10333
10046
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10334
10047
  );
@@ -10399,7 +10112,7 @@ function createReportView(config) {
10399
10112
  const cloud = resolveCloud(ctx);
10400
10113
  const extra = extraFields ? extraFields(ctx) : [];
10401
10114
  const fields = [...extra, ...baseFields ?? reportFields];
10402
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10115
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10403
10116
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10404
10117
  const screenshotPromise = isDomBacked ? captureScreenshot({
10405
10118
  target: targetEl ?? void 0,
@@ -10976,7 +10689,7 @@ var pinSettingsView = {
10976
10689
  // src/browser/views/built-in/index.ts
10977
10690
  function buildDefaultViews(shortcut) {
10978
10691
  return [
10979
- archiveReasonView,
10692
+ closeReasonView,
10980
10693
  createCommandPaletteView(shortcut),
10981
10694
  explorePageView,
10982
10695
  componentDetailView,
@@ -11004,6 +10717,27 @@ function buildDefaultViews(shortcut) {
11004
10717
  }
11005
10718
 
11006
10719
  // src/browser/create-uidex.ts
10720
+ function getVisibleEntities() {
10721
+ if (typeof document === "undefined") return [];
10722
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10723
+ const nodes = document.querySelectorAll(selector);
10724
+ const ids = [];
10725
+ const seen = /* @__PURE__ */ new Set();
10726
+ for (const node of nodes) {
10727
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10728
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10729
+ const id = node.getAttribute(attr);
10730
+ if (!id) continue;
10731
+ const key = `${kind}:${id}`;
10732
+ if (!seen.has(key)) {
10733
+ seen.add(key);
10734
+ ids.push(key);
10735
+ }
10736
+ break;
10737
+ }
10738
+ }
10739
+ return ids;
10740
+ }
11007
10741
  function createUidex(options = {}) {
11008
10742
  const registry = createRegistry();
11009
10743
  const inspectorRef = { current: null };
@@ -11038,7 +10772,7 @@ function createUidex(options = {}) {
11038
10772
  const views = createRouter({ session });
11039
10773
  const cloud = options.cloud ?? null;
11040
10774
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
11041
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10775
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
11042
10776
  if (options.defaultViews !== false) {
11043
10777
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
11044
10778
  }
@@ -11048,6 +10782,94 @@ function createUidex(options = {}) {
11048
10782
  let shadowRoot = null;
11049
10783
  const mountCleanup = createCleanupStack();
11050
10784
  let mounted = false;
10785
+ function syncReportsToRegistry() {
10786
+ const layer = pinLayerRef.current;
10787
+ if (!layer) return;
10788
+ const byEntity = /* @__PURE__ */ new Map();
10789
+ for (const pin of layer.getAllPins()) {
10790
+ const cid = pin.entity ?? "";
10791
+ if (!cid) continue;
10792
+ const list = byEntity.get(cid);
10793
+ if (list) list.push(pin);
10794
+ else byEntity.set(cid, [pin]);
10795
+ }
10796
+ for (const [cid, pins] of byEntity) {
10797
+ const ref2 = parseComponentRef(cid);
10798
+ registry.setReports(ref2.kind, ref2.id, pins);
10799
+ }
10800
+ }
10801
+ function setupKeyBindings(root, viewStack) {
10802
+ const bindings = bindShadowKeys({
10803
+ shadowRoot: root,
10804
+ session,
10805
+ getActionsPopup: () => viewStack.getActionsPopup(),
10806
+ getPinLayer: () => pinLayerRef.current,
10807
+ shortcut: options.shortcut
10808
+ });
10809
+ mountCleanup.add(bindings);
10810
+ return bindings;
10811
+ }
10812
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10813
+ if (cloud) {
10814
+ registry.closeReport = async (reportId, status) => {
10815
+ pinLayerRef.current?.removePin(reportId);
10816
+ await cloud.pins.close(
10817
+ reportId,
10818
+ status
10819
+ );
10820
+ syncReportsToRegistry();
10821
+ };
10822
+ }
10823
+ const pinLayer = createPinLayer({
10824
+ container: root,
10825
+ onOpenPinDetail: (componentId, reportId) => {
10826
+ const ref2 = parseComponentRef(componentId);
10827
+ setSelectedReportId(reportId);
10828
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10829
+ session.mode.transition.enterViewing(views.buildStack(entry));
10830
+ },
10831
+ onHoverPin: (anchor, componentId) => {
10832
+ const overlay = overlayRef.current;
10833
+ if (!overlay) return;
10834
+ if (!anchor || !componentId) {
10835
+ overlay.hide();
10836
+ return;
10837
+ }
10838
+ overlay.show(anchor, {
10839
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10840
+ });
10841
+ },
10842
+ onPinsChanged: syncReportsToRegistry
10843
+ });
10844
+ shell.menuBar.setPinLayer(pinLayer);
10845
+ pinLayerRef.current = pinLayer;
10846
+ mountCleanup.add(() => {
10847
+ pinLayer.destroy();
10848
+ pinLayerRef.current = null;
10849
+ registry.closeReport = void 0;
10850
+ });
10851
+ if (cloud) {
10852
+ const detach = pinLayer.attachCloud({
10853
+ cloud,
10854
+ channel,
10855
+ getRoute: getCurrentRoute,
10856
+ getPathname,
10857
+ getVisibleEntities,
10858
+ getMatchMode,
10859
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10860
+ });
10861
+ mountCleanup.add(detach);
10862
+ if (channel && typeof window !== "undefined") {
10863
+ const onRouteChange = () => {
10864
+ const route = getCurrentRoute();
10865
+ channel?.joinRoute(route);
10866
+ void pinLayer.refresh();
10867
+ };
10868
+ const detachRoute = bindRouteChange(onRouteChange);
10869
+ mountCleanup.add(detachRoute);
10870
+ }
10871
+ }
10872
+ }
11051
10873
  function mount(target) {
11052
10874
  if (mounted) return;
11053
10875
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11056,28 +10878,7 @@ function createUidex(options = {}) {
11056
10878
  }
11057
10879
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11058
10880
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11059
- const getVisibleEntities = () => {
11060
- if (typeof document === "undefined") return [];
11061
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11062
- const nodes = document.querySelectorAll(selector);
11063
- const ids = [];
11064
- const seen = /* @__PURE__ */ new Set();
11065
- for (const node of nodes) {
11066
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11067
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11068
- const id = node.getAttribute(attr);
11069
- if (!id) continue;
11070
- const key = `${kind}:${id}`;
11071
- if (!seen.has(key)) {
11072
- seen.add(key);
11073
- ids.push(key);
11074
- }
11075
- break;
11076
- }
11077
- }
11078
- return ids;
11079
- };
11080
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10881
+ const getMatchMode = () => readMode();
11081
10882
  let channel = null;
11082
10883
  if (cloud && options.user) {
11083
10884
  channel = cloud.realtime.connect({
@@ -11098,7 +10899,6 @@ function createUidex(options = {}) {
11098
10899
  onSelect: (match) => {
11099
10900
  const route = views.resolve(match.ref);
11100
10901
  if (!route) return;
11101
- session.select(match.ref);
11102
10902
  const entry = { id: route.view.id, ref: match.ref };
11103
10903
  session.mode.transition.enterViewing(views.buildStack(entry));
11104
10904
  }
@@ -11149,92 +10949,15 @@ function createUidex(options = {}) {
11149
10949
  });
11150
10950
  mountCleanup.add(viewStack);
11151
10951
  if (shadowRoot) {
11152
- keyBindings = bindShadowKeys({
10952
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10953
+ setupPinLayer(
11153
10954
  shadowRoot,
11154
- session,
11155
- getActionsPopup: () => viewStack.getActionsPopup(),
11156
- getPinLayer: () => pinLayerRef.current,
11157
- shortcut: options.shortcut
11158
- });
11159
- mountCleanup.add(keyBindings);
11160
- }
11161
- if (shadowRoot) {
11162
- const syncReportsToRegistry = () => {
11163
- const layer = pinLayerRef.current;
11164
- if (!layer) return;
11165
- const byEntity = /* @__PURE__ */ new Map();
11166
- for (const pin of layer.getAllPins()) {
11167
- const cid = pin.entity ?? "";
11168
- if (!cid) continue;
11169
- const list = byEntity.get(cid);
11170
- if (list) list.push(pin);
11171
- else byEntity.set(cid, [pin]);
11172
- }
11173
- for (const [cid, pins] of byEntity) {
11174
- const ref2 = parseComponentRef(cid);
11175
- registry.setReports(ref2.kind, ref2.id, pins);
11176
- }
11177
- };
11178
- if (cloud) {
11179
- registry.archiveReport = async (reportId, reason) => {
11180
- pinLayerRef.current?.removePin(reportId);
11181
- await cloud.pins.archive(
11182
- reportId,
11183
- reason
11184
- );
11185
- syncReportsToRegistry();
11186
- };
11187
- }
11188
- const pinLayer = createPinLayer({
11189
- container: shadowRoot,
11190
- currentBranch: options.git?.branch ?? null,
11191
- onOpenPinDetail: (componentId, reportId) => {
11192
- const ref2 = parseComponentRef(componentId);
11193
- setSelectedReportId(reportId);
11194
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11195
- session.mode.transition.enterViewing(views.buildStack(entry));
11196
- },
11197
- onHoverPin: (anchor, componentId) => {
11198
- const overlay = overlayRef.current;
11199
- if (!overlay) return;
11200
- if (!anchor || !componentId) {
11201
- overlay.hide();
11202
- return;
11203
- }
11204
- overlay.show(anchor, {
11205
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11206
- });
11207
- },
11208
- onPinsChanged: syncReportsToRegistry
11209
- });
11210
- shell.menuBar.setPinLayer(pinLayer);
11211
- pinLayerRef.current = pinLayer;
11212
- mountCleanup.add(() => {
11213
- pinLayer.destroy();
11214
- pinLayerRef.current = null;
11215
- registry.archiveReport = void 0;
11216
- });
11217
- if (cloud) {
11218
- const detach = pinLayer.attachCloud({
11219
- cloud,
11220
- channel,
11221
- getRoute: getCurrentRoute,
11222
- getPathname,
11223
- getVisibleEntities,
11224
- getMatchMode,
11225
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11226
- });
11227
- mountCleanup.add(detach);
11228
- if (channel && typeof window !== "undefined") {
11229
- const onRouteChange = () => {
11230
- const route = getCurrentRoute();
11231
- channel?.joinRoute(route);
11232
- void pinLayer.refresh();
11233
- };
11234
- const detachRoute = bindRouteChange(onRouteChange);
11235
- mountCleanup.add(detachRoute);
11236
- }
11237
- }
10955
+ shell,
10956
+ channel,
10957
+ getCurrentRoute,
10958
+ getMatchMode,
10959
+ getPathname
10960
+ );
11238
10961
  }
11239
10962
  if (ingest) {
11240
10963
  ingest.start();
@@ -11273,18 +10996,23 @@ function UidexProvider({
11273
10996
  config,
11274
10997
  children
11275
10998
  }) {
11276
- const [ownedInstance] = (0, import_react.useState)(() => {
10999
+ const [owned] = (0, import_react.useState)(() => {
11277
11000
  if (externalInstance) return null;
11278
- const resolvedCloud = cloud !== void 0 ? cloud : projectKey ? (0, import_cloud.cloud)({ projectKey }) : null;
11279
- return createUidex({ ...config, cloud: resolvedCloud, user });
11001
+ const ownedCloud = cloud === void 0 && projectKey && typeof window !== "undefined" ? (0, import_cloud.cloud)({ projectKey }) : null;
11002
+ const resolvedCloud = cloud !== void 0 ? cloud : ownedCloud;
11003
+ return {
11004
+ instance: createUidex({ ...config, cloud: resolvedCloud, user }),
11005
+ cloud: ownedCloud
11006
+ };
11280
11007
  });
11281
- const instance = externalInstance ?? ownedInstance;
11008
+ const instance = externalInstance ?? owned.instance;
11282
11009
  (0, import_react.useEffect)(() => {
11283
- if (!ownedInstance) return;
11010
+ if (!owned) return;
11284
11011
  return () => {
11285
- ownedInstance.unmount();
11012
+ owned.instance.unmount();
11013
+ owned.cloud?.dispose?.();
11286
11014
  };
11287
- }, [ownedInstance]);
11015
+ }, [owned]);
11288
11016
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UidexContext.Provider, { value: instance, children });
11289
11017
  }
11290
11018
 
@@ -11410,6 +11138,7 @@ function UidexDevtools({
11410
11138
  setInstance(inst);
11411
11139
  return () => {
11412
11140
  inst.unmount();
11141
+ cloud?.dispose?.();
11413
11142
  };
11414
11143
  }, [projectKey, user?.id]);
11415
11144
  if (!instance) return null;