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
@@ -94,13 +94,14 @@ function createRegistry() {
94
94
  };
95
95
  const getPatternsForKind = (kind) => {
96
96
  const cached = patternCache.get(kind);
97
- if (cached !== void 0)
98
- return cached;
97
+ if (cached !== void 0) return cached;
99
98
  const patterns = [];
100
99
  for (const [key, entity] of store[kind]) {
101
- if (key.endsWith("*")) {
100
+ if (key.includes("*")) {
101
+ const segments = key.split("*");
102
102
  patterns.push({
103
- prefix: key.slice(0, -1),
103
+ segments,
104
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
104
105
  entity
105
106
  });
106
107
  }
@@ -111,13 +112,25 @@ function createRegistry() {
111
112
  );
112
113
  return patterns;
113
114
  };
115
+ const matchesSegments = (segments, id) => {
116
+ const first = segments[0];
117
+ const last = segments[segments.length - 1];
118
+ if (!id.startsWith(first)) return false;
119
+ let pos = first.length;
120
+ for (let i = 1; i < segments.length - 1; i++) {
121
+ const idx = id.indexOf(segments[i], pos);
122
+ if (idx === -1) return false;
123
+ pos = idx + segments[i].length;
124
+ }
125
+ return id.endsWith(last) && id.length - last.length >= pos;
126
+ };
114
127
  const matchPattern = (kind, id) => {
115
128
  assertEntityKind(kind);
116
129
  const patterns = getPatternsForKind(kind);
117
130
  if (patterns.length === 0) return void 0;
118
131
  let best;
119
132
  for (const entry of patterns) {
120
- if (id.startsWith(entry.prefix) && (best === void 0 || entry.prefix.length > best.prefix.length)) {
133
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
121
134
  best = entry;
122
135
  }
123
136
  }
@@ -428,7 +441,7 @@ function createNetworkCapture(options = {}) {
428
441
 
429
442
  // src/browser/ingest/index.ts
430
443
  function createIngest(options = {}) {
431
- const { session, ...opts } = options;
444
+ const opts = options;
432
445
  const wantConsole = opts.captureConsole !== false;
433
446
  const wantNetwork = opts.captureNetwork !== false;
434
447
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -447,14 +460,12 @@ function createIngest(options = {}) {
447
460
  consoleCapture?.start();
448
461
  networkCapture?.start();
449
462
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
450
- if (active) session?.setIngest(true);
451
463
  }
452
464
  function stop() {
453
465
  if (!active) return;
454
466
  consoleCapture?.stop();
455
467
  networkCapture?.stop();
456
468
  active = false;
457
- session?.setIngest(false);
458
469
  }
459
470
  return {
460
471
  start,
@@ -576,8 +587,7 @@ var COMMAND_PALETTE_ENTRY = {
576
587
  function createModeStore(options) {
577
588
  const { nav, bindings } = options;
578
589
  const store = createStore(() => ({
579
- mode: "idle",
580
- inspectorActive: false
590
+ mode: "idle"
581
591
  }));
582
592
  const transition = {
583
593
  openPalette() {
@@ -586,17 +596,17 @@ function createModeStore(options) {
586
596
  bindings?.destroyInspector?.();
587
597
  }
588
598
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
589
- store.setState({ mode: "palette", inspectorActive: false });
599
+ store.setState({ mode: "palette" });
590
600
  },
591
601
  openInspector() {
592
602
  bindings?.mountInspector?.();
593
603
  nav.nav.clear();
594
- store.setState({ mode: "inspecting", inspectorActive: true });
604
+ store.setState({ mode: "inspecting" });
595
605
  },
596
606
  closeInspector() {
597
607
  bindings?.destroyInspector?.();
598
608
  nav.nav.clear();
599
- store.setState({ mode: "idle", inspectorActive: false });
609
+ store.setState({ mode: "idle" });
600
610
  },
601
611
  toggleInspector() {
602
612
  if (store.getState().mode === "inspecting") {
@@ -611,7 +621,7 @@ function createModeStore(options) {
611
621
  bindings?.destroyInspector?.();
612
622
  }
613
623
  nav.nav.reset(initialStack);
614
- store.setState({ mode: "viewing", inspectorActive: false });
624
+ store.setState({ mode: "viewing" });
615
625
  },
616
626
  dismiss() {
617
627
  const prev = store.getState();
@@ -619,7 +629,7 @@ function createModeStore(options) {
619
629
  bindings?.destroyInspector?.();
620
630
  }
621
631
  nav.nav.clear();
622
- store.setState({ mode: "idle", inspectorActive: false });
632
+ store.setState({ mode: "idle" });
623
633
  },
624
634
  popOrTransition() {
625
635
  const { stack } = nav.getState();
@@ -627,12 +637,12 @@ function createModeStore(options) {
627
637
  nav.nav.pop();
628
638
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
629
639
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
630
- store.setState({ mode: "palette", inspectorActive: false });
640
+ store.setState({ mode: "palette" });
631
641
  } else if (stack.length === 2) {
632
642
  nav.nav.pop();
633
643
  } else {
634
644
  nav.nav.clear();
635
- store.setState({ mode: "idle", inspectorActive: false });
645
+ store.setState({ mode: "idle" });
636
646
  }
637
647
  },
638
648
  pushView(entry) {
@@ -649,7 +659,7 @@ function createModeStore(options) {
649
659
  case "inspecting":
650
660
  bindings?.destroyInspector?.();
651
661
  nav.nav.reset([entry]);
652
- store.setState({ mode: "viewing", inspectorActive: false });
662
+ store.setState({ mode: "viewing" });
653
663
  break;
654
664
  case "palette":
655
665
  case "viewing":
@@ -684,14 +694,6 @@ function createNavigationStore() {
684
694
  store.setState({ stack: s.slice(0, -1) });
685
695
  }
686
696
  },
687
- replace(entry) {
688
- const s = store.getState().stack;
689
- if (s.length === 0) {
690
- store.setState({ stack: [entry] });
691
- } else {
692
- store.setState({ stack: [...s.slice(0, -1), entry] });
693
- }
694
- },
695
697
  clear() {
696
698
  store.setState({ stack: [] });
697
699
  },
@@ -706,14 +708,11 @@ function createNavigationStore() {
706
708
 
707
709
  // src/browser/session/store.ts
708
710
  var defaultSnapshot = {
709
- hover: null,
710
- selection: null,
711
711
  stack: [],
712
712
  pinnedHighlight: null,
713
- inspectorActive: false,
713
+ mode: "idle",
714
714
  theme: "auto",
715
715
  resolvedTheme: "light",
716
- ingestActive: false,
717
716
  user: null
718
717
  };
719
718
  function resolveTheme(preference, detect) {
@@ -764,7 +763,6 @@ function createSession(options = {}) {
764
763
  } else if (highlightMode === "transient") {
765
764
  onUpdateOverlay?.(hlCtx);
766
765
  }
767
- store.setState({ hover: ref2 });
768
766
  },
769
767
  unhover() {
770
768
  if (highlightMode === "transient") {
@@ -774,7 +772,6 @@ function createSession(options = {}) {
774
772
  hlCtx.color = null;
775
773
  onHideOverlay?.();
776
774
  }
777
- store.setState({ hover: null });
778
775
  },
779
776
  pin(ref2) {
780
777
  const pinRef = ref2 ?? hlCtx.ref;
@@ -802,14 +799,11 @@ function createSession(options = {}) {
802
799
  };
803
800
  const store = createStore3(() => ({
804
801
  ...defaultSnapshot,
805
- hover: overrides.hover ?? null,
806
- selection: overrides.selection ?? null,
807
802
  stack: [],
808
803
  pinnedHighlight: null,
809
- inspectorActive: false,
804
+ mode: "idle",
810
805
  theme: initialPref,
811
806
  resolvedTheme: initialResolved,
812
- ingestActive: overrides.ingestActive ?? false,
813
807
  user: overrides.user ?? null
814
808
  }));
815
809
  nav.subscribe(() => {
@@ -819,29 +813,21 @@ function createSession(options = {}) {
819
813
  }
820
814
  });
821
815
  modeStore.subscribe(() => {
822
- const { inspectorActive } = modeStore.getState();
823
- if (store.getState().inspectorActive !== inspectorActive) {
824
- store.setState({ inspectorActive });
816
+ const { mode } = modeStore.getState();
817
+ if (store.getState().mode !== mode) {
818
+ store.setState({ mode });
825
819
  }
826
820
  });
827
821
  const session = store;
828
822
  session.nav = nav;
829
823
  session.mode = modeStore;
830
824
  session.highlight = highlightActions;
831
- session.select = (ref2) => {
832
- if (sameRef(store.getState().selection, ref2)) return;
833
- store.setState({ selection: ref2 });
834
- };
835
825
  session.setTheme = (theme, resolved) => {
836
826
  const state = store.getState();
837
827
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
838
828
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
839
829
  store.setState({ theme, resolvedTheme: nextResolved });
840
830
  };
841
- session.setIngest = (active) => {
842
- if (store.getState().ingestActive === active) return;
843
- store.setState({ ingestActive: active });
844
- };
845
831
  if (initialStack.length > 0) {
846
832
  modeStore.transition.openPalette();
847
833
  for (let i = 1; i < initialStack.length; i++) {
@@ -1154,9 +1140,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1154
1140
  .right-0 {
1155
1141
  right: calc(var(--spacing) * 0);
1156
1142
  }
1157
- .right-2 {
1158
- right: calc(var(--spacing) * 2);
1159
- }
1160
1143
  .bottom-full {
1161
1144
  bottom: 100%;
1162
1145
  }
@@ -1199,9 +1182,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1199
1182
  .mx-2 {
1200
1183
  margin-inline: calc(var(--spacing) * 2);
1201
1184
  }
1202
- .my-1 {
1203
- margin-block: calc(var(--spacing) * 1);
1204
- }
1205
1185
  .ms-auto {
1206
1186
  margin-inline-start: auto;
1207
1187
  }
@@ -1238,9 +1218,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1238
1218
  .inline-flex {
1239
1219
  display: inline-flex;
1240
1220
  }
1241
- .list-item {
1242
- display: list-item;
1243
- }
1244
1221
  .table {
1245
1222
  display: table;
1246
1223
  }
@@ -1248,10 +1225,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1248
1225
  width: calc(var(--spacing) * 2);
1249
1226
  height: calc(var(--spacing) * 2);
1250
1227
  }
1251
- .size-3 {
1252
- width: calc(var(--spacing) * 3);
1253
- height: calc(var(--spacing) * 3);
1254
- }
1255
1228
  .size-3\\.5 {
1256
1229
  width: calc(var(--spacing) * 3.5);
1257
1230
  height: calc(var(--spacing) * 3.5);
@@ -1309,15 +1282,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1309
1282
  .h-\\[26rem\\] {
1310
1283
  height: 26rem;
1311
1284
  }
1312
- .h-auto {
1313
- height: auto;
1314
- }
1315
1285
  .h-full {
1316
1286
  height: 100%;
1317
1287
  }
1318
- .h-px {
1319
- height: 1px;
1320
- }
1321
1288
  .max-h-32 {
1322
1289
  max-height: calc(var(--spacing) * 32);
1323
1290
  }
@@ -1327,9 +1294,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1327
1294
  .min-h-0 {
1328
1295
  min-height: calc(var(--spacing) * 0);
1329
1296
  }
1330
- .min-h-7 {
1331
- min-height: calc(var(--spacing) * 7);
1332
- }
1333
1297
  .min-h-8 {
1334
1298
  min-height: calc(var(--spacing) * 8);
1335
1299
  }
@@ -1354,9 +1318,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1354
1318
  .w-56 {
1355
1319
  width: calc(var(--spacing) * 56);
1356
1320
  }
1357
- .w-auto {
1358
- width: auto;
1359
- }
1360
1321
  .w-full {
1361
1322
  width: 100%;
1362
1323
  }
@@ -1431,9 +1392,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1431
1392
  .animate-spin {
1432
1393
  animation: var(--animate-spin);
1433
1394
  }
1434
- .cursor-default {
1435
- cursor: default;
1436
- }
1437
1395
  .cursor-pointer {
1438
1396
  cursor: pointer;
1439
1397
  }
@@ -1443,9 +1401,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1443
1401
  .scroll-py-2 {
1444
1402
  scroll-padding-block: calc(var(--spacing) * 2);
1445
1403
  }
1446
- .list-none {
1447
- list-style-type: none;
1448
- }
1449
1404
  .appearance-none {
1450
1405
  appearance: none;
1451
1406
  }
@@ -1467,9 +1422,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1467
1422
  .justify-center {
1468
1423
  justify-content: center;
1469
1424
  }
1470
- .justify-start {
1471
- justify-content: flex-start;
1472
- }
1473
1425
  .gap-0 {
1474
1426
  gap: calc(var(--spacing) * 0);
1475
1427
  }
@@ -1494,9 +1446,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1494
1446
  .gap-6 {
1495
1447
  gap: calc(var(--spacing) * 6);
1496
1448
  }
1497
- .self-start {
1498
- align-self: flex-start;
1499
- }
1500
1449
  .truncate {
1501
1450
  overflow: hidden;
1502
1451
  text-overflow: ellipsis;
@@ -1711,9 +1660,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1711
1660
  .p-6 {
1712
1661
  padding: calc(var(--spacing) * 6);
1713
1662
  }
1714
- .px-0 {
1715
- padding-inline: calc(var(--spacing) * 0);
1716
- }
1717
1663
  .px-1 {
1718
1664
  padding-inline: calc(var(--spacing) * 1);
1719
1665
  }
@@ -1738,9 +1684,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1738
1684
  .px-6 {
1739
1685
  padding-inline: calc(var(--spacing) * 6);
1740
1686
  }
1741
- .py-0 {
1742
- padding-block: calc(var(--spacing) * 0);
1743
- }
1744
1687
  .py-1 {
1745
1688
  padding-block: calc(var(--spacing) * 1);
1746
1689
  }
@@ -1813,9 +1756,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1813
1756
  .text-\\[9px\\] {
1814
1757
  font-size: 9px;
1815
1758
  }
1816
- .text-\\[10px\\] {
1817
- font-size: 10px;
1818
- }
1819
1759
  .text-\\[11px\\] {
1820
1760
  font-size: 11px;
1821
1761
  }
@@ -2055,10 +1995,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2055
1995
  outline-style: var(--tw-outline-style);
2056
1996
  outline-width: 1px;
2057
1997
  }
2058
- .blur {
2059
- --tw-blur: blur(8px);
2060
- 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,);
2061
- }
2062
1998
  .filter {
2063
1999
  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,);
2064
2000
  }
@@ -2224,25 +2160,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2224
2160
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2225
2161
  }
2226
2162
  }
2227
- .focus-within\\:border-ring {
2228
- &:focus-within {
2229
- border-color: var(--ring);
2230
- }
2231
- }
2232
- .focus-within\\:ring-\\[3px\\] {
2233
- &:focus-within {
2234
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2235
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2236
- }
2237
- }
2238
- .focus-within\\:ring-ring\\/30 {
2239
- &:focus-within {
2240
- --tw-ring-color: var(--ring);
2241
- @supports (color: color-mix(in lab, red, red)) {
2242
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2243
- }
2244
- }
2245
- }
2246
2163
  .hover\\:border-destructive\\/30 {
2247
2164
  &:hover {
2248
2165
  @media (hover: hover) {
@@ -2386,11 +2303,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2386
2303
  pointer-events: none;
2387
2304
  }
2388
2305
  }
2389
- .disabled\\:cursor-not-allowed {
2390
- &:disabled {
2391
- cursor: not-allowed;
2392
- }
2393
- }
2394
2306
  .disabled\\:opacity-50 {
2395
2307
  &:disabled {
2396
2308
  opacity: 50%;
@@ -2401,11 +2313,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2401
2313
  opacity: 60%;
2402
2314
  }
2403
2315
  }
2404
- .has-disabled\\:opacity-60 {
2405
- &:has(*:disabled) {
2406
- opacity: 60%;
2407
- }
2408
- }
2409
2316
  .aria-invalid\\:border-destructive\\/40 {
2410
2317
  &[aria-invalid="true"] {
2411
2318
  border-color: var(--destructive);
@@ -2812,32 +2719,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2812
2719
  height: calc(var(--spacing) * 4.5);
2813
2720
  }
2814
2721
  }
2815
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2816
- &>svg {
2817
- pointer-events: none;
2818
- }
2819
- }
2820
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2821
- &>svg {
2822
- margin-inline: calc(var(--spacing) * -0.5);
2823
- }
2824
- }
2825
- .\\[\\&\\>svg\\]\\:shrink-0 {
2826
- &>svg {
2827
- flex-shrink: 0;
2828
- }
2829
- }
2830
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2831
- &>svg:not([class*='opacity-']) {
2832
- opacity: 80%;
2833
- }
2834
- }
2835
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2836
- &>svg:not([class*='size-']) {
2837
- width: calc(var(--spacing) * 4);
2838
- height: calc(var(--spacing) * 4);
2839
- }
2840
- }
2841
2722
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2842
2723
  [data-kbd-nav] & {
2843
2724
  &:focus-within {
@@ -3399,18 +3280,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3399
3280
 
3400
3281
  // src/browser/surface/constants.ts
3401
3282
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3402
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3403
3283
  var Z_BASE = 2147483630;
3404
3284
  var Z_OVERLAY = 2147483635;
3405
3285
  var Z_PIN_LAYER = 2147483640;
3406
3286
  var Z_CHROME = 2147483645;
3407
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3287
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3408
3288
  var UIDEX_ATTR_TO_KIND = [
3409
3289
  ["data-uidex", "element"],
3410
3290
  ["data-uidex-region", "region"],
3411
3291
  ["data-uidex-widget", "widget"],
3412
3292
  ["data-uidex-primitive", "primitive"]
3413
3293
  ];
3294
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3295
+ "element",
3296
+ "region",
3297
+ "widget",
3298
+ "primitive"
3299
+ ]);
3414
3300
 
3415
3301
  // src/browser/surface/host.ts
3416
3302
  function createSurfaceHost(options) {
@@ -3700,7 +3586,6 @@ function createInspector(options) {
3700
3586
  e.preventDefault();
3701
3587
  e.stopPropagation();
3702
3588
  const match = stack[layerIndex];
3703
- session.select(match.ref);
3704
3589
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3705
3590
  };
3706
3591
  const onContextMenu = (e) => {
@@ -3749,8 +3634,6 @@ function createInspector(options) {
3749
3634
 
3750
3635
  // src/browser/surface/menu-bar.ts
3751
3636
  import {
3752
- ChevronLeft,
3753
- ChevronRight,
3754
3637
  Command,
3755
3638
  Highlighter,
3756
3639
  MapPin,
@@ -4028,49 +3911,12 @@ function createMenuBar(options) {
4028
3911
  },
4029
3912
  pinIcon
4030
3913
  );
4031
- const commitCycler = el("div", {
4032
- class: "relative z-1 inline-flex items-center gap-0.5",
4033
- attrs: { "data-uidex-menubar-commit-cycler": "" }
4034
- });
4035
- commitCycler.hidden = true;
4036
- const prevIcon = createLucideElement2(ChevronLeft);
4037
- prevIcon.setAttribute("class", "size-3");
4038
- prevIcon.setAttribute("aria-hidden", "true");
4039
- const prevBtn = el(
4040
- "button",
4041
- {
4042
- class: BUTTON_CLASS,
4043
- attrs: { type: "button", "aria-label": "Previous commit" },
4044
- style: { width: "18px", height: "18px" }
4045
- },
4046
- prevIcon
4047
- );
4048
- const commitLabel = el("span", {
4049
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4050
- attrs: { "data-uidex-menubar-commit-label": "" }
4051
- });
4052
- const nextIcon = createLucideElement2(ChevronRight);
4053
- nextIcon.setAttribute("class", "size-3");
4054
- nextIcon.setAttribute("aria-hidden", "true");
4055
- const nextBtn = el(
4056
- "button",
4057
- {
4058
- class: BUTTON_CLASS,
4059
- attrs: { type: "button", "aria-label": "Next commit" },
4060
- style: { width: "18px", height: "18px" }
4061
- },
4062
- nextIcon
4063
- );
4064
- commitCycler.appendChild(prevBtn);
4065
- commitCycler.appendChild(commitLabel);
4066
- commitCycler.appendChild(nextBtn);
4067
3914
  const pinWrapper = el("div", {
4068
3915
  class: "relative z-1 inline-flex items-center gap-0.5",
4069
3916
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4070
3917
  });
4071
3918
  pinWrapper.hidden = true;
4072
3919
  pinWrapper.appendChild(pinBtn);
4073
- pinWrapper.appendChild(commitCycler);
4074
3920
  root.appendChild(pinWrapper);
4075
3921
  const updatePinUI = () => {
4076
3922
  if (!activePinLayer) {
@@ -4078,16 +3924,7 @@ function createMenuBar(options) {
4078
3924
  return;
4079
3925
  }
4080
3926
  const pinsVisible = activePinLayer.visible;
4081
- const state = activePinLayer.filterState;
4082
- const hasCommits = state.commits.length > 0;
4083
3927
  pinWrapper.hidden = false;
4084
- commitCycler.hidden = !pinsVisible || !hasCommits;
4085
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4086
- commitLabel.textContent = `all (${state.commits.length})`;
4087
- } else {
4088
- const sha = state.commits[state.commitIndex] ?? "";
4089
- commitLabel.textContent = sha.slice(0, 7);
4090
- }
4091
3928
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4092
3929
  };
4093
3930
  pinBtn.addEventListener("click", (e) => {
@@ -4096,14 +3933,6 @@ function createMenuBar(options) {
4096
3933
  activePinLayer.setVisible(!activePinLayer.visible);
4097
3934
  }
4098
3935
  });
4099
- prevBtn.addEventListener("click", (e) => {
4100
- e.stopPropagation();
4101
- activePinLayer?.prevCommit();
4102
- });
4103
- nextBtn.addEventListener("click", (e) => {
4104
- e.stopPropagation();
4105
- activePinLayer?.nextCommit();
4106
- });
4107
3936
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4108
3937
  const presenceIcon = createLucideElement2(Users);
4109
3938
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4197,7 +4026,7 @@ function createMenuBar(options) {
4197
4026
  container.appendChild(root);
4198
4027
  const syncButtonStates = () => {
4199
4028
  const state = session.getState();
4200
- const inspectActive = state.inspectorActive;
4029
+ const inspectActive = state.mode === "inspecting";
4201
4030
  inspectBtn.setAttribute(
4202
4031
  "data-uidex-menubar-inspect-active",
4203
4032
  inspectActive ? "true" : "false"
@@ -4316,6 +4145,49 @@ function createMenuBar(options) {
4316
4145
  };
4317
4146
  }
4318
4147
 
4148
+ // src/browser/internal/repositioner.ts
4149
+ function createRepositioner(onReflow) {
4150
+ let rafId = null;
4151
+ let attached = false;
4152
+ const schedule = () => {
4153
+ if (rafId !== null) return;
4154
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4155
+ rafId = null;
4156
+ onReflow();
4157
+ }) : setTimeout(() => {
4158
+ rafId = null;
4159
+ onReflow();
4160
+ }, 0);
4161
+ };
4162
+ const cancel = () => {
4163
+ if (rafId === null) return;
4164
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4165
+ else clearTimeout(rafId);
4166
+ rafId = null;
4167
+ };
4168
+ const onScroll = () => schedule();
4169
+ const onResize = () => schedule();
4170
+ const attach = () => {
4171
+ if (attached) return;
4172
+ attached = true;
4173
+ window.addEventListener("resize", onResize);
4174
+ window.addEventListener("scroll", onScroll, {
4175
+ capture: true,
4176
+ passive: true
4177
+ });
4178
+ };
4179
+ const detach = () => {
4180
+ if (!attached) return;
4181
+ attached = false;
4182
+ window.removeEventListener("resize", onResize);
4183
+ window.removeEventListener("scroll", onScroll, {
4184
+ capture: true
4185
+ });
4186
+ cancel();
4187
+ };
4188
+ return { schedule, cancel, attach, detach };
4189
+ }
4190
+
4319
4191
  // src/browser/surface/overlay.ts
4320
4192
  var DEFAULT_COLOR = "#34d399";
4321
4193
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4383,44 +4255,7 @@ function createOverlay(deps) {
4383
4255
  fillOpacity: DEFAULT_FILL_OPACITY,
4384
4256
  backdrop: false
4385
4257
  };
4386
- let rafId = null;
4387
- let attached = false;
4388
- const schedule = () => {
4389
- if (rafId !== null) return;
4390
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4391
- rafId = null;
4392
- updatePosition();
4393
- }) : setTimeout(() => {
4394
- rafId = null;
4395
- updatePosition();
4396
- }, 0);
4397
- };
4398
- const cancelSchedule = () => {
4399
- if (rafId === null) return;
4400
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4401
- else clearTimeout(rafId);
4402
- rafId = null;
4403
- };
4404
- const onScroll = () => schedule();
4405
- const onResize = () => schedule();
4406
- const attach = () => {
4407
- if (attached) return;
4408
- attached = true;
4409
- window.addEventListener("resize", onResize);
4410
- window.addEventListener("scroll", onScroll, {
4411
- capture: true,
4412
- passive: true
4413
- });
4414
- };
4415
- const detach = () => {
4416
- if (!attached) return;
4417
- attached = false;
4418
- window.removeEventListener("resize", onResize);
4419
- window.removeEventListener("scroll", onScroll, {
4420
- capture: true
4421
- });
4422
- cancelSchedule();
4423
- };
4258
+ const repositioner = createRepositioner(() => updatePosition());
4424
4259
  function updatePosition() {
4425
4260
  if (!target) return;
4426
4261
  const rect = target.getBoundingClientRect();
@@ -4484,16 +4319,16 @@ function createOverlay(deps) {
4484
4319
  box.offsetHeight;
4485
4320
  }
4486
4321
  box.style.opacity = "1";
4487
- attach();
4322
+ repositioner.attach();
4488
4323
  },
4489
4324
  hide() {
4490
4325
  target = null;
4491
4326
  box.style.opacity = "0";
4492
4327
  backdrop.style.opacity = "0";
4493
- detach();
4328
+ repositioner.detach();
4494
4329
  },
4495
4330
  destroy() {
4496
- detach();
4331
+ repositioner.detach();
4497
4332
  box.remove();
4498
4333
  backdrop.remove();
4499
4334
  target = null;
@@ -4610,8 +4445,7 @@ function createSurfaceShell(options) {
4610
4445
  const overlay = createOverlay({ container: host.shadowRoot });
4611
4446
  cleanup.add(overlay);
4612
4447
  const tooltip = createCursorTooltip({
4613
- container: host.chromeEl,
4614
- session: options.session
4448
+ container: host.chromeEl
4615
4449
  });
4616
4450
  cleanup.add(tooltip);
4617
4451
  const afterHover = options.inspector?.onAfterHover;
@@ -4803,9 +4637,6 @@ function createPinLayer(options) {
4803
4637
  const seenIds = /* @__PURE__ */ new Set();
4804
4638
  const byComp = /* @__PURE__ */ new Map();
4805
4639
  const indicators = /* @__PURE__ */ new Map();
4806
- let filter = { branch: null, commit: null };
4807
- let commits = [];
4808
- let commitIndex = -1;
4809
4640
  const filterCbs = /* @__PURE__ */ new Set();
4810
4641
  const notifyFilter = () => {
4811
4642
  for (const cb of filterCbs) cb();
@@ -4820,8 +4651,6 @@ function createPinLayer(options) {
4820
4651
  }
4821
4652
  };
4822
4653
  const rerender = () => {
4823
- commits = [];
4824
- commitIndex = -1;
4825
4654
  rebuildFiltered();
4826
4655
  for (const id of Array.from(indicators.keys())) {
4827
4656
  if (!byComp.has(id)) removeIndicator(id);
@@ -4830,45 +4659,8 @@ function createPinLayer(options) {
4830
4659
  notifyFilter();
4831
4660
  onPinsChanged?.();
4832
4661
  };
4833
- let rafId = null;
4834
- let winAttached = false;
4835
4662
  let obs = null;
4836
- const schedulePos = () => {
4837
- if (rafId !== null) return;
4838
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4839
- rafId = null;
4840
- posAll();
4841
- }) : setTimeout(() => {
4842
- rafId = null;
4843
- posAll();
4844
- }, 0);
4845
- };
4846
- const cancelPos = () => {
4847
- if (rafId === null) return;
4848
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4849
- else clearTimeout(rafId);
4850
- rafId = null;
4851
- };
4852
- const onScroll = () => schedulePos();
4853
- const onResize = () => schedulePos();
4854
- const attachWin = () => {
4855
- if (winAttached) return;
4856
- winAttached = true;
4857
- window.addEventListener("scroll", onScroll, {
4858
- capture: true,
4859
- passive: true
4860
- });
4861
- window.addEventListener("resize", onResize);
4862
- };
4863
- const detachWin = () => {
4864
- if (!winAttached) return;
4865
- winAttached = false;
4866
- window.removeEventListener("scroll", onScroll, {
4867
- capture: true
4868
- });
4869
- window.removeEventListener("resize", onResize);
4870
- cancelPos();
4871
- };
4663
+ const repositioner = createRepositioner(() => posAll());
4872
4664
  const attachObs = () => {
4873
4665
  if (obs || typeof MutationObserver === "undefined") return;
4874
4666
  obs = new MutationObserver((recs) => {
@@ -4892,7 +4684,7 @@ function createPinLayer(options) {
4892
4684
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4893
4685
  changed = true;
4894
4686
  }
4895
- if (changed) schedulePos();
4687
+ if (changed) repositioner.schedule();
4896
4688
  };
4897
4689
  const expand = (st) => {
4898
4690
  if (st.expanded) return;
@@ -5134,7 +4926,7 @@ function createPinLayer(options) {
5134
4926
  if (!st) {
5135
4927
  st = buildIndicator(componentId);
5136
4928
  indicators.set(componentId, st);
5137
- attachWin();
4929
+ repositioner.attach();
5138
4930
  attachObs();
5139
4931
  }
5140
4932
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5148,7 +4940,7 @@ function createPinLayer(options) {
5148
4940
  st.wrap.remove();
5149
4941
  indicators.delete(componentId);
5150
4942
  if (indicators.size === 0) {
5151
- detachWin();
4943
+ repositioner.detach();
5152
4944
  detachObs();
5153
4945
  }
5154
4946
  };
@@ -5183,17 +4975,22 @@ function createPinLayer(options) {
5183
4975
  seenIds.clear();
5184
4976
  byComp.clear();
5185
4977
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5186
- commits = [];
5187
- commitIndex = -1;
5188
4978
  notifyFilter();
5189
4979
  },
5190
4980
  getPinsForElement: (id) => byComp.get(id) ?? [],
5191
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5192
4981
  getAllPins: () => allPins.slice(),
5193
4982
  attachChannel(channel) {
5194
- return channel.onPin((pin) => {
4983
+ const offPin = channel.onPin((pin) => {
5195
4984
  layer.addPin(pin);
5196
4985
  });
4986
+ const offArchived = channel.onPinArchived?.((reportId) => {
4987
+ layer.removePin(reportId);
4988
+ }) ?? (() => {
4989
+ });
4990
+ return () => {
4991
+ offPin();
4992
+ offArchived();
4993
+ };
5197
4994
  },
5198
4995
  attachCloud(opts) {
5199
4996
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5224,39 +5021,6 @@ function createPinLayer(options) {
5224
5021
  async refresh() {
5225
5022
  if (activeRefresh) await activeRefresh();
5226
5023
  },
5227
- get filterState() {
5228
- return {
5229
- branch: filter.branch,
5230
- commit: filter.commit,
5231
- commits,
5232
- commitIndex
5233
- };
5234
- },
5235
- setFilter(next) {
5236
- filter = {
5237
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5238
- commit: next.commit !== void 0 ? next.commit : filter.commit
5239
- };
5240
- rerender();
5241
- },
5242
- nextCommit() {
5243
- if (!commits.length) return;
5244
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5245
- filter = {
5246
- ...filter,
5247
- commit: commitIndex === -1 ? null : commits[commitIndex]
5248
- };
5249
- rerender();
5250
- },
5251
- prevCommit() {
5252
- if (!commits.length) return;
5253
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5254
- filter = {
5255
- ...filter,
5256
- commit: commitIndex === -1 ? null : commits[commitIndex]
5257
- };
5258
- rerender();
5259
- },
5260
5024
  onFilterChange(cb) {
5261
5025
  filterCbs.add(cb);
5262
5026
  return () => {
@@ -5275,7 +5039,7 @@ function createPinLayer(options) {
5275
5039
  },
5276
5040
  destroy() {
5277
5041
  layer.clear();
5278
- detachWin();
5042
+ repositioner.detach();
5279
5043
  detachObs();
5280
5044
  layerEl.remove();
5281
5045
  activeRefresh = null;
@@ -5318,9 +5082,11 @@ function createRouter(options) {
5318
5082
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5319
5083
  throw new ViewValidationError("View must have a non-empty string id");
5320
5084
  }
5321
- if (typeof view.surface !== "function") {
5085
+ const hasSurface = typeof view.surface === "function";
5086
+ const hasRender = typeof view.render === "function";
5087
+ if (!hasSurface && !hasRender) {
5322
5088
  throw new ViewValidationError(
5323
- `View ${view.id}: 'surface' must be a function`
5089
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5324
5090
  );
5325
5091
  }
5326
5092
  if (!view.matches && !view.palette) {
@@ -5378,7 +5144,6 @@ function createRouter(options) {
5378
5144
  if (idx >= 0) recentRefs.splice(idx, 1);
5379
5145
  recentRefs.unshift(ref2);
5380
5146
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5381
- options.session.select(ref2);
5382
5147
  const entry = { id: match.view.id, ref: ref2 };
5383
5148
  const { mode } = options.session.mode.getState();
5384
5149
  if (mode === "idle" || mode === "inspecting") {
@@ -5433,57 +5198,7 @@ function detectDev() {
5433
5198
 
5434
5199
  // src/browser/internal/lit.ts
5435
5200
  import { html, svg, render, nothing } from "lit-html";
5436
- import { repeat } from "lit-html/directives/repeat.js";
5437
5201
  import { ref, createRef } from "lit-html/directives/ref.js";
5438
- import { classMap } from "lit-html/directives/class-map.js";
5439
-
5440
- // src/browser/internal/apply-props.ts
5441
- function applyProps(node, props) {
5442
- const removers = [];
5443
- for (const [key, rawValue] of Object.entries(props)) {
5444
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5445
- if (key.startsWith("on") && typeof rawValue === "function") {
5446
- const eventName = key.slice(2).toLowerCase();
5447
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5448
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5449
- const listener = rawValue;
5450
- node.addEventListener(effectiveEvent, listener);
5451
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5452
- continue;
5453
- }
5454
- if (rawValue === void 0 || rawValue === null) {
5455
- node.removeAttribute(key);
5456
- continue;
5457
- }
5458
- if (typeof rawValue === "boolean") {
5459
- if (rawValue) node.setAttribute(key, "");
5460
- else node.removeAttribute(key);
5461
- continue;
5462
- }
5463
- if (key === "style" && typeof rawValue === "object") {
5464
- Object.assign(
5465
- node.style,
5466
- rawValue
5467
- );
5468
- continue;
5469
- }
5470
- if (key === "className" || key === "class") {
5471
- node.setAttribute("class", String(rawValue));
5472
- continue;
5473
- }
5474
- if (key === "htmlFor") {
5475
- node.setAttribute("for", String(rawValue));
5476
- continue;
5477
- }
5478
- if (key === "tabIndex") {
5479
- ;
5480
- node.tabIndex = Number(rawValue);
5481
- continue;
5482
- }
5483
- node.setAttribute(key, String(rawValue));
5484
- }
5485
- return () => removers.forEach((fn) => fn());
5486
- }
5487
5202
 
5488
5203
  // src/browser/internal/lit-icon.ts
5489
5204
  import { noChange } from "lit-html";
@@ -5521,13 +5236,23 @@ var LucideIconDirective = class extends Directive {
5521
5236
  };
5522
5237
  var icon = directive(LucideIconDirective);
5523
5238
 
5524
- // src/browser/ui/cva.ts
5525
- function cva(base, config = {}) {
5526
- return (props = {}) => {
5527
- const out = [base];
5528
- const variants = config.variants;
5529
- if (!variants) return out;
5530
- for (const key of Object.keys(variants)) {
5239
+ // src/browser/views/primitives/chip.ts
5240
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5241
+ function createChip(options = {}, children = []) {
5242
+ const { class: extra, ...rest } = options;
5243
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5244
+ }
5245
+
5246
+ // src/browser/views/primitives/kind-icon.ts
5247
+ import { createElement as createLucideElement4 } from "lucide";
5248
+
5249
+ // src/browser/ui/cva.ts
5250
+ function cva(base, config = {}) {
5251
+ return (props = {}) => {
5252
+ const out = [base];
5253
+ const variants = config.variants;
5254
+ if (!variants) return out;
5255
+ for (const key of Object.keys(variants)) {
5531
5256
  const picked = props[key] ?? config.defaultVariants?.[key];
5532
5257
  if (picked === void 0) continue;
5533
5258
  const cls = variants[key][picked];
@@ -5568,16 +5293,6 @@ function badgeTpl(content, options = {}) {
5568
5293
  `;
5569
5294
  }
5570
5295
 
5571
- // src/browser/views/primitives/chip.ts
5572
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5573
- function createChip(options = {}, children = []) {
5574
- const { class: extra, ...rest } = options;
5575
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5576
- }
5577
-
5578
- // src/browser/views/primitives/kind-icon.ts
5579
- import { createElement as createLucideElement4 } from "lucide";
5580
-
5581
5296
  // src/browser/views/primitives/icon-tile.ts
5582
5297
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5583
5298
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6085,6 +5800,54 @@ function createScrollArea(props = {}) {
6085
5800
  );
6086
5801
  }
6087
5802
 
5803
+ // src/browser/internal/apply-props.ts
5804
+ function applyProps(node, props) {
5805
+ const removers = [];
5806
+ for (const [key, rawValue] of Object.entries(props)) {
5807
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5808
+ if (key.startsWith("on") && typeof rawValue === "function") {
5809
+ const eventName = key.slice(2).toLowerCase();
5810
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5811
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5812
+ const listener = rawValue;
5813
+ node.addEventListener(effectiveEvent, listener);
5814
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5815
+ continue;
5816
+ }
5817
+ if (rawValue === void 0 || rawValue === null) {
5818
+ node.removeAttribute(key);
5819
+ continue;
5820
+ }
5821
+ if (typeof rawValue === "boolean") {
5822
+ if (rawValue) node.setAttribute(key, "");
5823
+ else node.removeAttribute(key);
5824
+ continue;
5825
+ }
5826
+ if (key === "style" && typeof rawValue === "object") {
5827
+ Object.assign(
5828
+ node.style,
5829
+ rawValue
5830
+ );
5831
+ continue;
5832
+ }
5833
+ if (key === "className" || key === "class") {
5834
+ node.setAttribute("class", String(rawValue));
5835
+ continue;
5836
+ }
5837
+ if (key === "htmlFor") {
5838
+ node.setAttribute("for", String(rawValue));
5839
+ continue;
5840
+ }
5841
+ if (key === "tabIndex") {
5842
+ ;
5843
+ node.tabIndex = Number(rawValue);
5844
+ continue;
5845
+ }
5846
+ node.setAttribute(key, String(rawValue));
5847
+ }
5848
+ return () => removers.forEach((fn) => fn());
5849
+ }
5850
+
6088
5851
  // src/browser/views/builder/spread-props.ts
6089
5852
  function spreadProps(node, props) {
6090
5853
  return applyProps(node, props);
@@ -6104,21 +5867,6 @@ function createPersistentSpreads() {
6104
5867
  };
6105
5868
  }
6106
5869
 
6107
- // src/browser/views/render/detail.ts
6108
- import {
6109
- ArchiveX,
6110
- Camera,
6111
- ChevronDown,
6112
- Copy,
6113
- Highlighter as Highlighter2,
6114
- Inbox,
6115
- MessageCircleWarning,
6116
- StickyNote as StickyNote2,
6117
- TicketPlus,
6118
- View,
6119
- createElement as createLucideElement5
6120
- } from "lucide";
6121
-
6122
5870
  // src/browser/internal/arrow-nav.ts
6123
5871
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6124
5872
  function bindArrowNav(options) {
@@ -6190,30 +5938,6 @@ function focusItem(items, idx) {
6190
5938
  items[idx].focus();
6191
5939
  }
6192
5940
 
6193
- // src/browser/views/builder/filter.ts
6194
- function normalizeQuery(query) {
6195
- return query.trim().toLowerCase();
6196
- }
6197
- function matchesQuery(haystack, query) {
6198
- const q = normalizeQuery(query);
6199
- if (!q) return true;
6200
- return haystack.toLowerCase().includes(q);
6201
- }
6202
- function filterEntities(entities, query) {
6203
- const q = normalizeQuery(query);
6204
- if (!q) return entities;
6205
- return entities.filter((e) => {
6206
- const name = displayName(e).toLowerCase();
6207
- const id = entityKey(e).toLowerCase();
6208
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6209
- });
6210
- }
6211
- function filterIds(ids, query) {
6212
- const q = normalizeQuery(query);
6213
- if (!q) return ids;
6214
- return ids.filter((id) => id.toLowerCase().includes(q));
6215
- }
6216
-
6217
5941
  // src/browser/views/labels.ts
6218
5942
  var SECTION_LABELS = {
6219
5943
  acceptance: "Acceptance criteria",
@@ -6238,171 +5962,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6238
5962
  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";
6239
5963
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6240
5964
 
6241
- // src/browser/ui/button.ts
6242
- 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";
6243
- var buttonVariants = cva(buttonBase, {
6244
- defaultVariants: { size: "default", variant: "default" },
6245
- variants: {
6246
- size: {
6247
- default: "h-8 px-3",
6248
- sm: "h-7 gap-1.5 px-2.5",
6249
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6250
- lg: "h-9 px-3.5",
6251
- xl: "h-10 px-4 text-base",
6252
- icon: "size-8",
6253
- "icon-sm": "size-7",
6254
- "icon-lg": "size-9",
6255
- "icon-xs": "size-6 rounded-md"
6256
- },
6257
- variant: {
6258
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6259
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6260
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6261
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6262
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6263
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6264
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6265
- }
6266
- }
6267
- });
6268
-
6269
- // src/browser/views/primitives/entity-presence.ts
6270
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6271
- "element",
6272
- "region",
6273
- "widget",
6274
- "primitive"
6275
- ]);
6276
- var TOUCH_RESOLVE_KINDS = [
6277
- "element",
6278
- "widget",
6279
- "region",
6280
- "primitive"
6281
- ];
6282
- function isAbsentFromPage(ref2, registry) {
6283
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6284
- return !resolveEntityElement(ref2);
6285
- }
6286
- if (ref2.kind === "flow" && registry) {
6287
- const flow = registry.get("flow", ref2.id);
6288
- if (!flow) return true;
6289
- for (const touchId of flow.touches) {
6290
- for (const kind of TOUCH_RESOLVE_KINDS) {
6291
- const entity = registry.get(kind, touchId);
6292
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6293
- }
6294
- }
6295
- return true;
6296
- }
6297
- return false;
6298
- }
6299
-
6300
- // src/browser/ui/kbd.ts
6301
- 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";
6302
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6303
- function createCommandShortcut(options = {}, children = []) {
6304
- const { class: extra, attrs, ...rest } = options;
6305
- return el(
6306
- "kbd",
6307
- {
6308
- ...rest,
6309
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6310
- attrs: { "data-slot": "command-shortcut", ...attrs }
6311
- },
6312
- children
6313
- );
6314
- }
6315
- function kbdTpl(text, className) {
6316
- return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6317
- >${text}</kbd
6318
- >`;
6319
- }
6320
- function commandShortcutTpl(text, className) {
6321
- return html`<kbd
6322
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6323
- data-slot="command-shortcut"
6324
- >${text}</kbd
6325
- >`;
6326
- }
6327
-
6328
- // src/browser/views/primitives/row.ts
6329
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6330
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6331
- function fillRowWithHandle(host, content) {
6332
- if (content.leading) host.append(content.leading);
6333
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6334
- host.append(label);
6335
- if (content.subtitle) {
6336
- host.append(
6337
- el("span", {
6338
- class: SUBTITLE_CLASS,
6339
- attrs: { "data-uidex-row-subtitle": "" },
6340
- text: content.subtitle
6341
- })
6342
- );
6343
- }
6344
- if (content.trailing != null) {
6345
- host.append(
6346
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6347
- );
6348
- }
6349
- return { label };
6350
- }
6351
- function rowTpl(content) {
6352
- return html`
6353
- ${content.leading ?? nothing}
6354
- <span class=${LABEL_CLASS}>${content.label}</span>
6355
- ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6356
- >${content.subtitle}</span
6357
- >` : nothing}
6358
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6359
- `;
6360
- }
6361
-
6362
- // src/browser/views/primitives/entity-link.ts
6363
- var ACTION_CLASS = cn(
6364
- LIST_ITEM_CLASS,
6365
- LIST_ITEM_INTERACTIVE_CLASS,
6366
- "w-full cursor-pointer text-left font-normal"
6367
- );
6368
- function entityLinkTpl(options) {
6369
- const { ctx, target, label, leading, class: extraClass } = options;
6370
- const absent = isAbsentFromPage(target);
6371
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6372
- const onClick = () => ctx.views.navigate(target);
6373
- const preview = () => {
6374
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6375
- };
6376
- const restoreParent = () => {
6377
- if (ctx.ref) {
6378
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6379
- } else {
6380
- ctx.highlight.hide();
6381
- }
6382
- };
6383
- return html`
6384
- <button
6385
- type="button"
6386
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6387
- data-slot="list-item-button"
6388
- data-uidex-entity-link
6389
- data-uidex-ref-kind=${target.kind}
6390
- data-uidex-ref-id=${target.id}
6391
- title=${absent ? "Not on this page" : ""}
6392
- @click=${onClick}
6393
- @mouseenter=${preview}
6394
- @mouseleave=${restoreParent}
6395
- @focus=${preview}
6396
- @blur=${restoreParent}
6397
- >
6398
- ${rowTpl({
6399
- leading,
6400
- label: resolvedLabel
6401
- })}
6402
- </button>
6403
- `;
6404
- }
6405
-
6406
5965
  // src/browser/views/primitives/text.ts
6407
5966
  var MUTED_CLASS = "text-sm text-muted-foreground";
6408
5967
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6414,7 +5973,20 @@ function headingTpl(text, className) {
6414
5973
  return html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6415
5974
  }
6416
5975
 
6417
- // src/browser/views/render/detail.ts
5976
+ // src/browser/views/render/detail-actions.ts
5977
+ import {
5978
+ ArchiveX,
5979
+ Camera,
5980
+ ChevronDown,
5981
+ Copy,
5982
+ Highlighter as Highlighter2,
5983
+ Inbox,
5984
+ MessageCircleWarning,
5985
+ StickyNote as StickyNote2,
5986
+ TicketPlus,
5987
+ View,
5988
+ createElement as createLucideElement5
5989
+ } from "lucide";
6418
5990
  var ICON_MAP = {
6419
5991
  "archive-x": ArchiveX,
6420
5992
  copy: Copy,
@@ -6430,23 +6002,6 @@ var ICON_MAP = {
6430
6002
  function iconFor(icon2) {
6431
6003
  return icon2 ? ICON_MAP[icon2] : null;
6432
6004
  }
6433
- function subtitleTpl(subtitle) {
6434
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6435
- return html`<p
6436
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6437
- data-uidex-detail-subtitle
6438
- >
6439
- ${text}
6440
- </p>`;
6441
- }
6442
- function notFoundTpl(ref2) {
6443
- return html`<p
6444
- class=${cn("text-muted-foreground text-sm", "p-4")}
6445
- data-uidex-detail-missing
6446
- >
6447
- ${ref2.kind}: ${ref2.id} not found in registry
6448
- </p>`;
6449
- }
6450
6005
  function renderActions(actions, ctx, root, heading) {
6451
6006
  const cleanups = [];
6452
6007
  const buttons = [];
@@ -6557,6 +6112,169 @@ function renderActions(actions, ctx, root, heading) {
6557
6112
  }
6558
6113
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6559
6114
  }
6115
+
6116
+ // src/browser/views/render/detail-sections.ts
6117
+ import {
6118
+ html as staticHtml,
6119
+ literal
6120
+ } from "lit-html/static.js";
6121
+
6122
+ // src/browser/views/builder/filter.ts
6123
+ function normalizeQuery(query) {
6124
+ return query.trim().toLowerCase();
6125
+ }
6126
+ function matchesQuery(haystack, query) {
6127
+ const q = normalizeQuery(query);
6128
+ if (!q) return true;
6129
+ return haystack.toLowerCase().includes(q);
6130
+ }
6131
+ function filterEntities(entities, query) {
6132
+ const q = normalizeQuery(query);
6133
+ if (!q) return entities;
6134
+ return entities.filter((e) => {
6135
+ const name = displayName(e).toLowerCase();
6136
+ const id = entityKey(e).toLowerCase();
6137
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6138
+ });
6139
+ }
6140
+ function filterIds(ids, query) {
6141
+ const q = normalizeQuery(query);
6142
+ if (!q) return ids;
6143
+ return ids.filter((id) => id.toLowerCase().includes(q));
6144
+ }
6145
+
6146
+ // src/browser/views/primitives/entity-presence.ts
6147
+ var TOUCH_RESOLVE_KINDS = [
6148
+ "element",
6149
+ "widget",
6150
+ "region",
6151
+ "primitive"
6152
+ ];
6153
+ function isAbsentFromPage(ref2, registry) {
6154
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6155
+ return !resolveEntityElement(ref2);
6156
+ }
6157
+ if (ref2.kind === "flow" && registry) {
6158
+ const flow = registry.get("flow", ref2.id);
6159
+ if (!flow) return true;
6160
+ for (const touchId of flow.touches) {
6161
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6162
+ const entity = registry.get(kind, touchId);
6163
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6164
+ }
6165
+ }
6166
+ return true;
6167
+ }
6168
+ return false;
6169
+ }
6170
+
6171
+ // src/browser/ui/kbd.ts
6172
+ 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";
6173
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6174
+ function createCommandShortcut(options = {}, children = []) {
6175
+ const { class: extra, attrs, ...rest } = options;
6176
+ return el(
6177
+ "kbd",
6178
+ {
6179
+ ...rest,
6180
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6181
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6182
+ },
6183
+ children
6184
+ );
6185
+ }
6186
+ function kbdTpl(text, className) {
6187
+ return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6188
+ >${text}</kbd
6189
+ >`;
6190
+ }
6191
+ function commandShortcutTpl(text, className) {
6192
+ return html`<kbd
6193
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6194
+ data-slot="command-shortcut"
6195
+ >${text}</kbd
6196
+ >`;
6197
+ }
6198
+
6199
+ // src/browser/views/primitives/row.ts
6200
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6201
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6202
+ function fillRowWithHandle(host, content) {
6203
+ if (content.leading) host.append(content.leading);
6204
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6205
+ host.append(label);
6206
+ if (content.subtitle) {
6207
+ host.append(
6208
+ el("span", {
6209
+ class: SUBTITLE_CLASS,
6210
+ attrs: { "data-uidex-row-subtitle": "" },
6211
+ text: content.subtitle
6212
+ })
6213
+ );
6214
+ }
6215
+ if (content.trailing != null) {
6216
+ host.append(
6217
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6218
+ );
6219
+ }
6220
+ return { label };
6221
+ }
6222
+ function rowTpl(content) {
6223
+ return html`
6224
+ ${content.leading ?? nothing}
6225
+ <span class=${LABEL_CLASS}>${content.label}</span>
6226
+ ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6227
+ >${content.subtitle}</span
6228
+ >` : nothing}
6229
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6230
+ `;
6231
+ }
6232
+
6233
+ // src/browser/views/primitives/entity-link.ts
6234
+ var ACTION_CLASS = cn(
6235
+ LIST_ITEM_CLASS,
6236
+ LIST_ITEM_INTERACTIVE_CLASS,
6237
+ "w-full cursor-pointer text-left font-normal"
6238
+ );
6239
+ function entityLinkTpl(options) {
6240
+ const { ctx, target, label, leading, class: extraClass } = options;
6241
+ const absent = isAbsentFromPage(target);
6242
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6243
+ const onClick = () => ctx.views.navigate(target);
6244
+ const preview = () => {
6245
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6246
+ };
6247
+ const restoreParent = () => {
6248
+ if (ctx.ref) {
6249
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6250
+ } else {
6251
+ ctx.highlight.hide();
6252
+ }
6253
+ };
6254
+ return html`
6255
+ <button
6256
+ type="button"
6257
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6258
+ data-slot="list-item-button"
6259
+ data-uidex-entity-link
6260
+ data-uidex-ref-kind=${target.kind}
6261
+ data-uidex-ref-id=${target.id}
6262
+ title=${absent ? "Not on this page" : ""}
6263
+ @click=${onClick}
6264
+ @mouseenter=${preview}
6265
+ @mouseleave=${restoreParent}
6266
+ @focus=${preview}
6267
+ @blur=${restoreParent}
6268
+ >
6269
+ ${rowTpl({
6270
+ leading,
6271
+ label: resolvedLabel
6272
+ })}
6273
+ </button>
6274
+ `;
6275
+ }
6276
+
6277
+ // src/browser/views/render/detail-sections.ts
6560
6278
  function entityListItems(ctx, entities) {
6561
6279
  return html`${entities.map((entity) => {
6562
6280
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6570,71 +6288,29 @@ function entityListItems(ctx, entities) {
6570
6288
  </li>`;
6571
6289
  })}`;
6572
6290
  }
6573
- function composesListTpl(ctx, label, entities) {
6574
- if (entities.length === 0) return null;
6575
- return html`
6576
- <section class="flex flex-col" data-uidex-detail-composes>
6577
- ${headingTpl(label)}
6578
- <ul class="flex flex-col">
6579
- ${entityListItems(ctx, entities)}
6580
- </ul>
6581
- </section>
6582
- `;
6583
- }
6584
- function usedByListTpl(ctx, label, entities) {
6585
- if (entities.length === 0) return null;
6586
- return html`
6587
- <section class="flex flex-col" data-uidex-detail-used-by>
6588
- ${headingTpl(label)}
6589
- <ul class="flex flex-col">
6590
- ${entityListItems(ctx, entities)}
6591
- </ul>
6592
- </section>
6593
- `;
6594
- }
6595
- function flowListTpl(ctx, flows) {
6596
- if (flows.length === 0) return null;
6597
- return html`
6598
- <section class="flex flex-col" data-uidex-detail-flows>
6599
- ${headingTpl(SECTION_LABELS.flows)}
6600
- <ul class="flex flex-col">
6601
- ${flows.map(
6602
- (flow) => html`<li>
6603
- ${entityLinkTpl({
6604
- ctx,
6605
- target: { kind: "flow", id: flow.id },
6606
- label: displayName(flow),
6607
- leading: kindIconTileTpl("flow")
6608
- })}
6609
- </li>`
6610
- )}
6611
- </ul>
6612
- </section>
6613
- `;
6614
- }
6615
- function touchesTpl(ctx, entities, query) {
6291
+ var DETAIL_SECTION_ATTRS = {
6292
+ composes: literal`data-uidex-detail-composes`,
6293
+ "used-by": literal`data-uidex-detail-used-by`,
6294
+ flows: literal`data-uidex-detail-flows`,
6295
+ touches: literal`data-uidex-detail-touches`
6296
+ };
6297
+ function entitySectionTpl(ctx, opts) {
6298
+ const { label, entities, dataAttr, emptyText } = opts;
6299
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6616
6300
  if (entities.length === 0) {
6617
- return html`
6618
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6619
- ${headingTpl(SECTION_LABELS.touches)}
6620
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6301
+ if (emptyText === void 0) return null;
6302
+ return staticHtml`
6303
+ <section class="flex flex-col gap-2" ${attr}>
6304
+ ${headingTpl(label)}
6305
+ ${mutedTextTpl(emptyText)}
6621
6306
  </section>
6622
6307
  `;
6623
6308
  }
6624
- return html`
6625
- <section class="flex flex-col" data-uidex-detail-touches>
6626
- ${headingTpl(SECTION_LABELS.touches)}
6309
+ return staticHtml`
6310
+ <section class="flex flex-col" ${attr}>
6311
+ ${headingTpl(label)}
6627
6312
  <ul class="flex flex-col">
6628
- ${entities.map(
6629
- (entity) => html`<li>
6630
- ${entityLinkTpl({
6631
- ctx,
6632
- target: { kind: entity.kind, id: entityKey(entity) },
6633
- label: displayName(entity),
6634
- leading: kindIconTileTpl(entity.kind)
6635
- })}
6636
- </li>`
6637
- )}
6313
+ ${entityListItems(ctx, entities)}
6638
6314
  </ul>
6639
6315
  </section>
6640
6316
  `;
@@ -6791,6 +6467,20 @@ function screenshotTpl(url) {
6791
6467
  </section>
6792
6468
  `;
6793
6469
  }
6470
+ function lazyScreenshotEl(load) {
6471
+ const holder = document.createElement("div");
6472
+ holder.hidden = true;
6473
+ void load().then(
6474
+ (url) => {
6475
+ if (!url) return;
6476
+ render(screenshotTpl(url), holder);
6477
+ holder.hidden = false;
6478
+ },
6479
+ () => {
6480
+ }
6481
+ );
6482
+ return holder;
6483
+ }
6794
6484
  function metadataListTpl(entries) {
6795
6485
  return html`
6796
6486
  <div
@@ -6821,28 +6511,30 @@ function sectionTpl(section, ctx, query) {
6821
6511
  return acceptanceChecklistTpl(section.items);
6822
6512
  }
6823
6513
  case "composes":
6824
- return composesListTpl(
6825
- ctx,
6826
- section.label,
6827
- section.filterable ? filterEntities(section.entities, query) : section.entities
6828
- );
6514
+ return entitySectionTpl(ctx, {
6515
+ label: section.label,
6516
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6517
+ dataAttr: "composes"
6518
+ });
6829
6519
  case "used-by":
6830
- return usedByListTpl(
6831
- ctx,
6832
- section.label,
6833
- section.filterable ? filterEntities(section.entities, query) : section.entities
6834
- );
6520
+ return entitySectionTpl(ctx, {
6521
+ label: section.label,
6522
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6523
+ dataAttr: "used-by"
6524
+ });
6835
6525
  case "flows":
6836
- return flowListTpl(
6837
- ctx,
6838
- section.filterable ? filterEntities(section.flows, query) : section.flows
6839
- );
6526
+ return entitySectionTpl(ctx, {
6527
+ label: SECTION_LABELS.flows,
6528
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6529
+ dataAttr: "flows"
6530
+ });
6840
6531
  case "touches":
6841
- return touchesTpl(
6842
- ctx,
6843
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6844
- query
6845
- );
6532
+ return entitySectionTpl(ctx, {
6533
+ label: SECTION_LABELS.touches,
6534
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6535
+ dataAttr: "touches",
6536
+ emptyText: query ? "No matches" : "No entities touched"
6537
+ });
6846
6538
  case "steps":
6847
6539
  return stepsTpl(
6848
6540
  ctx,
@@ -6854,11 +6546,32 @@ function sectionTpl(section, ctx, query) {
6854
6546
  section.filterable ? filterIds(section.paths, query) : section.paths
6855
6547
  );
6856
6548
  case "screenshot":
6857
- return screenshotTpl(section.url);
6549
+ if (section.url) return screenshotTpl(section.url);
6550
+ if (section.load) return html`${lazyScreenshotEl(section.load)}`;
6551
+ return null;
6858
6552
  case "metadata":
6859
6553
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6860
6554
  }
6861
6555
  }
6556
+
6557
+ // src/browser/views/render/detail.ts
6558
+ function subtitleTpl(subtitle) {
6559
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6560
+ return html`<p
6561
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6562
+ data-uidex-detail-subtitle
6563
+ >
6564
+ ${text}
6565
+ </p>`;
6566
+ }
6567
+ function notFoundTpl(ref2) {
6568
+ return html`<p
6569
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6570
+ data-uidex-detail-missing
6571
+ >
6572
+ ${ref2.kind}: ${ref2.id} not found in registry
6573
+ </p>`;
6574
+ }
6862
6575
  function hasFilterableSections(sections) {
6863
6576
  return sections.some((s) => "filterable" in s && s.filterable === true);
6864
6577
  }
@@ -7219,6 +6932,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7219
6932
  };
7220
6933
  }
7221
6934
 
6935
+ // src/browser/ui/button.ts
6936
+ 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";
6937
+ var buttonVariants = cva(buttonBase, {
6938
+ defaultVariants: { size: "default", variant: "default" },
6939
+ variants: {
6940
+ size: {
6941
+ default: "h-8 px-3",
6942
+ sm: "h-7 gap-1.5 px-2.5",
6943
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6944
+ lg: "h-9 px-3.5",
6945
+ xl: "h-10 px-4 text-base",
6946
+ icon: "size-8",
6947
+ "icon-sm": "size-7",
6948
+ "icon-lg": "size-9",
6949
+ "icon-xs": "size-6 rounded-md"
6950
+ },
6951
+ variant: {
6952
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6953
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6954
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6955
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6956
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6957
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6958
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6959
+ }
6960
+ }
6961
+ });
6962
+
7222
6963
  // src/browser/views/render/form.ts
7223
6964
  var fieldSeq = 0;
7224
6965
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7415,9 +7156,9 @@ function iconMediaTpl(iconTpl) {
7415
7156
  </div>
7416
7157
  `;
7417
7158
  }
7418
- function loadingViewTpl() {
7159
+ function loadingViewTpl(rootRef) {
7419
7160
  return html`
7420
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7161
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${ref(rootRef)}>
7421
7162
  ${iconMediaTpl(icon(Loader2, "animate-spin"))}
7422
7163
  <div class="flex max-w-sm flex-col items-center text-center">
7423
7164
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7430,42 +7171,47 @@ function loadingViewTpl() {
7430
7171
  </div>
7431
7172
  `;
7432
7173
  }
7433
- function successViewTpl() {
7174
+ function successViewTpl(refs) {
7434
7175
  return html`
7435
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7176
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${ref(refs.root)}>
7436
7177
  ${iconMediaTpl(icon(CircleCheck))}
7437
7178
  <div class="flex max-w-sm flex-col items-center text-center">
7438
7179
  <div
7439
7180
  class="font-heading text-xl font-semibold"
7440
7181
  data-uidex-success-title
7182
+ ${ref(refs.title)}
7441
7183
  ></div>
7442
7184
  </div>
7443
7185
  <div
7444
7186
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7445
7187
  data-uidex-success-actions
7188
+ ${ref(refs.actions)}
7446
7189
  >
7447
7190
  <a
7448
7191
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7449
7192
  target="_blank"
7450
7193
  rel="noreferrer"
7451
7194
  data-uidex-success-link
7195
+ ${ref(refs.link)}
7452
7196
  ></a>
7453
7197
  </div>
7454
7198
  </div>
7455
7199
  `;
7456
7200
  }
7457
- function errorViewTpl() {
7201
+ function errorViewTpl(refs) {
7458
7202
  return html`
7459
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7203
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${ref(refs.root)}>
7460
7204
  ${iconMediaTpl(icon(CircleX))}
7461
7205
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7462
7206
  <div
7463
7207
  class="font-heading text-xl font-semibold"
7464
7208
  data-uidex-error-title
7209
+ ${ref(refs.title)}
7465
7210
  ></div>
7466
7211
  <p
7467
7212
  class="text-muted-foreground text-sm"
7468
7213
  data-uidex-error-description
7214
+ ${ref(refs.description)}
7469
7215
  ></p>
7470
7216
  </div>
7471
7217
  <div
@@ -7476,6 +7222,7 @@ function errorViewTpl() {
7476
7222
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7477
7223
  data-slot="button"
7478
7224
  data-uidex-retry-button
7225
+ ${ref(refs.retry)}
7479
7226
  >
7480
7227
  Try Again
7481
7228
  </button>
@@ -7517,8 +7264,15 @@ function renderFormSurface(surface, ctx, root) {
7517
7264
  });
7518
7265
  }
7519
7266
  const formRef = createRef();
7520
- const statusRef = createRef();
7521
- const submitRef = createRef();
7267
+ const loadingRef = createRef();
7268
+ const successRef = createRef();
7269
+ const successTitleRef = createRef();
7270
+ const successLinkRef = createRef();
7271
+ const successActionsRef = createRef();
7272
+ const errorRef = createRef();
7273
+ const errorTitleRef = createRef();
7274
+ const errorDescriptionRef = createRef();
7275
+ const retryRef = createRef();
7522
7276
  render(
7523
7277
  html`
7524
7278
  <section
@@ -7533,7 +7287,19 @@ function renderFormSurface(surface, ctx, root) {
7533
7287
  novalidate
7534
7288
  data-uidex-form=${surface.id}
7535
7289
  ></form>
7536
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7290
+ ${loadingViewTpl(loadingRef)}
7291
+ ${successViewTpl({
7292
+ root: successRef,
7293
+ title: successTitleRef,
7294
+ link: successLinkRef,
7295
+ actions: successActionsRef
7296
+ })}
7297
+ ${errorViewTpl({
7298
+ root: errorRef,
7299
+ title: errorTitleRef,
7300
+ description: errorDescriptionRef,
7301
+ retry: retryRef
7302
+ })}
7537
7303
  </section>
7538
7304
  `,
7539
7305
  root
@@ -7563,32 +7329,15 @@ function renderFormSurface(surface, ctx, root) {
7563
7329
  }
7564
7330
  });
7565
7331
  form.append(status);
7566
- const sectionEl = root.querySelector(
7567
- "[data-uidex-form-surface]"
7568
- );
7569
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7570
- const successView = sectionEl.querySelector(
7571
- "[data-uidex-success-view]"
7572
- );
7573
- const successTitle = sectionEl.querySelector(
7574
- "[data-uidex-success-title]"
7575
- );
7576
- const successLink = sectionEl.querySelector(
7577
- "[data-uidex-success-link]"
7578
- );
7579
- const successActions = successLink.parentElement;
7580
- const errorView = sectionEl.querySelector(
7581
- "[data-uidex-error-view]"
7582
- );
7583
- const errorTitle = sectionEl.querySelector(
7584
- "[data-uidex-error-title]"
7585
- );
7586
- const errorDescription = sectionEl.querySelector(
7587
- "[data-uidex-error-description]"
7588
- );
7589
- const retryButton = sectionEl.querySelector(
7590
- "[data-uidex-retry-button]"
7591
- );
7332
+ const loadingView = loadingRef.value;
7333
+ const successView = successRef.value;
7334
+ const successTitle = successTitleRef.value;
7335
+ const successLink = successLinkRef.value;
7336
+ const successActions = successActionsRef.value;
7337
+ const errorView = errorRef.value;
7338
+ const errorTitle = errorTitleRef.value;
7339
+ const errorDescription = errorDescriptionRef.value;
7340
+ const retryButton = retryRef.value;
7592
7341
  function clearAllFieldErrors() {
7593
7342
  for (const f of fields) f.setError(null);
7594
7343
  }
@@ -7989,7 +7738,7 @@ function defaultFilter(item, query) {
7989
7738
  function rowTag(item) {
7990
7739
  return item.tag ?? item.value;
7991
7740
  }
7992
- function resolveLeadingTpl(item, ctx) {
7741
+ function resolveLeadingTpl(item) {
7993
7742
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
7994
7743
  if (item.leading) {
7995
7744
  const node = item.leading();
@@ -8094,7 +7843,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8094
7843
  data-uidex-item-value=${item.value}
8095
7844
  >
8096
7845
  ${rowTpl({
8097
- leading: resolveLeadingTpl(item, ctx),
7846
+ leading: resolveLeadingTpl(item),
8098
7847
  label: item.label,
8099
7848
  subtitle: item.subtitle,
8100
7849
  trailing: item.trailing ?? item.shortcut
@@ -8139,7 +7888,7 @@ function renderListSurface(surface, ctx, root) {
8139
7888
  return root.ownerDocument ?? document;
8140
7889
  };
8141
7890
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8142
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7891
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8143
7892
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8144
7893
  const controller = createListController({
8145
7894
  surfaceId: surface.id,
@@ -8835,52 +8584,16 @@ function sameRef2(a, b) {
8835
8584
  if (a === null || b === null) return false;
8836
8585
  return a.kind === b.kind && a.id === b.id;
8837
8586
  }
8838
- function resolveHints(view, ctx) {
8839
- const h = view.hints;
8840
- if (!h) return [];
8841
- if (typeof h === "function") {
8842
- try {
8843
- return h(ctx) ?? [];
8844
- } catch (err) {
8845
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8846
- return [];
8847
- }
8848
- }
8849
- return h;
8850
- }
8851
- function resolveTitle(view, ctx) {
8852
- const t = view.title;
8853
- if (!t) return "";
8854
- if (typeof t === "function") {
8855
- try {
8856
- return t(ctx) ?? "";
8857
- } catch (err) {
8858
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8859
- return "";
8860
- }
8861
- }
8862
- return t;
8863
- }
8864
- function resolveActions(view, ctx, globalActions) {
8865
- const result = [];
8866
- const fn = view.actions;
8867
- if (fn) {
8868
- try {
8869
- const viewActions2 = fn(ctx) ?? [];
8870
- result.push(...viewActions2);
8871
- } catch (err) {
8872
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8873
- }
8874
- }
8875
- if (globalActions) {
8587
+ function resolveProp(view, ctx, value, propName, fallback) {
8588
+ if (typeof value === "function") {
8876
8589
  try {
8877
- const globals = globalActions(ctx) ?? [];
8878
- result.push(...globals);
8590
+ return value(ctx) ?? fallback;
8879
8591
  } catch (err) {
8880
- console.error(`[uidex] globalActions() threw`, err);
8592
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8593
+ return fallback;
8881
8594
  }
8882
8595
  }
8883
- return result;
8596
+ return value ?? fallback;
8884
8597
  }
8885
8598
  function createViewStack(options) {
8886
8599
  const { container, views, session, registry, highlight } = options;
@@ -8948,39 +8661,40 @@ function createViewStack(options) {
8948
8661
  color: KIND_STYLE[top.ctx.ref.kind].color
8949
8662
  });
8950
8663
  }
8951
- function updateChrome() {
8952
- if (!shell) return;
8953
- const top = mounted[mounted.length - 1];
8954
- if (!top) return;
8664
+ function updateNavButtons(shell2, top) {
8955
8665
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8956
- shell.backBtn.hidden = atRoot;
8957
- shell.searchIcon.hidden = !atRoot;
8666
+ shell2.backBtn.hidden = atRoot;
8667
+ shell2.searchIcon.hidden = !atRoot;
8668
+ }
8669
+ function updateTitle(shell2, top) {
8958
8670
  const searchable = top.view.searchable !== false;
8959
- shell.searchInput.hidden = !searchable;
8960
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8961
- shell.headerTitle.textContent = titleText;
8962
- shell.headerTitle.hidden = searchable || !titleText;
8963
- shell.footerLeft.replaceChildren();
8671
+ shell2.searchInput.hidden = !searchable;
8672
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8673
+ shell2.headerTitle.textContent = titleText;
8674
+ shell2.headerTitle.hidden = searchable || !titleText;
8675
+ }
8676
+ function updateFooterChip(shell2, top) {
8677
+ shell2.footerLeft.replaceChildren();
8964
8678
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8965
8679
  if (refEntity) {
8966
- shell.footerLeft.append(
8680
+ shell2.footerLeft.append(
8967
8681
  renderKindChip({
8968
8682
  entity: refEntity,
8969
8683
  withKindName: true
8970
8684
  })
8971
8685
  );
8972
8686
  } else {
8973
- shell.footerLeft.append(shell.logo);
8687
+ shell2.footerLeft.append(shell2.logo);
8974
8688
  }
8975
- hintChangeSub?.();
8976
- hintChangeSub = null;
8977
- highlightActionsSub?.();
8978
- highlightActionsSub = null;
8979
- shell.footerRight.replaceChildren();
8980
- const footerItems = [];
8981
- const enterHint = resolveHints(top.view, top.ctx).find(
8982
- (h) => h.key.includes("\u21B5")
8983
- );
8689
+ }
8690
+ function buildEnterHint(top, footerItems) {
8691
+ const enterHint = resolveProp(
8692
+ top.view,
8693
+ top.ctx,
8694
+ top.view.hints,
8695
+ "hints",
8696
+ []
8697
+ ).find((h) => h.key.includes("\u21B5"));
8984
8698
  const src = top.mounted.submitIntent;
8985
8699
  if (src) {
8986
8700
  const key = enterHint?.key ?? "\u21B5";
@@ -9001,28 +8715,52 @@ function createViewStack(options) {
9001
8715
  } else if (enterHint) {
9002
8716
  footerItems.push(createHint(enterHint.key, enterHint.label));
9003
8717
  }
8718
+ }
8719
+ function buildActions(shell2, top, footerItems) {
9004
8720
  const actionsDivider = createFooterDivider();
9005
- const viewActions2 = resolveActions(top.view, top.ctx);
8721
+ const viewActions2 = resolveProp(
8722
+ top.view,
8723
+ top.ctx,
8724
+ top.view.actions,
8725
+ "actions",
8726
+ []
8727
+ );
9006
8728
  const hlSrc = top.mounted.highlightActions;
9007
8729
  const syncActions = () => {
9008
8730
  const hlActions = hlSrc?.get() ?? [];
9009
8731
  const combined = [...hlActions, ...viewActions2];
9010
- shell.actionsPopup.setActions(combined);
8732
+ shell2.actionsPopup.setActions(combined);
9011
8733
  const visible = combined.length > 0;
9012
- shell.actionsPopup.trigger.hidden = !visible;
8734
+ shell2.actionsPopup.trigger.hidden = !visible;
9013
8735
  actionsDivider.hidden = !visible || footerItems.length === 0;
9014
8736
  };
9015
8737
  if (hlSrc) {
9016
8738
  highlightActionsSub = hlSrc.subscribe(syncActions);
9017
8739
  }
9018
8740
  for (let i = 0; i < footerItems.length; i++) {
9019
- if (i > 0) shell.footerRight.append(createFooterDivider());
9020
- shell.footerRight.append(footerItems[i]);
8741
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8742
+ shell2.footerRight.append(footerItems[i]);
9021
8743
  }
9022
- shell.footerRight.append(actionsDivider);
9023
- shell.footerRight.append(shell.actionsPopup.trigger);
8744
+ shell2.footerRight.append(actionsDivider);
8745
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
9024
8746
  syncActions();
9025
8747
  }
8748
+ function updateChrome() {
8749
+ if (!shell) return;
8750
+ const top = mounted[mounted.length - 1];
8751
+ if (!top) return;
8752
+ updateNavButtons(shell, top);
8753
+ updateTitle(shell, top);
8754
+ updateFooterChip(shell, top);
8755
+ hintChangeSub?.();
8756
+ hintChangeSub = null;
8757
+ highlightActionsSub?.();
8758
+ highlightActionsSub = null;
8759
+ shell.footerRight.replaceChildren();
8760
+ const footerItems = [];
8761
+ buildEnterHint(top, footerItems);
8762
+ buildActions(shell, top, footerItems);
8763
+ }
9026
8764
  function render2() {
9027
8765
  if (!container.isConnected) return;
9028
8766
  const stack = session.getState().stack;
@@ -9119,7 +8857,7 @@ function createViewStack(options) {
9119
8857
 
9120
8858
  // src/browser/views/built-in/ids.ts
9121
8859
  var BUILT_IN_VIEW_IDS = {
9122
- archiveReason: "archive-reason",
8860
+ closeReason: "close-reason",
9123
8861
  commandPalette: "command-palette",
9124
8862
  elements: "elements",
9125
8863
  entityReports: "entity-reports",
@@ -9170,7 +8908,7 @@ function parentDetail(ref2) {
9170
8908
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9171
8909
  }
9172
8910
 
9173
- // src/browser/views/built-in/archive-reason.ts
8911
+ // src/browser/views/built-in/close-reason.ts
9174
8912
  import {
9175
8913
  Ban,
9176
8914
  BugOff,
@@ -9180,10 +8918,10 @@ import {
9180
8918
  createElement as createLucideElement6
9181
8919
  } from "lucide";
9182
8920
  var pendingReportId = null;
9183
- var afterArchive = null;
9184
- function setArchiveTarget(reportId, onDone) {
8921
+ var afterClose = null;
8922
+ function setCloseTarget(reportId, onDone) {
9185
8923
  pendingReportId = reportId;
9186
- afterArchive = onDone;
8924
+ afterClose = onDone;
9187
8925
  }
9188
8926
  function leadingIcon(iconNode) {
9189
8927
  return () => {
@@ -9198,16 +8936,16 @@ function spinnerTile() {
9198
8936
  return createIconTile(spinner);
9199
8937
  }
9200
8938
  var REASONS = [
9201
- { value: "fixed", label: "Fixed", icon: CircleCheck2 },
8939
+ { value: "fixed", label: "Resolved", icon: CircleCheck2 },
9202
8940
  { value: "not_a_bug", label: "Not a bug", icon: BugOff },
9203
8941
  { value: "duplicate", label: "Duplicate", icon: Copy2 },
9204
8942
  { value: "wont_fix", label: "Won't fix", icon: Ban }
9205
8943
  ];
9206
- var archiveReasonView = {
9207
- id: BUILT_IN_VIEW_IDS.archiveReason,
8944
+ var closeReasonView = {
8945
+ id: BUILT_IN_VIEW_IDS.closeReason,
9208
8946
  matches: () => false,
9209
8947
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9210
- title: "Archive reason",
8948
+ title: "Close reason",
9211
8949
  searchable: false,
9212
8950
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9213
8951
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9220,12 +8958,12 @@ var archiveReasonView = {
9220
8958
  }));
9221
8959
  const surface = {
9222
8960
  kind: "list",
9223
- id: "uidex-archive-reason",
8961
+ id: "uidex-close-reason",
9224
8962
  searchable: false,
9225
8963
  items,
9226
8964
  emptyLabel: "No reasons available",
9227
8965
  onSelect: async (item) => {
9228
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8966
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9229
8967
  busy = true;
9230
8968
  const row = root.querySelector(
9231
8969
  `[data-uidex-item-value="${item.value}"]`
@@ -9237,13 +8975,10 @@ var archiveReasonView = {
9237
8975
  row.style.pointerEvents = "none";
9238
8976
  }
9239
8977
  try {
9240
- await ctx.registry.archiveReport(
9241
- pendingReportId,
9242
- item.value
9243
- );
9244
- const done = afterArchive;
8978
+ await ctx.registry.closeReport(pendingReportId, item.value);
8979
+ const done = afterClose;
9245
8980
  pendingReportId = null;
9246
- afterArchive = null;
8981
+ afterClose = null;
9247
8982
  done?.();
9248
8983
  } catch {
9249
8984
  busy = false;
@@ -9261,7 +8996,7 @@ var archiveReasonView = {
9261
8996
  }
9262
8997
  const error = document.createElement("p");
9263
8998
  error.className = "text-destructive px-4 py-2 text-xs";
9264
- error.textContent = "Archive failed \u2014 try again";
8999
+ error.textContent = "Close failed \u2014 try again";
9265
9000
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9266
9001
  setTimeout(() => error.remove(), 3e3);
9267
9002
  }
@@ -9588,7 +9323,8 @@ function createCommandPaletteView(shortcut) {
9588
9323
  }
9589
9324
 
9590
9325
  // src/browser/internal/screenshot.ts
9591
- import { domToPng } from "modern-screenshot";
9326
+ import { domToPng, domToWebp } from "modern-screenshot";
9327
+ var WEBP_QUALITY = 0.85;
9592
9328
  function resolveBackgroundColor(el2) {
9593
9329
  let node = el2;
9594
9330
  while (node) {
@@ -9602,8 +9338,7 @@ function isUidexChrome(node) {
9602
9338
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9603
9339
  const el2 = node;
9604
9340
  if (el2.shadowRoot !== null) return true;
9605
- const cls = el2.classList;
9606
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9341
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9607
9342
  }
9608
9343
  async function captureScreenshot(options = {}) {
9609
9344
  if (typeof document === "undefined") {
@@ -9615,8 +9350,10 @@ async function captureScreenshot(options = {}) {
9615
9350
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9616
9351
  const padding = 16;
9617
9352
  const height = target.scrollHeight || target.clientHeight;
9618
- return domToPng(target, {
9353
+ const encode = options.format === "png" ? domToPng : domToWebp;
9354
+ return encode(target, {
9619
9355
  scale,
9356
+ quality: WEBP_QUALITY,
9620
9357
  width: width + padding * 2,
9621
9358
  height: height + padding * 2,
9622
9359
  backgroundColor: resolveBackgroundColor(target),
@@ -9672,12 +9409,6 @@ function collectFlowsTouching(ctx, targetId) {
9672
9409
  }
9673
9410
 
9674
9411
  // src/browser/views/builder/detail-builder.ts
9675
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9676
- "element",
9677
- "region",
9678
- "widget",
9679
- "primitive"
9680
- ]);
9681
9412
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9682
9413
  function copyPathAction(ref2, loc) {
9683
9414
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9779,7 +9510,10 @@ function copyScreenshotAction(ref2) {
9779
9510
  const target = resolveEntityElement(ref2) ?? void 0;
9780
9511
  const dataUrl = await captureScreenshot({
9781
9512
  target,
9782
- maxWidth: 1280
9513
+ maxWidth: 1280,
9514
+ // Clipboard write requires PNG — the async Clipboard API rejects
9515
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9516
+ format: "png"
9783
9517
  });
9784
9518
  const res = await fetch(dataUrl);
9785
9519
  const blob = await res.blob();
@@ -9830,7 +9564,7 @@ function createEntityDetailView(config) {
9830
9564
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9831
9565
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9832
9566
  }
9833
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9567
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9834
9568
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9835
9569
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9836
9570
  }
@@ -9856,7 +9590,7 @@ function createEntityDetailView(config) {
9856
9590
  sections.push(s);
9857
9591
  }
9858
9592
  }
9859
- if (!DOM_BACKED_KINDS2.has(kind)) {
9593
+ if (!DOM_BACKED_KINDS.has(kind)) {
9860
9594
  sections.push({
9861
9595
  id: "composes",
9862
9596
  label: SECTION_LABELS.contains,
@@ -9864,7 +9598,7 @@ function createEntityDetailView(config) {
9864
9598
  filterable: true
9865
9599
  });
9866
9600
  }
9867
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9601
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9868
9602
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9869
9603
  if (featureEntities.length > 0) {
9870
9604
  sections.push({
@@ -9894,35 +9628,14 @@ function createEntityDetailView(config) {
9894
9628
  }
9895
9629
 
9896
9630
  // src/browser/views/built-in/entity-detail.ts
9897
- function collectDomParents(ctx, ref2) {
9898
- const el2 = resolveEntityElement(ref2);
9899
- if (!el2) return [];
9900
- const parents = [];
9901
- const seen = /* @__PURE__ */ new Set();
9902
- let node = el2.parentElement;
9903
- while (node) {
9904
- if (node instanceof HTMLElement) {
9905
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9906
- const id = node.getAttribute(attr);
9907
- if (id) {
9908
- const key = `${kind}:${id}`;
9909
- if (!seen.has(key)) {
9910
- seen.add(key);
9911
- const entity = ctx.registry.get(kind, id);
9912
- if (entity) parents.push(entity);
9913
- }
9914
- }
9915
- }
9916
- }
9917
- node = node.parentElement;
9918
- }
9919
- return parents;
9631
+ function collectFeatureConsumers(ctx, featureId) {
9632
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9920
9633
  }
9921
- function usedBySection(ctx, ref2) {
9634
+ function usedBySection(ctx, featureId) {
9922
9635
  return {
9923
9636
  id: "used-by",
9924
9637
  label: SECTION_LABELS.usedBy,
9925
- entities: collectDomParents(ctx, ref2),
9638
+ entities: collectFeatureConsumers(ctx, featureId),
9926
9639
  filterable: true
9927
9640
  };
9928
9641
  }
@@ -9943,9 +9656,7 @@ var featureDetailView = createEntityDetailView({
9943
9656
  id: "feature-detail",
9944
9657
  kind: "feature",
9945
9658
  fallbackTitle: "Feature",
9946
- extraSections: (ctx, entity) => [
9947
- usedBySection(ctx, { kind: "feature", id: entity.id })
9948
- ]
9659
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9949
9660
  });
9950
9661
  var regionDetailView = createEntityDetailView({
9951
9662
  id: "region-detail",
@@ -10073,6 +9784,15 @@ var reportDetailView = {
10073
9784
  }
10074
9785
  if (report.screenshot) {
10075
9786
  sections.push({ id: "screenshot", url: report.screenshot });
9787
+ } else {
9788
+ const pins = ctx.cloud?.pins;
9789
+ const fetchScreenshot = pins?.screenshot;
9790
+ if (pins && fetchScreenshot) {
9791
+ sections.push({
9792
+ id: "screenshot",
9793
+ load: () => fetchScreenshot.call(pins, report.id)
9794
+ });
9795
+ }
10076
9796
  }
10077
9797
  const metaEntries = [];
10078
9798
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -10083,14 +9803,14 @@ var reportDetailView = {
10083
9803
  sections.push({ id: "metadata", entries: metaEntries });
10084
9804
  }
10085
9805
  const actions = [];
10086
- if (ctx.registry.archiveReport) {
9806
+ if (ctx.registry.closeReport) {
10087
9807
  actions.push({
10088
- id: "archive",
10089
- label: "Archive",
9808
+ id: "close",
9809
+ label: "Close",
10090
9810
  icon: "archive-x",
10091
9811
  run: () => {
10092
- setArchiveTarget(report.id, () => ctx.pop());
10093
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9812
+ setCloseTarget(report.id, () => ctx.pop());
9813
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10094
9814
  }
10095
9815
  });
10096
9816
  }
@@ -10128,13 +9848,13 @@ function reportToItem(report, ctx) {
10128
9848
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10129
9849
  const kind = ctx.ref?.kind ?? "element";
10130
9850
  const actions = [];
10131
- if (ctx.registry.archiveReport) {
9851
+ if (ctx.registry.closeReport) {
10132
9852
  actions.push({
10133
- id: `archive-${report.id}`,
10134
- label: "Archive",
9853
+ id: `close-${report.id}`,
9854
+ label: "Close",
10135
9855
  perform: () => {
10136
- setArchiveTarget(report.id, () => ctx.pop());
10137
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9856
+ setCloseTarget(report.id, () => ctx.pop());
9857
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10138
9858
  }
10139
9859
  });
10140
9860
  }
@@ -10287,7 +10007,7 @@ function capitalize2(s) {
10287
10007
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10288
10008
  }
10289
10009
  function renderPayloadMarkdown(payload) {
10290
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
10010
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10291
10011
  const ctx = payload.context;
10292
10012
  const lines = [
10293
10013
  `# ${heading}`,
@@ -10356,12 +10076,6 @@ var reportFields = [
10356
10076
  ];
10357
10077
 
10358
10078
  // src/browser/views/built-in/report/view-builder.ts
10359
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10360
- "element",
10361
- "region",
10362
- "widget",
10363
- "primitive"
10364
- ]);
10365
10079
  var KIND_TO_ATTR = new Map(
10366
10080
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10367
10081
  );
@@ -10432,7 +10146,7 @@ function createReportView(config) {
10432
10146
  const cloud = resolveCloud(ctx);
10433
10147
  const extra = extraFields ? extraFields(ctx) : [];
10434
10148
  const fields = [...extra, ...baseFields ?? reportFields];
10435
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10149
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10436
10150
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10437
10151
  const screenshotPromise = isDomBacked ? captureScreenshot({
10438
10152
  target: targetEl ?? void 0,
@@ -11015,7 +10729,7 @@ var pinSettingsView = {
11015
10729
  // src/browser/views/built-in/index.ts
11016
10730
  function buildDefaultViews(shortcut) {
11017
10731
  return [
11018
- archiveReasonView,
10732
+ closeReasonView,
11019
10733
  createCommandPaletteView(shortcut),
11020
10734
  explorePageView,
11021
10735
  componentDetailView,
@@ -11043,6 +10757,27 @@ function buildDefaultViews(shortcut) {
11043
10757
  }
11044
10758
 
11045
10759
  // src/browser/create-uidex.ts
10760
+ function getVisibleEntities() {
10761
+ if (typeof document === "undefined") return [];
10762
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10763
+ const nodes = document.querySelectorAll(selector);
10764
+ const ids = [];
10765
+ const seen = /* @__PURE__ */ new Set();
10766
+ for (const node of nodes) {
10767
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10768
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10769
+ const id = node.getAttribute(attr);
10770
+ if (!id) continue;
10771
+ const key = `${kind}:${id}`;
10772
+ if (!seen.has(key)) {
10773
+ seen.add(key);
10774
+ ids.push(key);
10775
+ }
10776
+ break;
10777
+ }
10778
+ }
10779
+ return ids;
10780
+ }
11046
10781
  function createUidex(options = {}) {
11047
10782
  const registry = createRegistry();
11048
10783
  const inspectorRef = { current: null };
@@ -11077,7 +10812,7 @@ function createUidex(options = {}) {
11077
10812
  const views = createRouter({ session });
11078
10813
  const cloud = options.cloud ?? null;
11079
10814
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
11080
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10815
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
11081
10816
  if (options.defaultViews !== false) {
11082
10817
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
11083
10818
  }
@@ -11087,6 +10822,94 @@ function createUidex(options = {}) {
11087
10822
  let shadowRoot = null;
11088
10823
  const mountCleanup = createCleanupStack();
11089
10824
  let mounted = false;
10825
+ function syncReportsToRegistry() {
10826
+ const layer = pinLayerRef.current;
10827
+ if (!layer) return;
10828
+ const byEntity = /* @__PURE__ */ new Map();
10829
+ for (const pin of layer.getAllPins()) {
10830
+ const cid = pin.entity ?? "";
10831
+ if (!cid) continue;
10832
+ const list = byEntity.get(cid);
10833
+ if (list) list.push(pin);
10834
+ else byEntity.set(cid, [pin]);
10835
+ }
10836
+ for (const [cid, pins] of byEntity) {
10837
+ const ref2 = parseComponentRef(cid);
10838
+ registry.setReports(ref2.kind, ref2.id, pins);
10839
+ }
10840
+ }
10841
+ function setupKeyBindings(root, viewStack) {
10842
+ const bindings = bindShadowKeys({
10843
+ shadowRoot: root,
10844
+ session,
10845
+ getActionsPopup: () => viewStack.getActionsPopup(),
10846
+ getPinLayer: () => pinLayerRef.current,
10847
+ shortcut: options.shortcut
10848
+ });
10849
+ mountCleanup.add(bindings);
10850
+ return bindings;
10851
+ }
10852
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10853
+ if (cloud) {
10854
+ registry.closeReport = async (reportId, status) => {
10855
+ pinLayerRef.current?.removePin(reportId);
10856
+ await cloud.pins.close(
10857
+ reportId,
10858
+ status
10859
+ );
10860
+ syncReportsToRegistry();
10861
+ };
10862
+ }
10863
+ const pinLayer = createPinLayer({
10864
+ container: root,
10865
+ onOpenPinDetail: (componentId, reportId) => {
10866
+ const ref2 = parseComponentRef(componentId);
10867
+ setSelectedReportId(reportId);
10868
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10869
+ session.mode.transition.enterViewing(views.buildStack(entry));
10870
+ },
10871
+ onHoverPin: (anchor, componentId) => {
10872
+ const overlay = overlayRef.current;
10873
+ if (!overlay) return;
10874
+ if (!anchor || !componentId) {
10875
+ overlay.hide();
10876
+ return;
10877
+ }
10878
+ overlay.show(anchor, {
10879
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10880
+ });
10881
+ },
10882
+ onPinsChanged: syncReportsToRegistry
10883
+ });
10884
+ shell.menuBar.setPinLayer(pinLayer);
10885
+ pinLayerRef.current = pinLayer;
10886
+ mountCleanup.add(() => {
10887
+ pinLayer.destroy();
10888
+ pinLayerRef.current = null;
10889
+ registry.closeReport = void 0;
10890
+ });
10891
+ if (cloud) {
10892
+ const detach = pinLayer.attachCloud({
10893
+ cloud,
10894
+ channel,
10895
+ getRoute: getCurrentRoute,
10896
+ getPathname,
10897
+ getVisibleEntities,
10898
+ getMatchMode,
10899
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10900
+ });
10901
+ mountCleanup.add(detach);
10902
+ if (channel && typeof window !== "undefined") {
10903
+ const onRouteChange = () => {
10904
+ const route = getCurrentRoute();
10905
+ channel?.joinRoute(route);
10906
+ void pinLayer.refresh();
10907
+ };
10908
+ const detachRoute = bindRouteChange(onRouteChange);
10909
+ mountCleanup.add(detachRoute);
10910
+ }
10911
+ }
10912
+ }
11090
10913
  function mount(target) {
11091
10914
  if (mounted) return;
11092
10915
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11095,28 +10918,7 @@ function createUidex(options = {}) {
11095
10918
  }
11096
10919
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11097
10920
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11098
- const getVisibleEntities = () => {
11099
- if (typeof document === "undefined") return [];
11100
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11101
- const nodes = document.querySelectorAll(selector);
11102
- const ids = [];
11103
- const seen = /* @__PURE__ */ new Set();
11104
- for (const node of nodes) {
11105
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11106
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11107
- const id = node.getAttribute(attr);
11108
- if (!id) continue;
11109
- const key = `${kind}:${id}`;
11110
- if (!seen.has(key)) {
11111
- seen.add(key);
11112
- ids.push(key);
11113
- }
11114
- break;
11115
- }
11116
- }
11117
- return ids;
11118
- };
11119
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10921
+ const getMatchMode = () => readMode();
11120
10922
  let channel = null;
11121
10923
  if (cloud && options.user) {
11122
10924
  channel = cloud.realtime.connect({
@@ -11137,7 +10939,6 @@ function createUidex(options = {}) {
11137
10939
  onSelect: (match) => {
11138
10940
  const route = views.resolve(match.ref);
11139
10941
  if (!route) return;
11140
- session.select(match.ref);
11141
10942
  const entry = { id: route.view.id, ref: match.ref };
11142
10943
  session.mode.transition.enterViewing(views.buildStack(entry));
11143
10944
  }
@@ -11188,92 +10989,15 @@ function createUidex(options = {}) {
11188
10989
  });
11189
10990
  mountCleanup.add(viewStack);
11190
10991
  if (shadowRoot) {
11191
- keyBindings = bindShadowKeys({
10992
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10993
+ setupPinLayer(
11192
10994
  shadowRoot,
11193
- session,
11194
- getActionsPopup: () => viewStack.getActionsPopup(),
11195
- getPinLayer: () => pinLayerRef.current,
11196
- shortcut: options.shortcut
11197
- });
11198
- mountCleanup.add(keyBindings);
11199
- }
11200
- if (shadowRoot) {
11201
- const syncReportsToRegistry = () => {
11202
- const layer = pinLayerRef.current;
11203
- if (!layer) return;
11204
- const byEntity = /* @__PURE__ */ new Map();
11205
- for (const pin of layer.getAllPins()) {
11206
- const cid = pin.entity ?? "";
11207
- if (!cid) continue;
11208
- const list = byEntity.get(cid);
11209
- if (list) list.push(pin);
11210
- else byEntity.set(cid, [pin]);
11211
- }
11212
- for (const [cid, pins] of byEntity) {
11213
- const ref2 = parseComponentRef(cid);
11214
- registry.setReports(ref2.kind, ref2.id, pins);
11215
- }
11216
- };
11217
- if (cloud) {
11218
- registry.archiveReport = async (reportId, reason) => {
11219
- pinLayerRef.current?.removePin(reportId);
11220
- await cloud.pins.archive(
11221
- reportId,
11222
- reason
11223
- );
11224
- syncReportsToRegistry();
11225
- };
11226
- }
11227
- const pinLayer = createPinLayer({
11228
- container: shadowRoot,
11229
- currentBranch: options.git?.branch ?? null,
11230
- onOpenPinDetail: (componentId, reportId) => {
11231
- const ref2 = parseComponentRef(componentId);
11232
- setSelectedReportId(reportId);
11233
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11234
- session.mode.transition.enterViewing(views.buildStack(entry));
11235
- },
11236
- onHoverPin: (anchor, componentId) => {
11237
- const overlay = overlayRef.current;
11238
- if (!overlay) return;
11239
- if (!anchor || !componentId) {
11240
- overlay.hide();
11241
- return;
11242
- }
11243
- overlay.show(anchor, {
11244
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11245
- });
11246
- },
11247
- onPinsChanged: syncReportsToRegistry
11248
- });
11249
- shell.menuBar.setPinLayer(pinLayer);
11250
- pinLayerRef.current = pinLayer;
11251
- mountCleanup.add(() => {
11252
- pinLayer.destroy();
11253
- pinLayerRef.current = null;
11254
- registry.archiveReport = void 0;
11255
- });
11256
- if (cloud) {
11257
- const detach = pinLayer.attachCloud({
11258
- cloud,
11259
- channel,
11260
- getRoute: getCurrentRoute,
11261
- getPathname,
11262
- getVisibleEntities,
11263
- getMatchMode,
11264
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11265
- });
11266
- mountCleanup.add(detach);
11267
- if (channel && typeof window !== "undefined") {
11268
- const onRouteChange = () => {
11269
- const route = getCurrentRoute();
11270
- channel?.joinRoute(route);
11271
- void pinLayer.refresh();
11272
- };
11273
- const detachRoute = bindRouteChange(onRouteChange);
11274
- mountCleanup.add(detachRoute);
11275
- }
11276
- }
10995
+ shell,
10996
+ channel,
10997
+ getCurrentRoute,
10998
+ getMatchMode,
10999
+ getPathname
11000
+ );
11277
11001
  }
11278
11002
  if (ingest) {
11279
11003
  ingest.start();
@@ -11312,18 +11036,23 @@ function UidexProvider({
11312
11036
  config,
11313
11037
  children
11314
11038
  }) {
11315
- const [ownedInstance] = useState(() => {
11039
+ const [owned] = useState(() => {
11316
11040
  if (externalInstance) return null;
11317
- const resolvedCloud = cloud !== void 0 ? cloud : projectKey ? createCloud({ projectKey }) : null;
11318
- return createUidex({ ...config, cloud: resolvedCloud, user });
11041
+ const ownedCloud = cloud === void 0 && projectKey && typeof window !== "undefined" ? createCloud({ projectKey }) : null;
11042
+ const resolvedCloud = cloud !== void 0 ? cloud : ownedCloud;
11043
+ return {
11044
+ instance: createUidex({ ...config, cloud: resolvedCloud, user }),
11045
+ cloud: ownedCloud
11046
+ };
11319
11047
  });
11320
- const instance = externalInstance ?? ownedInstance;
11048
+ const instance = externalInstance ?? owned.instance;
11321
11049
  useEffect(() => {
11322
- if (!ownedInstance) return;
11050
+ if (!owned) return;
11323
11051
  return () => {
11324
- ownedInstance.unmount();
11052
+ owned.instance.unmount();
11053
+ owned.cloud?.dispose?.();
11325
11054
  };
11326
- }, [ownedInstance]);
11055
+ }, [owned]);
11327
11056
  return /* @__PURE__ */ jsx(UidexContext.Provider, { value: instance, children });
11328
11057
  }
11329
11058
 
@@ -11458,6 +11187,7 @@ function UidexDevtools({
11458
11187
  setInstance(inst);
11459
11188
  return () => {
11460
11189
  inst.unmount();
11190
+ cloud?.dispose?.();
11461
11191
  };
11462
11192
  }, [projectKey, user?.id]);
11463
11193
  if (!instance) return null;