uidex 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/cli.cjs +1542 -1227
  3. package/dist/cli/cli.cjs.map +1 -1
  4. package/dist/cloud/index.cjs +385 -175
  5. package/dist/cloud/index.cjs.map +1 -1
  6. package/dist/cloud/index.d.cts +192 -4
  7. package/dist/cloud/index.d.ts +192 -4
  8. package/dist/cloud/index.js +377 -177
  9. package/dist/cloud/index.js.map +1 -1
  10. package/dist/headless/index.cjs +116 -251
  11. package/dist/headless/index.cjs.map +1 -1
  12. package/dist/headless/index.d.cts +6 -11
  13. package/dist/headless/index.d.ts +6 -11
  14. package/dist/headless/index.js +116 -253
  15. package/dist/headless/index.js.map +1 -1
  16. package/dist/index.cjs +776 -1055
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +152 -160
  19. package/dist/index.d.ts +152 -160
  20. package/dist/index.js +792 -1066
  21. package/dist/index.js.map +1 -1
  22. package/dist/react/index.cjs +801 -1019
  23. package/dist/react/index.cjs.map +1 -1
  24. package/dist/react/index.d.cts +102 -86
  25. package/dist/react/index.d.ts +102 -86
  26. package/dist/react/index.js +821 -1038
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/scan/index.cjs +1550 -1220
  29. package/dist/scan/index.cjs.map +1 -1
  30. package/dist/scan/index.d.cts +210 -12
  31. package/dist/scan/index.d.ts +210 -12
  32. package/dist/scan/index.js +1547 -1219
  33. package/dist/scan/index.js.map +1 -1
  34. package/package.json +22 -21
  35. package/templates/claude/SKILL.md +71 -0
  36. package/templates/claude/references/audit.md +43 -0
  37. package/templates/claude/{rules.md → references/conventions.md} +25 -28
  38. package/templates/claude/audit.md +0 -43
  39. /package/templates/claude/{api.md → references/api.md} +0 -0
@@ -115,6 +115,7 @@ function freezeEntity(entity, flows) {
115
115
  function createRegistry() {
116
116
  const store = emptyStore();
117
117
  let flowsCache = null;
118
+ const patternCache = /* @__PURE__ */ new Map();
118
119
  const getFlows = () => {
119
120
  if (flowsCache === null) flowsCache = Array.from(store.flow.values());
120
121
  return flowsCache;
@@ -124,6 +125,7 @@ function createRegistry() {
124
125
  const key = entityKey(entity);
125
126
  store[entity.kind].set(key, entity);
126
127
  flowsCache = null;
128
+ patternCache.delete(entity.kind);
127
129
  };
128
130
  const get = (kind, id) => {
129
131
  assertEntityKind(kind);
@@ -131,6 +133,51 @@ function createRegistry() {
131
133
  if (raw === void 0) return void 0;
132
134
  return freezeEntity(raw, getFlows());
133
135
  };
136
+ const getPatternsForKind = (kind) => {
137
+ const cached = patternCache.get(kind);
138
+ if (cached !== void 0) return cached;
139
+ const patterns = [];
140
+ for (const [key, entity] of store[kind]) {
141
+ if (key.includes("*")) {
142
+ const segments = key.split("*");
143
+ patterns.push({
144
+ segments,
145
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
146
+ entity
147
+ });
148
+ }
149
+ }
150
+ patternCache.set(
151
+ kind,
152
+ patterns
153
+ );
154
+ return patterns;
155
+ };
156
+ const matchesSegments = (segments, id) => {
157
+ const first = segments[0];
158
+ const last = segments[segments.length - 1];
159
+ if (!id.startsWith(first)) return false;
160
+ let pos = first.length;
161
+ for (let i = 1; i < segments.length - 1; i++) {
162
+ const idx = id.indexOf(segments[i], pos);
163
+ if (idx === -1) return false;
164
+ pos = idx + segments[i].length;
165
+ }
166
+ return id.endsWith(last) && id.length - last.length >= pos;
167
+ };
168
+ const matchPattern = (kind, id) => {
169
+ assertEntityKind(kind);
170
+ const patterns = getPatternsForKind(kind);
171
+ if (patterns.length === 0) return void 0;
172
+ let best;
173
+ for (const entry of patterns) {
174
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
175
+ best = entry;
176
+ }
177
+ }
178
+ if (best === void 0) return void 0;
179
+ return freezeEntity(best.entity, getFlows());
180
+ };
134
181
  const list = (kind) => {
135
182
  assertEntityKind(kind);
136
183
  const flows = getFlows();
@@ -181,6 +228,7 @@ function createRegistry() {
181
228
  return {
182
229
  add,
183
230
  get,
231
+ matchPattern,
184
232
  list,
185
233
  query,
186
234
  byScope,
@@ -425,7 +473,7 @@ function createNetworkCapture(options = {}) {
425
473
 
426
474
  // src/browser/ingest/index.ts
427
475
  function createIngest(options = {}) {
428
- const { session, ...opts } = options;
476
+ const opts = options;
429
477
  const wantConsole = opts.captureConsole !== false;
430
478
  const wantNetwork = opts.captureNetwork !== false;
431
479
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -444,14 +492,12 @@ function createIngest(options = {}) {
444
492
  consoleCapture?.start();
445
493
  networkCapture?.start();
446
494
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
447
- if (active) session?.setIngest(true);
448
495
  }
449
496
  function stop() {
450
497
  if (!active) return;
451
498
  consoleCapture?.stop();
452
499
  networkCapture?.stop();
453
500
  active = false;
454
- session?.setIngest(false);
455
501
  }
456
502
  return {
457
503
  start,
@@ -573,8 +619,7 @@ var COMMAND_PALETTE_ENTRY = {
573
619
  function createModeStore(options) {
574
620
  const { nav, bindings } = options;
575
621
  const store = (0, import_vanilla.createStore)(() => ({
576
- mode: "idle",
577
- inspectorActive: false
622
+ mode: "idle"
578
623
  }));
579
624
  const transition = {
580
625
  openPalette() {
@@ -583,17 +628,17 @@ function createModeStore(options) {
583
628
  bindings?.destroyInspector?.();
584
629
  }
585
630
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
586
- store.setState({ mode: "palette", inspectorActive: false });
631
+ store.setState({ mode: "palette" });
587
632
  },
588
633
  openInspector() {
589
634
  bindings?.mountInspector?.();
590
635
  nav.nav.clear();
591
- store.setState({ mode: "inspecting", inspectorActive: true });
636
+ store.setState({ mode: "inspecting" });
592
637
  },
593
638
  closeInspector() {
594
639
  bindings?.destroyInspector?.();
595
640
  nav.nav.clear();
596
- store.setState({ mode: "idle", inspectorActive: false });
641
+ store.setState({ mode: "idle" });
597
642
  },
598
643
  toggleInspector() {
599
644
  if (store.getState().mode === "inspecting") {
@@ -608,7 +653,7 @@ function createModeStore(options) {
608
653
  bindings?.destroyInspector?.();
609
654
  }
610
655
  nav.nav.reset(initialStack);
611
- store.setState({ mode: "viewing", inspectorActive: false });
656
+ store.setState({ mode: "viewing" });
612
657
  },
613
658
  dismiss() {
614
659
  const prev = store.getState();
@@ -616,7 +661,7 @@ function createModeStore(options) {
616
661
  bindings?.destroyInspector?.();
617
662
  }
618
663
  nav.nav.clear();
619
- store.setState({ mode: "idle", inspectorActive: false });
664
+ store.setState({ mode: "idle" });
620
665
  },
621
666
  popOrTransition() {
622
667
  const { stack } = nav.getState();
@@ -624,12 +669,12 @@ function createModeStore(options) {
624
669
  nav.nav.pop();
625
670
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
626
671
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
627
- store.setState({ mode: "palette", inspectorActive: false });
672
+ store.setState({ mode: "palette" });
628
673
  } else if (stack.length === 2) {
629
674
  nav.nav.pop();
630
675
  } else {
631
676
  nav.nav.clear();
632
- store.setState({ mode: "idle", inspectorActive: false });
677
+ store.setState({ mode: "idle" });
633
678
  }
634
679
  },
635
680
  pushView(entry) {
@@ -646,7 +691,7 @@ function createModeStore(options) {
646
691
  case "inspecting":
647
692
  bindings?.destroyInspector?.();
648
693
  nav.nav.reset([entry]);
649
- store.setState({ mode: "viewing", inspectorActive: false });
694
+ store.setState({ mode: "viewing" });
650
695
  break;
651
696
  case "palette":
652
697
  case "viewing":
@@ -681,14 +726,6 @@ function createNavigationStore() {
681
726
  store.setState({ stack: s.slice(0, -1) });
682
727
  }
683
728
  },
684
- replace(entry) {
685
- const s = store.getState().stack;
686
- if (s.length === 0) {
687
- store.setState({ stack: [entry] });
688
- } else {
689
- store.setState({ stack: [...s.slice(0, -1), entry] });
690
- }
691
- },
692
729
  clear() {
693
730
  store.setState({ stack: [] });
694
731
  },
@@ -703,14 +740,11 @@ function createNavigationStore() {
703
740
 
704
741
  // src/browser/session/store.ts
705
742
  var defaultSnapshot = {
706
- hover: null,
707
- selection: null,
708
743
  stack: [],
709
744
  pinnedHighlight: null,
710
- inspectorActive: false,
745
+ mode: "idle",
711
746
  theme: "auto",
712
747
  resolvedTheme: "light",
713
- ingestActive: false,
714
748
  user: null
715
749
  };
716
750
  function resolveTheme(preference, detect) {
@@ -761,7 +795,6 @@ function createSession(options = {}) {
761
795
  } else if (highlightMode === "transient") {
762
796
  onUpdateOverlay?.(hlCtx);
763
797
  }
764
- store.setState({ hover: ref2 });
765
798
  },
766
799
  unhover() {
767
800
  if (highlightMode === "transient") {
@@ -771,7 +804,6 @@ function createSession(options = {}) {
771
804
  hlCtx.color = null;
772
805
  onHideOverlay?.();
773
806
  }
774
- store.setState({ hover: null });
775
807
  },
776
808
  pin(ref2) {
777
809
  const pinRef = ref2 ?? hlCtx.ref;
@@ -799,14 +831,11 @@ function createSession(options = {}) {
799
831
  };
800
832
  const store = (0, import_vanilla3.createStore)(() => ({
801
833
  ...defaultSnapshot,
802
- hover: overrides.hover ?? null,
803
- selection: overrides.selection ?? null,
804
834
  stack: [],
805
835
  pinnedHighlight: null,
806
- inspectorActive: false,
836
+ mode: "idle",
807
837
  theme: initialPref,
808
838
  resolvedTheme: initialResolved,
809
- ingestActive: overrides.ingestActive ?? false,
810
839
  user: overrides.user ?? null
811
840
  }));
812
841
  nav.subscribe(() => {
@@ -816,29 +845,21 @@ function createSession(options = {}) {
816
845
  }
817
846
  });
818
847
  modeStore.subscribe(() => {
819
- const { inspectorActive } = modeStore.getState();
820
- if (store.getState().inspectorActive !== inspectorActive) {
821
- store.setState({ inspectorActive });
848
+ const { mode } = modeStore.getState();
849
+ if (store.getState().mode !== mode) {
850
+ store.setState({ mode });
822
851
  }
823
852
  });
824
853
  const session = store;
825
854
  session.nav = nav;
826
855
  session.mode = modeStore;
827
856
  session.highlight = highlightActions;
828
- session.select = (ref2) => {
829
- if (sameRef(store.getState().selection, ref2)) return;
830
- store.setState({ selection: ref2 });
831
- };
832
857
  session.setTheme = (theme, resolved) => {
833
858
  const state = store.getState();
834
859
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
835
860
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
836
861
  store.setState({ theme, resolvedTheme: nextResolved });
837
862
  };
838
- session.setIngest = (active) => {
839
- if (store.getState().ingestActive === active) return;
840
- store.setState({ ingestActive: active });
841
- };
842
863
  if (initialStack.length > 0) {
843
864
  modeStore.transition.openPalette();
844
865
  for (let i = 1; i < initialStack.length; i++) {
@@ -1130,6 +1151,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1130
1151
  .relative {
1131
1152
  position: relative;
1132
1153
  }
1154
+ .static {
1155
+ position: static;
1156
+ }
1133
1157
  .inset-0 {
1134
1158
  inset: calc(var(--spacing) * 0);
1135
1159
  }
@@ -1148,9 +1172,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1148
1172
  .right-0 {
1149
1173
  right: calc(var(--spacing) * 0);
1150
1174
  }
1151
- .right-2 {
1152
- right: calc(var(--spacing) * 2);
1153
- }
1154
1175
  .bottom-full {
1155
1176
  bottom: 100%;
1156
1177
  }
@@ -1193,9 +1214,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1193
1214
  .mx-2 {
1194
1215
  margin-inline: calc(var(--spacing) * 2);
1195
1216
  }
1196
- .my-1 {
1197
- margin-block: calc(var(--spacing) * 1);
1198
- }
1199
1217
  .ms-auto {
1200
1218
  margin-inline-start: auto;
1201
1219
  }
@@ -1232,9 +1250,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1232
1250
  .inline-flex {
1233
1251
  display: inline-flex;
1234
1252
  }
1235
- .list-item {
1236
- display: list-item;
1237
- }
1238
1253
  .table {
1239
1254
  display: table;
1240
1255
  }
@@ -1242,10 +1257,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1242
1257
  width: calc(var(--spacing) * 2);
1243
1258
  height: calc(var(--spacing) * 2);
1244
1259
  }
1245
- .size-3 {
1246
- width: calc(var(--spacing) * 3);
1247
- height: calc(var(--spacing) * 3);
1248
- }
1249
1260
  .size-3\\.5 {
1250
1261
  width: calc(var(--spacing) * 3.5);
1251
1262
  height: calc(var(--spacing) * 3.5);
@@ -1303,15 +1314,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1303
1314
  .h-\\[26rem\\] {
1304
1315
  height: 26rem;
1305
1316
  }
1306
- .h-auto {
1307
- height: auto;
1308
- }
1309
1317
  .h-full {
1310
1318
  height: 100%;
1311
1319
  }
1312
- .h-px {
1313
- height: 1px;
1314
- }
1315
1320
  .max-h-32 {
1316
1321
  max-height: calc(var(--spacing) * 32);
1317
1322
  }
@@ -1321,9 +1326,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1321
1326
  .min-h-0 {
1322
1327
  min-height: calc(var(--spacing) * 0);
1323
1328
  }
1324
- .min-h-7 {
1325
- min-height: calc(var(--spacing) * 7);
1326
- }
1327
1329
  .min-h-8 {
1328
1330
  min-height: calc(var(--spacing) * 8);
1329
1331
  }
@@ -1348,9 +1350,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1348
1350
  .w-56 {
1349
1351
  width: calc(var(--spacing) * 56);
1350
1352
  }
1351
- .w-auto {
1352
- width: auto;
1353
- }
1354
1353
  .w-full {
1355
1354
  width: 100%;
1356
1355
  }
@@ -1425,9 +1424,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1425
1424
  .animate-spin {
1426
1425
  animation: var(--animate-spin);
1427
1426
  }
1428
- .cursor-default {
1429
- cursor: default;
1430
- }
1431
1427
  .cursor-pointer {
1432
1428
  cursor: pointer;
1433
1429
  }
@@ -1437,9 +1433,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1437
1433
  .scroll-py-2 {
1438
1434
  scroll-padding-block: calc(var(--spacing) * 2);
1439
1435
  }
1440
- .list-none {
1441
- list-style-type: none;
1442
- }
1443
1436
  .appearance-none {
1444
1437
  appearance: none;
1445
1438
  }
@@ -1461,9 +1454,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1461
1454
  .justify-center {
1462
1455
  justify-content: center;
1463
1456
  }
1464
- .justify-start {
1465
- justify-content: flex-start;
1466
- }
1467
1457
  .gap-0 {
1468
1458
  gap: calc(var(--spacing) * 0);
1469
1459
  }
@@ -1488,9 +1478,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1488
1478
  .gap-6 {
1489
1479
  gap: calc(var(--spacing) * 6);
1490
1480
  }
1491
- .self-start {
1492
- align-self: flex-start;
1493
- }
1494
1481
  .truncate {
1495
1482
  overflow: hidden;
1496
1483
  text-overflow: ellipsis;
@@ -1705,9 +1692,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1705
1692
  .p-6 {
1706
1693
  padding: calc(var(--spacing) * 6);
1707
1694
  }
1708
- .px-0 {
1709
- padding-inline: calc(var(--spacing) * 0);
1710
- }
1711
1695
  .px-1 {
1712
1696
  padding-inline: calc(var(--spacing) * 1);
1713
1697
  }
@@ -1732,9 +1716,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1732
1716
  .px-6 {
1733
1717
  padding-inline: calc(var(--spacing) * 6);
1734
1718
  }
1735
- .py-0 {
1736
- padding-block: calc(var(--spacing) * 0);
1737
- }
1738
1719
  .py-1 {
1739
1720
  padding-block: calc(var(--spacing) * 1);
1740
1721
  }
@@ -1807,9 +1788,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1807
1788
  .text-\\[9px\\] {
1808
1789
  font-size: 9px;
1809
1790
  }
1810
- .text-\\[10px\\] {
1811
- font-size: 10px;
1812
- }
1813
1791
  .text-\\[11px\\] {
1814
1792
  font-size: 11px;
1815
1793
  }
@@ -2049,10 +2027,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2049
2027
  outline-style: var(--tw-outline-style);
2050
2028
  outline-width: 1px;
2051
2029
  }
2052
- .blur {
2053
- --tw-blur: blur(8px);
2054
- 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,);
2055
- }
2056
2030
  .filter {
2057
2031
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
2058
2032
  }
@@ -2218,25 +2192,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2218
2192
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2219
2193
  }
2220
2194
  }
2221
- .focus-within\\:border-ring {
2222
- &:focus-within {
2223
- border-color: var(--ring);
2224
- }
2225
- }
2226
- .focus-within\\:ring-\\[3px\\] {
2227
- &:focus-within {
2228
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2229
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2230
- }
2231
- }
2232
- .focus-within\\:ring-ring\\/30 {
2233
- &:focus-within {
2234
- --tw-ring-color: var(--ring);
2235
- @supports (color: color-mix(in lab, red, red)) {
2236
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2237
- }
2238
- }
2239
- }
2240
2195
  .hover\\:border-destructive\\/30 {
2241
2196
  &:hover {
2242
2197
  @media (hover: hover) {
@@ -2380,11 +2335,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2380
2335
  pointer-events: none;
2381
2336
  }
2382
2337
  }
2383
- .disabled\\:cursor-not-allowed {
2384
- &:disabled {
2385
- cursor: not-allowed;
2386
- }
2387
- }
2388
2338
  .disabled\\:opacity-50 {
2389
2339
  &:disabled {
2390
2340
  opacity: 50%;
@@ -2395,11 +2345,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2395
2345
  opacity: 60%;
2396
2346
  }
2397
2347
  }
2398
- .has-disabled\\:opacity-60 {
2399
- &:has(*:disabled) {
2400
- opacity: 60%;
2401
- }
2402
- }
2403
2348
  .aria-invalid\\:border-destructive\\/40 {
2404
2349
  &[aria-invalid="true"] {
2405
2350
  border-color: var(--destructive);
@@ -2806,32 +2751,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2806
2751
  height: calc(var(--spacing) * 4.5);
2807
2752
  }
2808
2753
  }
2809
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2810
- &>svg {
2811
- pointer-events: none;
2812
- }
2813
- }
2814
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2815
- &>svg {
2816
- margin-inline: calc(var(--spacing) * -0.5);
2817
- }
2818
- }
2819
- .\\[\\&\\>svg\\]\\:shrink-0 {
2820
- &>svg {
2821
- flex-shrink: 0;
2822
- }
2823
- }
2824
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2825
- &>svg:not([class*='opacity-']) {
2826
- opacity: 80%;
2827
- }
2828
- }
2829
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2830
- &>svg:not([class*='size-']) {
2831
- width: calc(var(--spacing) * 4);
2832
- height: calc(var(--spacing) * 4);
2833
- }
2834
- }
2835
2754
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2836
2755
  [data-kbd-nav] & {
2837
2756
  &:focus-within {
@@ -3393,18 +3312,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3393
3312
 
3394
3313
  // src/browser/surface/constants.ts
3395
3314
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3396
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3397
3315
  var Z_BASE = 2147483630;
3398
3316
  var Z_OVERLAY = 2147483635;
3399
3317
  var Z_PIN_LAYER = 2147483640;
3400
3318
  var Z_CHROME = 2147483645;
3401
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3319
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3402
3320
  var UIDEX_ATTR_TO_KIND = [
3403
3321
  ["data-uidex", "element"],
3404
3322
  ["data-uidex-region", "region"],
3405
3323
  ["data-uidex-widget", "widget"],
3406
3324
  ["data-uidex-primitive", "primitive"]
3407
3325
  ];
3326
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3327
+ "element",
3328
+ "region",
3329
+ "widget",
3330
+ "primitive"
3331
+ ]);
3408
3332
 
3409
3333
  // src/browser/surface/host.ts
3410
3334
  function createSurfaceHost(options) {
@@ -3582,7 +3506,7 @@ function createCursorTooltip(deps) {
3582
3506
  // src/browser/surface/inspector.ts
3583
3507
  function entityForRef(ref2, registry) {
3584
3508
  if (registry) {
3585
- const found = registry.get(ref2.kind, ref2.id);
3509
+ const found = registry.get(ref2.kind, ref2.id) ?? registry.matchPattern?.(ref2.kind, ref2.id);
3586
3510
  if (found) return found;
3587
3511
  }
3588
3512
  if (ref2.kind === "route") return { kind: "route", path: ref2.id, page: ref2.id };
@@ -3694,7 +3618,6 @@ function createInspector(options) {
3694
3618
  e.preventDefault();
3695
3619
  e.stopPropagation();
3696
3620
  const match = stack[layerIndex];
3697
- session.select(match.ref);
3698
3621
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3699
3622
  };
3700
3623
  const onContextMenu = (e) => {
@@ -4013,49 +3936,12 @@ function createMenuBar(options) {
4013
3936
  },
4014
3937
  pinIcon
4015
3938
  );
4016
- const commitCycler = el("div", {
4017
- class: "relative z-1 inline-flex items-center gap-0.5",
4018
- attrs: { "data-uidex-menubar-commit-cycler": "" }
4019
- });
4020
- commitCycler.hidden = true;
4021
- const prevIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronLeft);
4022
- prevIcon.setAttribute("class", "size-3");
4023
- prevIcon.setAttribute("aria-hidden", "true");
4024
- const prevBtn = el(
4025
- "button",
4026
- {
4027
- class: BUTTON_CLASS,
4028
- attrs: { type: "button", "aria-label": "Previous commit" },
4029
- style: { width: "18px", height: "18px" }
4030
- },
4031
- prevIcon
4032
- );
4033
- const commitLabel = el("span", {
4034
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4035
- attrs: { "data-uidex-menubar-commit-label": "" }
4036
- });
4037
- const nextIcon = (0, import_lucide3.createElement)(import_lucide3.ChevronRight);
4038
- nextIcon.setAttribute("class", "size-3");
4039
- nextIcon.setAttribute("aria-hidden", "true");
4040
- const nextBtn = el(
4041
- "button",
4042
- {
4043
- class: BUTTON_CLASS,
4044
- attrs: { type: "button", "aria-label": "Next commit" },
4045
- style: { width: "18px", height: "18px" }
4046
- },
4047
- nextIcon
4048
- );
4049
- commitCycler.appendChild(prevBtn);
4050
- commitCycler.appendChild(commitLabel);
4051
- commitCycler.appendChild(nextBtn);
4052
3939
  const pinWrapper = el("div", {
4053
3940
  class: "relative z-1 inline-flex items-center gap-0.5",
4054
3941
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4055
3942
  });
4056
3943
  pinWrapper.hidden = true;
4057
3944
  pinWrapper.appendChild(pinBtn);
4058
- pinWrapper.appendChild(commitCycler);
4059
3945
  root.appendChild(pinWrapper);
4060
3946
  const updatePinUI = () => {
4061
3947
  if (!activePinLayer) {
@@ -4063,16 +3949,7 @@ function createMenuBar(options) {
4063
3949
  return;
4064
3950
  }
4065
3951
  const pinsVisible = activePinLayer.visible;
4066
- const state = activePinLayer.filterState;
4067
- const hasCommits = state.commits.length > 0;
4068
3952
  pinWrapper.hidden = false;
4069
- commitCycler.hidden = !pinsVisible || !hasCommits;
4070
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4071
- commitLabel.textContent = `all (${state.commits.length})`;
4072
- } else {
4073
- const sha = state.commits[state.commitIndex] ?? "";
4074
- commitLabel.textContent = sha.slice(0, 7);
4075
- }
4076
3953
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4077
3954
  };
4078
3955
  pinBtn.addEventListener("click", (e) => {
@@ -4081,14 +3958,6 @@ function createMenuBar(options) {
4081
3958
  activePinLayer.setVisible(!activePinLayer.visible);
4082
3959
  }
4083
3960
  });
4084
- prevBtn.addEventListener("click", (e) => {
4085
- e.stopPropagation();
4086
- activePinLayer?.prevCommit();
4087
- });
4088
- nextBtn.addEventListener("click", (e) => {
4089
- e.stopPropagation();
4090
- activePinLayer?.nextCommit();
4091
- });
4092
3961
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4093
3962
  const presenceIcon = (0, import_lucide3.createElement)(import_lucide3.Users);
4094
3963
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4182,7 +4051,7 @@ function createMenuBar(options) {
4182
4051
  container.appendChild(root);
4183
4052
  const syncButtonStates = () => {
4184
4053
  const state = session.getState();
4185
- const inspectActive = state.inspectorActive;
4054
+ const inspectActive = state.mode === "inspecting";
4186
4055
  inspectBtn.setAttribute(
4187
4056
  "data-uidex-menubar-inspect-active",
4188
4057
  inspectActive ? "true" : "false"
@@ -4301,6 +4170,49 @@ function createMenuBar(options) {
4301
4170
  };
4302
4171
  }
4303
4172
 
4173
+ // src/browser/internal/repositioner.ts
4174
+ function createRepositioner(onReflow) {
4175
+ let rafId = null;
4176
+ let attached = false;
4177
+ const schedule = () => {
4178
+ if (rafId !== null) return;
4179
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4180
+ rafId = null;
4181
+ onReflow();
4182
+ }) : setTimeout(() => {
4183
+ rafId = null;
4184
+ onReflow();
4185
+ }, 0);
4186
+ };
4187
+ const cancel = () => {
4188
+ if (rafId === null) return;
4189
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4190
+ else clearTimeout(rafId);
4191
+ rafId = null;
4192
+ };
4193
+ const onScroll = () => schedule();
4194
+ const onResize = () => schedule();
4195
+ const attach = () => {
4196
+ if (attached) return;
4197
+ attached = true;
4198
+ window.addEventListener("resize", onResize);
4199
+ window.addEventListener("scroll", onScroll, {
4200
+ capture: true,
4201
+ passive: true
4202
+ });
4203
+ };
4204
+ const detach = () => {
4205
+ if (!attached) return;
4206
+ attached = false;
4207
+ window.removeEventListener("resize", onResize);
4208
+ window.removeEventListener("scroll", onScroll, {
4209
+ capture: true
4210
+ });
4211
+ cancel();
4212
+ };
4213
+ return { schedule, cancel, attach, detach };
4214
+ }
4215
+
4304
4216
  // src/browser/surface/overlay.ts
4305
4217
  var DEFAULT_COLOR = "#34d399";
4306
4218
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4368,44 +4280,7 @@ function createOverlay(deps) {
4368
4280
  fillOpacity: DEFAULT_FILL_OPACITY,
4369
4281
  backdrop: false
4370
4282
  };
4371
- let rafId = null;
4372
- let attached = false;
4373
- const schedule = () => {
4374
- if (rafId !== null) return;
4375
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4376
- rafId = null;
4377
- updatePosition();
4378
- }) : setTimeout(() => {
4379
- rafId = null;
4380
- updatePosition();
4381
- }, 0);
4382
- };
4383
- const cancelSchedule = () => {
4384
- if (rafId === null) return;
4385
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4386
- else clearTimeout(rafId);
4387
- rafId = null;
4388
- };
4389
- const onScroll = () => schedule();
4390
- const onResize = () => schedule();
4391
- const attach = () => {
4392
- if (attached) return;
4393
- attached = true;
4394
- window.addEventListener("resize", onResize);
4395
- window.addEventListener("scroll", onScroll, {
4396
- capture: true,
4397
- passive: true
4398
- });
4399
- };
4400
- const detach = () => {
4401
- if (!attached) return;
4402
- attached = false;
4403
- window.removeEventListener("resize", onResize);
4404
- window.removeEventListener("scroll", onScroll, {
4405
- capture: true
4406
- });
4407
- cancelSchedule();
4408
- };
4283
+ const repositioner = createRepositioner(() => updatePosition());
4409
4284
  function updatePosition() {
4410
4285
  if (!target) return;
4411
4286
  const rect = target.getBoundingClientRect();
@@ -4469,16 +4344,16 @@ function createOverlay(deps) {
4469
4344
  box.offsetHeight;
4470
4345
  }
4471
4346
  box.style.opacity = "1";
4472
- attach();
4347
+ repositioner.attach();
4473
4348
  },
4474
4349
  hide() {
4475
4350
  target = null;
4476
4351
  box.style.opacity = "0";
4477
4352
  backdrop.style.opacity = "0";
4478
- detach();
4353
+ repositioner.detach();
4479
4354
  },
4480
4355
  destroy() {
4481
- detach();
4356
+ repositioner.detach();
4482
4357
  box.remove();
4483
4358
  backdrop.remove();
4484
4359
  target = null;
@@ -4595,8 +4470,7 @@ function createSurfaceShell(options) {
4595
4470
  const overlay = createOverlay({ container: host.shadowRoot });
4596
4471
  cleanup.add(overlay);
4597
4472
  const tooltip = createCursorTooltip({
4598
- container: host.chromeEl,
4599
- session: options.session
4473
+ container: host.chromeEl
4600
4474
  });
4601
4475
  cleanup.add(tooltip);
4602
4476
  const afterHover = options.inspector?.onAfterHover;
@@ -4780,9 +4654,6 @@ function createPinLayer(options) {
4780
4654
  const seenIds = /* @__PURE__ */ new Set();
4781
4655
  const byComp = /* @__PURE__ */ new Map();
4782
4656
  const indicators = /* @__PURE__ */ new Map();
4783
- let filter = { branch: null, commit: null };
4784
- let commits = [];
4785
- let commitIndex = -1;
4786
4657
  const filterCbs = /* @__PURE__ */ new Set();
4787
4658
  const notifyFilter = () => {
4788
4659
  for (const cb of filterCbs) cb();
@@ -4797,8 +4668,6 @@ function createPinLayer(options) {
4797
4668
  }
4798
4669
  };
4799
4670
  const rerender = () => {
4800
- commits = [];
4801
- commitIndex = -1;
4802
4671
  rebuildFiltered();
4803
4672
  for (const id of Array.from(indicators.keys())) {
4804
4673
  if (!byComp.has(id)) removeIndicator(id);
@@ -4807,45 +4676,8 @@ function createPinLayer(options) {
4807
4676
  notifyFilter();
4808
4677
  onPinsChanged?.();
4809
4678
  };
4810
- let rafId = null;
4811
- let winAttached = false;
4812
4679
  let obs = null;
4813
- const schedulePos = () => {
4814
- if (rafId !== null) return;
4815
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4816
- rafId = null;
4817
- posAll();
4818
- }) : setTimeout(() => {
4819
- rafId = null;
4820
- posAll();
4821
- }, 0);
4822
- };
4823
- const cancelPos = () => {
4824
- if (rafId === null) return;
4825
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4826
- else clearTimeout(rafId);
4827
- rafId = null;
4828
- };
4829
- const onScroll = () => schedulePos();
4830
- const onResize = () => schedulePos();
4831
- const attachWin = () => {
4832
- if (winAttached) return;
4833
- winAttached = true;
4834
- window.addEventListener("scroll", onScroll, {
4835
- capture: true,
4836
- passive: true
4837
- });
4838
- window.addEventListener("resize", onResize);
4839
- };
4840
- const detachWin = () => {
4841
- if (!winAttached) return;
4842
- winAttached = false;
4843
- window.removeEventListener("scroll", onScroll, {
4844
- capture: true
4845
- });
4846
- window.removeEventListener("resize", onResize);
4847
- cancelPos();
4848
- };
4680
+ const repositioner = createRepositioner(() => posAll());
4849
4681
  const attachObs = () => {
4850
4682
  if (obs || typeof MutationObserver === "undefined") return;
4851
4683
  obs = new MutationObserver((recs) => {
@@ -4869,7 +4701,7 @@ function createPinLayer(options) {
4869
4701
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4870
4702
  changed = true;
4871
4703
  }
4872
- if (changed) schedulePos();
4704
+ if (changed) repositioner.schedule();
4873
4705
  };
4874
4706
  const expand = (st) => {
4875
4707
  if (st.expanded) return;
@@ -5111,7 +4943,7 @@ function createPinLayer(options) {
5111
4943
  if (!st) {
5112
4944
  st = buildIndicator(componentId);
5113
4945
  indicators.set(componentId, st);
5114
- attachWin();
4946
+ repositioner.attach();
5115
4947
  attachObs();
5116
4948
  }
5117
4949
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5125,7 +4957,7 @@ function createPinLayer(options) {
5125
4957
  st.wrap.remove();
5126
4958
  indicators.delete(componentId);
5127
4959
  if (indicators.size === 0) {
5128
- detachWin();
4960
+ repositioner.detach();
5129
4961
  detachObs();
5130
4962
  }
5131
4963
  };
@@ -5160,17 +4992,22 @@ function createPinLayer(options) {
5160
4992
  seenIds.clear();
5161
4993
  byComp.clear();
5162
4994
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5163
- commits = [];
5164
- commitIndex = -1;
5165
4995
  notifyFilter();
5166
4996
  },
5167
4997
  getPinsForElement: (id) => byComp.get(id) ?? [],
5168
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5169
4998
  getAllPins: () => allPins.slice(),
5170
4999
  attachChannel(channel) {
5171
- return channel.onPin((pin) => {
5000
+ const offPin = channel.onPin((pin) => {
5172
5001
  layer.addPin(pin);
5173
5002
  });
5003
+ const offArchived = channel.onPinArchived?.((reportId) => {
5004
+ layer.removePin(reportId);
5005
+ }) ?? (() => {
5006
+ });
5007
+ return () => {
5008
+ offPin();
5009
+ offArchived();
5010
+ };
5174
5011
  },
5175
5012
  attachCloud(opts) {
5176
5013
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5201,39 +5038,6 @@ function createPinLayer(options) {
5201
5038
  async refresh() {
5202
5039
  if (activeRefresh) await activeRefresh();
5203
5040
  },
5204
- get filterState() {
5205
- return {
5206
- branch: filter.branch,
5207
- commit: filter.commit,
5208
- commits,
5209
- commitIndex
5210
- };
5211
- },
5212
- setFilter(next) {
5213
- filter = {
5214
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5215
- commit: next.commit !== void 0 ? next.commit : filter.commit
5216
- };
5217
- rerender();
5218
- },
5219
- nextCommit() {
5220
- if (!commits.length) return;
5221
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5222
- filter = {
5223
- ...filter,
5224
- commit: commitIndex === -1 ? null : commits[commitIndex]
5225
- };
5226
- rerender();
5227
- },
5228
- prevCommit() {
5229
- if (!commits.length) return;
5230
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5231
- filter = {
5232
- ...filter,
5233
- commit: commitIndex === -1 ? null : commits[commitIndex]
5234
- };
5235
- rerender();
5236
- },
5237
5041
  onFilterChange(cb) {
5238
5042
  filterCbs.add(cb);
5239
5043
  return () => {
@@ -5252,7 +5056,7 @@ function createPinLayer(options) {
5252
5056
  },
5253
5057
  destroy() {
5254
5058
  layer.clear();
5255
- detachWin();
5059
+ repositioner.detach();
5256
5060
  detachObs();
5257
5061
  layerEl.remove();
5258
5062
  activeRefresh = null;
@@ -5295,9 +5099,11 @@ function createRouter(options) {
5295
5099
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5296
5100
  throw new ViewValidationError("View must have a non-empty string id");
5297
5101
  }
5298
- if (typeof view.surface !== "function") {
5102
+ const hasSurface = typeof view.surface === "function";
5103
+ const hasRender = typeof view.render === "function";
5104
+ if (!hasSurface && !hasRender) {
5299
5105
  throw new ViewValidationError(
5300
- `View ${view.id}: 'surface' must be a function`
5106
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5301
5107
  );
5302
5108
  }
5303
5109
  if (!view.matches && !view.palette) {
@@ -5355,7 +5161,6 @@ function createRouter(options) {
5355
5161
  if (idx >= 0) recentRefs.splice(idx, 1);
5356
5162
  recentRefs.unshift(ref2);
5357
5163
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5358
- options.session.select(ref2);
5359
5164
  const entry = { id: match.view.id, ref: ref2 };
5360
5165
  const { mode } = options.session.mode.getState();
5361
5166
  if (mode === "idle" || mode === "inspecting") {
@@ -5410,70 +5215,20 @@ function detectDev() {
5410
5215
 
5411
5216
  // src/browser/internal/lit.ts
5412
5217
  var import_lit_html2 = require("lit-html");
5413
- var import_repeat = require("lit-html/directives/repeat.js");
5414
5218
  var import_ref = require("lit-html/directives/ref.js");
5415
- var import_class_map = require("lit-html/directives/class-map.js");
5416
5219
 
5417
- // src/browser/internal/apply-props.ts
5418
- function applyProps(node, props) {
5419
- const removers = [];
5420
- for (const [key, rawValue] of Object.entries(props)) {
5421
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5422
- if (key.startsWith("on") && typeof rawValue === "function") {
5423
- const eventName = key.slice(2).toLowerCase();
5424
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5425
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5426
- const listener = rawValue;
5427
- node.addEventListener(effectiveEvent, listener);
5428
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5429
- continue;
5430
- }
5431
- if (rawValue === void 0 || rawValue === null) {
5432
- node.removeAttribute(key);
5433
- continue;
5434
- }
5435
- if (typeof rawValue === "boolean") {
5436
- if (rawValue) node.setAttribute(key, "");
5437
- else node.removeAttribute(key);
5438
- continue;
5439
- }
5440
- if (key === "style" && typeof rawValue === "object") {
5441
- Object.assign(
5442
- node.style,
5443
- rawValue
5444
- );
5445
- continue;
5446
- }
5447
- if (key === "className" || key === "class") {
5448
- node.setAttribute("class", String(rawValue));
5449
- continue;
5450
- }
5451
- if (key === "htmlFor") {
5452
- node.setAttribute("for", String(rawValue));
5453
- continue;
5454
- }
5455
- if (key === "tabIndex") {
5456
- ;
5457
- node.tabIndex = Number(rawValue);
5458
- continue;
5459
- }
5460
- node.setAttribute(key, String(rawValue));
5461
- }
5462
- return () => removers.forEach((fn) => fn());
5463
- }
5464
-
5465
- // src/browser/internal/lit-icon.ts
5466
- var import_lit_html = require("lit-html");
5467
- var import_directive = require("lit-html/directive.js");
5468
- var import_lucide5 = require("lucide");
5469
- var LucideIconDirective = class extends import_directive.Directive {
5470
- _icon;
5471
- _class;
5472
- _el;
5473
- constructor(partInfo) {
5474
- super(partInfo);
5475
- if (partInfo.type !== import_directive.PartType.CHILD) {
5476
- throw new Error("icon() can only be used in a child position");
5220
+ // src/browser/internal/lit-icon.ts
5221
+ var import_lit_html = require("lit-html");
5222
+ var import_directive = require("lit-html/directive.js");
5223
+ var import_lucide5 = require("lucide");
5224
+ var LucideIconDirective = class extends import_directive.Directive {
5225
+ _icon;
5226
+ _class;
5227
+ _el;
5228
+ constructor(partInfo) {
5229
+ super(partInfo);
5230
+ if (partInfo.type !== import_directive.PartType.CHILD) {
5231
+ throw new Error("icon() can only be used in a child position");
5477
5232
  }
5478
5233
  }
5479
5234
  update(_part, [iconNode, className]) {
@@ -5494,6 +5249,16 @@ var LucideIconDirective = class extends import_directive.Directive {
5494
5249
  };
5495
5250
  var icon = (0, import_directive.directive)(LucideIconDirective);
5496
5251
 
5252
+ // src/browser/views/primitives/chip.ts
5253
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5254
+ function createChip(options = {}, children = []) {
5255
+ const { class: extra, ...rest } = options;
5256
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5257
+ }
5258
+
5259
+ // src/browser/views/primitives/kind-icon.ts
5260
+ var import_lucide6 = require("lucide");
5261
+
5497
5262
  // src/browser/ui/cva.ts
5498
5263
  function cva(base, config = {}) {
5499
5264
  return (props = {}) => {
@@ -5532,17 +5297,15 @@ var badgeVariants = cva(badgeBase, {
5532
5297
  }
5533
5298
  }
5534
5299
  });
5535
-
5536
- // src/browser/views/primitives/chip.ts
5537
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5538
- function createChip(options = {}, children = []) {
5539
- const { class: extra, ...rest } = options;
5540
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5300
+ function badgeTpl(content, options = {}) {
5301
+ const { variant, size, class: extra } = options;
5302
+ return import_lit_html2.html`
5303
+ <span class=${cn(badgeVariants({ variant, size }), extra)} data-slot="badge"
5304
+ >${content}</span
5305
+ >
5306
+ `;
5541
5307
  }
5542
5308
 
5543
- // src/browser/views/primitives/kind-icon.ts
5544
- var import_lucide6 = require("lucide");
5545
-
5546
5309
  // src/browser/views/primitives/icon-tile.ts
5547
5310
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5548
5311
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6034,6 +5797,54 @@ function createScrollArea(props = {}) {
6034
5797
  );
6035
5798
  }
6036
5799
 
5800
+ // src/browser/internal/apply-props.ts
5801
+ function applyProps(node, props) {
5802
+ const removers = [];
5803
+ for (const [key, rawValue] of Object.entries(props)) {
5804
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5805
+ if (key.startsWith("on") && typeof rawValue === "function") {
5806
+ const eventName = key.slice(2).toLowerCase();
5807
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5808
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5809
+ const listener = rawValue;
5810
+ node.addEventListener(effectiveEvent, listener);
5811
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5812
+ continue;
5813
+ }
5814
+ if (rawValue === void 0 || rawValue === null) {
5815
+ node.removeAttribute(key);
5816
+ continue;
5817
+ }
5818
+ if (typeof rawValue === "boolean") {
5819
+ if (rawValue) node.setAttribute(key, "");
5820
+ else node.removeAttribute(key);
5821
+ continue;
5822
+ }
5823
+ if (key === "style" && typeof rawValue === "object") {
5824
+ Object.assign(
5825
+ node.style,
5826
+ rawValue
5827
+ );
5828
+ continue;
5829
+ }
5830
+ if (key === "className" || key === "class") {
5831
+ node.setAttribute("class", String(rawValue));
5832
+ continue;
5833
+ }
5834
+ if (key === "htmlFor") {
5835
+ node.setAttribute("for", String(rawValue));
5836
+ continue;
5837
+ }
5838
+ if (key === "tabIndex") {
5839
+ ;
5840
+ node.tabIndex = Number(rawValue);
5841
+ continue;
5842
+ }
5843
+ node.setAttribute(key, String(rawValue));
5844
+ }
5845
+ return () => removers.forEach((fn) => fn());
5846
+ }
5847
+
6037
5848
  // src/browser/views/builder/spread-props.ts
6038
5849
  function spreadProps(node, props) {
6039
5850
  return applyProps(node, props);
@@ -6053,9 +5864,6 @@ function createPersistentSpreads() {
6053
5864
  };
6054
5865
  }
6055
5866
 
6056
- // src/browser/views/render/detail.ts
6057
- var import_lucide7 = require("lucide");
6058
-
6059
5867
  // src/browser/internal/arrow-nav.ts
6060
5868
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6061
5869
  function bindArrowNav(options) {
@@ -6127,30 +5935,6 @@ function focusItem(items, idx) {
6127
5935
  items[idx].focus();
6128
5936
  }
6129
5937
 
6130
- // src/browser/views/builder/filter.ts
6131
- function normalizeQuery(query) {
6132
- return query.trim().toLowerCase();
6133
- }
6134
- function matchesQuery(haystack, query) {
6135
- const q = normalizeQuery(query);
6136
- if (!q) return true;
6137
- return haystack.toLowerCase().includes(q);
6138
- }
6139
- function filterEntities(entities, query) {
6140
- const q = normalizeQuery(query);
6141
- if (!q) return entities;
6142
- return entities.filter((e) => {
6143
- const name = displayName(e).toLowerCase();
6144
- const id = entityKey(e).toLowerCase();
6145
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6146
- });
6147
- }
6148
- function filterIds(ids, query) {
6149
- const q = normalizeQuery(query);
6150
- if (!q) return ids;
6151
- return ids.filter((id) => id.toLowerCase().includes(q));
6152
- }
6153
-
6154
5938
  // src/browser/views/labels.ts
6155
5939
  var SECTION_LABELS = {
6156
5940
  acceptance: "Acceptance criteria",
@@ -6175,171 +5959,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6175
5959
  var LIST_ITEM_INTERACTIVE_CLASS = "uidex-item-interactive [[data-kbd-nav]_&]:focus:bg-accent [[data-kbd-nav]_&]:focus:text-accent-foreground [[data-kbd-nav]_&]:focus-within:bg-accent [[data-kbd-nav]_&]:focus-within:text-accent-foreground";
6176
5960
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6177
5961
 
6178
- // src/browser/ui/button.ts
6179
- 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";
6180
- var buttonVariants = cva(buttonBase, {
6181
- defaultVariants: { size: "default", variant: "default" },
6182
- variants: {
6183
- size: {
6184
- default: "h-8 px-3",
6185
- sm: "h-7 gap-1.5 px-2.5",
6186
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6187
- lg: "h-9 px-3.5",
6188
- xl: "h-10 px-4 text-base",
6189
- icon: "size-8",
6190
- "icon-sm": "size-7",
6191
- "icon-lg": "size-9",
6192
- "icon-xs": "size-6 rounded-md"
6193
- },
6194
- variant: {
6195
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6196
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6197
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6198
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6199
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6200
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6201
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6202
- }
6203
- }
6204
- });
6205
-
6206
- // src/browser/views/primitives/entity-presence.ts
6207
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6208
- "element",
6209
- "region",
6210
- "widget",
6211
- "primitive"
6212
- ]);
6213
- var TOUCH_RESOLVE_KINDS = [
6214
- "element",
6215
- "widget",
6216
- "region",
6217
- "primitive"
6218
- ];
6219
- function isAbsentFromPage(ref2, registry) {
6220
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6221
- return !resolveEntityElement(ref2);
6222
- }
6223
- if (ref2.kind === "flow" && registry) {
6224
- const flow = registry.get("flow", ref2.id);
6225
- if (!flow) return true;
6226
- for (const touchId of flow.touches) {
6227
- for (const kind of TOUCH_RESOLVE_KINDS) {
6228
- const entity = registry.get(kind, touchId);
6229
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6230
- }
6231
- }
6232
- return true;
6233
- }
6234
- return false;
6235
- }
6236
-
6237
- // src/browser/ui/kbd.ts
6238
- 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";
6239
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6240
- function createCommandShortcut(options = {}, children = []) {
6241
- const { class: extra, attrs, ...rest } = options;
6242
- return el(
6243
- "kbd",
6244
- {
6245
- ...rest,
6246
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6247
- attrs: { "data-slot": "command-shortcut", ...attrs }
6248
- },
6249
- children
6250
- );
6251
- }
6252
- function kbdTpl(text, className) {
6253
- return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6254
- >${text}</kbd
6255
- >`;
6256
- }
6257
- function commandShortcutTpl(text, className) {
6258
- return import_lit_html2.html`<kbd
6259
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6260
- data-slot="command-shortcut"
6261
- >${text}</kbd
6262
- >`;
6263
- }
6264
-
6265
- // src/browser/views/primitives/row.ts
6266
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6267
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6268
- function fillRowWithHandle(host, content) {
6269
- if (content.leading) host.append(content.leading);
6270
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6271
- host.append(label);
6272
- if (content.subtitle) {
6273
- host.append(
6274
- el("span", {
6275
- class: SUBTITLE_CLASS,
6276
- attrs: { "data-uidex-row-subtitle": "" },
6277
- text: content.subtitle
6278
- })
6279
- );
6280
- }
6281
- if (content.trailing != null) {
6282
- host.append(
6283
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6284
- );
6285
- }
6286
- return { label };
6287
- }
6288
- function rowTpl(content) {
6289
- return import_lit_html2.html`
6290
- ${content.leading ?? import_lit_html2.nothing}
6291
- <span class=${LABEL_CLASS}>${content.label}</span>
6292
- ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6293
- >${content.subtitle}</span
6294
- >` : import_lit_html2.nothing}
6295
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6296
- `;
6297
- }
6298
-
6299
- // src/browser/views/primitives/entity-link.ts
6300
- var ACTION_CLASS = cn(
6301
- LIST_ITEM_CLASS,
6302
- LIST_ITEM_INTERACTIVE_CLASS,
6303
- "w-full cursor-pointer text-left font-normal"
6304
- );
6305
- function entityLinkTpl(options) {
6306
- const { ctx, target, label, leading, class: extraClass } = options;
6307
- const absent = isAbsentFromPage(target);
6308
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6309
- const onClick = () => ctx.views.navigate(target);
6310
- const preview = () => {
6311
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6312
- };
6313
- const restoreParent = () => {
6314
- if (ctx.ref) {
6315
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6316
- } else {
6317
- ctx.highlight.hide();
6318
- }
6319
- };
6320
- return import_lit_html2.html`
6321
- <button
6322
- type="button"
6323
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6324
- data-slot="list-item-button"
6325
- data-uidex-entity-link
6326
- data-uidex-ref-kind=${target.kind}
6327
- data-uidex-ref-id=${target.id}
6328
- title=${absent ? "Not on this page" : ""}
6329
- @click=${onClick}
6330
- @mouseenter=${preview}
6331
- @mouseleave=${restoreParent}
6332
- @focus=${preview}
6333
- @blur=${restoreParent}
6334
- >
6335
- ${rowTpl({
6336
- leading,
6337
- label: resolvedLabel
6338
- })}
6339
- </button>
6340
- `;
6341
- }
6342
-
6343
5962
  // src/browser/views/primitives/text.ts
6344
5963
  var MUTED_CLASS = "text-sm text-muted-foreground";
6345
5964
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6351,7 +5970,8 @@ function headingTpl(text, className) {
6351
5970
  return import_lit_html2.html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6352
5971
  }
6353
5972
 
6354
- // src/browser/views/render/detail.ts
5973
+ // src/browser/views/render/detail-actions.ts
5974
+ var import_lucide7 = require("lucide");
6355
5975
  var ICON_MAP = {
6356
5976
  "archive-x": import_lucide7.ArchiveX,
6357
5977
  copy: import_lucide7.Copy,
@@ -6367,23 +5987,6 @@ var ICON_MAP = {
6367
5987
  function iconFor(icon2) {
6368
5988
  return icon2 ? ICON_MAP[icon2] : null;
6369
5989
  }
6370
- function subtitleTpl(subtitle) {
6371
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6372
- return import_lit_html2.html`<p
6373
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6374
- data-uidex-detail-subtitle
6375
- >
6376
- ${text}
6377
- </p>`;
6378
- }
6379
- function notFoundTpl(ref2) {
6380
- return import_lit_html2.html`<p
6381
- class=${cn("text-muted-foreground text-sm", "p-4")}
6382
- data-uidex-detail-missing
6383
- >
6384
- ${ref2.kind}: ${ref2.id} not found in registry
6385
- </p>`;
6386
- }
6387
5990
  function renderActions(actions, ctx, root, heading) {
6388
5991
  const cleanups = [];
6389
5992
  const buttons = [];
@@ -6494,6 +6097,166 @@ function renderActions(actions, ctx, root, heading) {
6494
6097
  }
6495
6098
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6496
6099
  }
6100
+
6101
+ // src/browser/views/render/detail-sections.ts
6102
+ var import_static = require("lit-html/static.js");
6103
+
6104
+ // src/browser/views/builder/filter.ts
6105
+ function normalizeQuery(query) {
6106
+ return query.trim().toLowerCase();
6107
+ }
6108
+ function matchesQuery(haystack, query) {
6109
+ const q = normalizeQuery(query);
6110
+ if (!q) return true;
6111
+ return haystack.toLowerCase().includes(q);
6112
+ }
6113
+ function filterEntities(entities, query) {
6114
+ const q = normalizeQuery(query);
6115
+ if (!q) return entities;
6116
+ return entities.filter((e) => {
6117
+ const name = displayName(e).toLowerCase();
6118
+ const id = entityKey(e).toLowerCase();
6119
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6120
+ });
6121
+ }
6122
+ function filterIds(ids, query) {
6123
+ const q = normalizeQuery(query);
6124
+ if (!q) return ids;
6125
+ return ids.filter((id) => id.toLowerCase().includes(q));
6126
+ }
6127
+
6128
+ // src/browser/views/primitives/entity-presence.ts
6129
+ var TOUCH_RESOLVE_KINDS = [
6130
+ "element",
6131
+ "widget",
6132
+ "region",
6133
+ "primitive"
6134
+ ];
6135
+ function isAbsentFromPage(ref2, registry) {
6136
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6137
+ return !resolveEntityElement(ref2);
6138
+ }
6139
+ if (ref2.kind === "flow" && registry) {
6140
+ const flow = registry.get("flow", ref2.id);
6141
+ if (!flow) return true;
6142
+ for (const touchId of flow.touches) {
6143
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6144
+ const entity = registry.get(kind, touchId);
6145
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6146
+ }
6147
+ }
6148
+ return true;
6149
+ }
6150
+ return false;
6151
+ }
6152
+
6153
+ // src/browser/ui/kbd.ts
6154
+ var KBD_CLASS = "bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3";
6155
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6156
+ function createCommandShortcut(options = {}, children = []) {
6157
+ const { class: extra, attrs, ...rest } = options;
6158
+ return el(
6159
+ "kbd",
6160
+ {
6161
+ ...rest,
6162
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6163
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6164
+ },
6165
+ children
6166
+ );
6167
+ }
6168
+ function kbdTpl(text, className) {
6169
+ return import_lit_html2.html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6170
+ >${text}</kbd
6171
+ >`;
6172
+ }
6173
+ function commandShortcutTpl(text, className) {
6174
+ return import_lit_html2.html`<kbd
6175
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6176
+ data-slot="command-shortcut"
6177
+ >${text}</kbd
6178
+ >`;
6179
+ }
6180
+
6181
+ // src/browser/views/primitives/row.ts
6182
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6183
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6184
+ function fillRowWithHandle(host, content) {
6185
+ if (content.leading) host.append(content.leading);
6186
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6187
+ host.append(label);
6188
+ if (content.subtitle) {
6189
+ host.append(
6190
+ el("span", {
6191
+ class: SUBTITLE_CLASS,
6192
+ attrs: { "data-uidex-row-subtitle": "" },
6193
+ text: content.subtitle
6194
+ })
6195
+ );
6196
+ }
6197
+ if (content.trailing != null) {
6198
+ host.append(
6199
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6200
+ );
6201
+ }
6202
+ return { label };
6203
+ }
6204
+ function rowTpl(content) {
6205
+ return import_lit_html2.html`
6206
+ ${content.leading ?? import_lit_html2.nothing}
6207
+ <span class=${LABEL_CLASS}>${content.label}</span>
6208
+ ${content.subtitle ? import_lit_html2.html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6209
+ >${content.subtitle}</span
6210
+ >` : import_lit_html2.nothing}
6211
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : import_lit_html2.nothing}
6212
+ `;
6213
+ }
6214
+
6215
+ // src/browser/views/primitives/entity-link.ts
6216
+ var ACTION_CLASS = cn(
6217
+ LIST_ITEM_CLASS,
6218
+ LIST_ITEM_INTERACTIVE_CLASS,
6219
+ "w-full cursor-pointer text-left font-normal"
6220
+ );
6221
+ function entityLinkTpl(options) {
6222
+ const { ctx, target, label, leading, class: extraClass } = options;
6223
+ const absent = isAbsentFromPage(target);
6224
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6225
+ const onClick = () => ctx.views.navigate(target);
6226
+ const preview = () => {
6227
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6228
+ };
6229
+ const restoreParent = () => {
6230
+ if (ctx.ref) {
6231
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6232
+ } else {
6233
+ ctx.highlight.hide();
6234
+ }
6235
+ };
6236
+ return import_lit_html2.html`
6237
+ <button
6238
+ type="button"
6239
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6240
+ data-slot="list-item-button"
6241
+ data-uidex-entity-link
6242
+ data-uidex-ref-kind=${target.kind}
6243
+ data-uidex-ref-id=${target.id}
6244
+ title=${absent ? "Not on this page" : ""}
6245
+ @click=${onClick}
6246
+ @mouseenter=${preview}
6247
+ @mouseleave=${restoreParent}
6248
+ @focus=${preview}
6249
+ @blur=${restoreParent}
6250
+ >
6251
+ ${rowTpl({
6252
+ leading,
6253
+ label: resolvedLabel
6254
+ })}
6255
+ </button>
6256
+ `;
6257
+ }
6258
+
6259
+ // src/browser/views/render/detail-sections.ts
6497
6260
  function entityListItems(ctx, entities) {
6498
6261
  return import_lit_html2.html`${entities.map((entity) => {
6499
6262
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6507,71 +6270,29 @@ function entityListItems(ctx, entities) {
6507
6270
  </li>`;
6508
6271
  })}`;
6509
6272
  }
6510
- function composesListTpl(ctx, label, entities) {
6511
- if (entities.length === 0) return null;
6512
- return import_lit_html2.html`
6513
- <section class="flex flex-col" data-uidex-detail-composes>
6514
- ${headingTpl(label)}
6515
- <ul class="flex flex-col">
6516
- ${entityListItems(ctx, entities)}
6517
- </ul>
6518
- </section>
6519
- `;
6520
- }
6521
- function usedByListTpl(ctx, label, entities) {
6522
- if (entities.length === 0) return null;
6523
- return import_lit_html2.html`
6524
- <section class="flex flex-col" data-uidex-detail-used-by>
6525
- ${headingTpl(label)}
6526
- <ul class="flex flex-col">
6527
- ${entityListItems(ctx, entities)}
6528
- </ul>
6529
- </section>
6530
- `;
6531
- }
6532
- function flowListTpl(ctx, flows) {
6533
- if (flows.length === 0) return null;
6534
- return import_lit_html2.html`
6535
- <section class="flex flex-col" data-uidex-detail-flows>
6536
- ${headingTpl(SECTION_LABELS.flows)}
6537
- <ul class="flex flex-col">
6538
- ${flows.map(
6539
- (flow) => import_lit_html2.html`<li>
6540
- ${entityLinkTpl({
6541
- ctx,
6542
- target: { kind: "flow", id: flow.id },
6543
- label: displayName(flow),
6544
- leading: kindIconTileTpl("flow")
6545
- })}
6546
- </li>`
6547
- )}
6548
- </ul>
6549
- </section>
6550
- `;
6551
- }
6552
- function touchesTpl(ctx, entities, query) {
6273
+ var DETAIL_SECTION_ATTRS = {
6274
+ composes: import_static.literal`data-uidex-detail-composes`,
6275
+ "used-by": import_static.literal`data-uidex-detail-used-by`,
6276
+ flows: import_static.literal`data-uidex-detail-flows`,
6277
+ touches: import_static.literal`data-uidex-detail-touches`
6278
+ };
6279
+ function entitySectionTpl(ctx, opts) {
6280
+ const { label, entities, dataAttr, emptyText } = opts;
6281
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6553
6282
  if (entities.length === 0) {
6554
- return import_lit_html2.html`
6555
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6556
- ${headingTpl(SECTION_LABELS.touches)}
6557
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6283
+ if (emptyText === void 0) return null;
6284
+ return import_static.html`
6285
+ <section class="flex flex-col gap-2" ${attr}>
6286
+ ${headingTpl(label)}
6287
+ ${mutedTextTpl(emptyText)}
6558
6288
  </section>
6559
6289
  `;
6560
6290
  }
6561
- return import_lit_html2.html`
6562
- <section class="flex flex-col" data-uidex-detail-touches>
6563
- ${headingTpl(SECTION_LABELS.touches)}
6291
+ return import_static.html`
6292
+ <section class="flex flex-col" ${attr}>
6293
+ ${headingTpl(label)}
6564
6294
  <ul class="flex flex-col">
6565
- ${entities.map(
6566
- (entity) => import_lit_html2.html`<li>
6567
- ${entityLinkTpl({
6568
- ctx,
6569
- target: { kind: entity.kind, id: entityKey(entity) },
6570
- label: displayName(entity),
6571
- leading: kindIconTileTpl(entity.kind)
6572
- })}
6573
- </li>`
6574
- )}
6295
+ ${entityListItems(ctx, entities)}
6575
6296
  </ul>
6576
6297
  </section>
6577
6298
  `;
@@ -6728,6 +6449,20 @@ function screenshotTpl(url) {
6728
6449
  </section>
6729
6450
  `;
6730
6451
  }
6452
+ function lazyScreenshotEl(load) {
6453
+ const holder = document.createElement("div");
6454
+ holder.hidden = true;
6455
+ void load().then(
6456
+ (url) => {
6457
+ if (!url) return;
6458
+ (0, import_lit_html2.render)(screenshotTpl(url), holder);
6459
+ holder.hidden = false;
6460
+ },
6461
+ () => {
6462
+ }
6463
+ );
6464
+ return holder;
6465
+ }
6731
6466
  function metadataListTpl(entries) {
6732
6467
  return import_lit_html2.html`
6733
6468
  <div
@@ -6758,28 +6493,30 @@ function sectionTpl(section, ctx, query) {
6758
6493
  return acceptanceChecklistTpl(section.items);
6759
6494
  }
6760
6495
  case "composes":
6761
- return composesListTpl(
6762
- ctx,
6763
- section.label,
6764
- section.filterable ? filterEntities(section.entities, query) : section.entities
6765
- );
6496
+ return entitySectionTpl(ctx, {
6497
+ label: section.label,
6498
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6499
+ dataAttr: "composes"
6500
+ });
6766
6501
  case "used-by":
6767
- return usedByListTpl(
6768
- ctx,
6769
- section.label,
6770
- section.filterable ? filterEntities(section.entities, query) : section.entities
6771
- );
6502
+ return entitySectionTpl(ctx, {
6503
+ label: section.label,
6504
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6505
+ dataAttr: "used-by"
6506
+ });
6772
6507
  case "flows":
6773
- return flowListTpl(
6774
- ctx,
6775
- section.filterable ? filterEntities(section.flows, query) : section.flows
6776
- );
6508
+ return entitySectionTpl(ctx, {
6509
+ label: SECTION_LABELS.flows,
6510
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6511
+ dataAttr: "flows"
6512
+ });
6777
6513
  case "touches":
6778
- return touchesTpl(
6779
- ctx,
6780
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6781
- query
6782
- );
6514
+ return entitySectionTpl(ctx, {
6515
+ label: SECTION_LABELS.touches,
6516
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6517
+ dataAttr: "touches",
6518
+ emptyText: query ? "No matches" : "No entities touched"
6519
+ });
6783
6520
  case "steps":
6784
6521
  return stepsTpl(
6785
6522
  ctx,
@@ -6791,11 +6528,32 @@ function sectionTpl(section, ctx, query) {
6791
6528
  section.filterable ? filterIds(section.paths, query) : section.paths
6792
6529
  );
6793
6530
  case "screenshot":
6794
- return screenshotTpl(section.url);
6531
+ if (section.url) return screenshotTpl(section.url);
6532
+ if (section.load) return import_lit_html2.html`${lazyScreenshotEl(section.load)}`;
6533
+ return null;
6795
6534
  case "metadata":
6796
6535
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6797
6536
  }
6798
6537
  }
6538
+
6539
+ // src/browser/views/render/detail.ts
6540
+ function subtitleTpl(subtitle) {
6541
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6542
+ return import_lit_html2.html`<p
6543
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6544
+ data-uidex-detail-subtitle
6545
+ >
6546
+ ${text}
6547
+ </p>`;
6548
+ }
6549
+ function notFoundTpl(ref2) {
6550
+ return import_lit_html2.html`<p
6551
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6552
+ data-uidex-detail-missing
6553
+ >
6554
+ ${ref2.kind}: ${ref2.id} not found in registry
6555
+ </p>`;
6556
+ }
6799
6557
  function hasFilterableSections(sections) {
6800
6558
  return sections.some((s) => "filterable" in s && s.filterable === true);
6801
6559
  }
@@ -6860,7 +6618,10 @@ function renderDetailSurface(surface, ctx, root) {
6860
6618
  >
6861
6619
  ${surface.title}
6862
6620
  </h2>` : import_lit_html2.nothing}
6863
- <span class="ml-auto">${kindBadgeTpl(surface.entityKind)}</span>
6621
+ <span class="ml-auto flex items-center gap-1">
6622
+ ${surface.unregistered ? badgeTpl("Unregistered", { variant: "warning", size: "sm" }) : import_lit_html2.nothing}
6623
+ ${kindBadgeTpl(surface.entityKind)}
6624
+ </span>
6864
6625
  </div>
6865
6626
  ${surface.subtitle ? subtitleTpl(surface.subtitle) : import_lit_html2.nothing}
6866
6627
  <div
@@ -7153,6 +6914,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7153
6914
  };
7154
6915
  }
7155
6916
 
6917
+ // src/browser/ui/button.ts
6918
+ var buttonBase = "focus-visible:ring-ring focus-visible:ring-offset-background disabled:opacity-60 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border text-sm font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg]:pointer-events-none [&_svg]:shrink-0";
6919
+ var buttonVariants = cva(buttonBase, {
6920
+ defaultVariants: { size: "default", variant: "default" },
6921
+ variants: {
6922
+ size: {
6923
+ default: "h-8 px-3",
6924
+ sm: "h-7 gap-1.5 px-2.5",
6925
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6926
+ lg: "h-9 px-3.5",
6927
+ xl: "h-10 px-4 text-base",
6928
+ icon: "size-8",
6929
+ "icon-sm": "size-7",
6930
+ "icon-lg": "size-9",
6931
+ "icon-xs": "size-6 rounded-md"
6932
+ },
6933
+ variant: {
6934
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6935
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6936
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6937
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6938
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6939
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6940
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6941
+ }
6942
+ }
6943
+ });
6944
+
7156
6945
  // src/browser/views/render/form.ts
7157
6946
  var fieldSeq = 0;
7158
6947
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7349,9 +7138,9 @@ function iconMediaTpl(iconTpl) {
7349
7138
  </div>
7350
7139
  `;
7351
7140
  }
7352
- function loadingViewTpl() {
7141
+ function loadingViewTpl(rootRef) {
7353
7142
  return import_lit_html2.html`
7354
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7143
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${(0, import_ref.ref)(rootRef)}>
7355
7144
  ${iconMediaTpl(icon(import_lucide8.Loader2, "animate-spin"))}
7356
7145
  <div class="flex max-w-sm flex-col items-center text-center">
7357
7146
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7364,42 +7153,47 @@ function loadingViewTpl() {
7364
7153
  </div>
7365
7154
  `;
7366
7155
  }
7367
- function successViewTpl() {
7156
+ function successViewTpl(refs) {
7368
7157
  return import_lit_html2.html`
7369
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7158
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${(0, import_ref.ref)(refs.root)}>
7370
7159
  ${iconMediaTpl(icon(import_lucide8.CircleCheck))}
7371
7160
  <div class="flex max-w-sm flex-col items-center text-center">
7372
7161
  <div
7373
7162
  class="font-heading text-xl font-semibold"
7374
7163
  data-uidex-success-title
7164
+ ${(0, import_ref.ref)(refs.title)}
7375
7165
  ></div>
7376
7166
  </div>
7377
7167
  <div
7378
7168
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7379
7169
  data-uidex-success-actions
7170
+ ${(0, import_ref.ref)(refs.actions)}
7380
7171
  >
7381
7172
  <a
7382
7173
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7383
7174
  target="_blank"
7384
7175
  rel="noreferrer"
7385
7176
  data-uidex-success-link
7177
+ ${(0, import_ref.ref)(refs.link)}
7386
7178
  ></a>
7387
7179
  </div>
7388
7180
  </div>
7389
7181
  `;
7390
7182
  }
7391
- function errorViewTpl() {
7183
+ function errorViewTpl(refs) {
7392
7184
  return import_lit_html2.html`
7393
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7185
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${(0, import_ref.ref)(refs.root)}>
7394
7186
  ${iconMediaTpl(icon(import_lucide8.CircleX))}
7395
7187
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7396
7188
  <div
7397
7189
  class="font-heading text-xl font-semibold"
7398
7190
  data-uidex-error-title
7191
+ ${(0, import_ref.ref)(refs.title)}
7399
7192
  ></div>
7400
7193
  <p
7401
7194
  class="text-muted-foreground text-sm"
7402
7195
  data-uidex-error-description
7196
+ ${(0, import_ref.ref)(refs.description)}
7403
7197
  ></p>
7404
7198
  </div>
7405
7199
  <div
@@ -7410,6 +7204,7 @@ function errorViewTpl() {
7410
7204
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7411
7205
  data-slot="button"
7412
7206
  data-uidex-retry-button
7207
+ ${(0, import_ref.ref)(refs.retry)}
7413
7208
  >
7414
7209
  Try Again
7415
7210
  </button>
@@ -7451,8 +7246,15 @@ function renderFormSurface(surface, ctx, root) {
7451
7246
  });
7452
7247
  }
7453
7248
  const formRef = (0, import_ref.createRef)();
7454
- const statusRef = (0, import_ref.createRef)();
7455
- const submitRef = (0, import_ref.createRef)();
7249
+ const loadingRef = (0, import_ref.createRef)();
7250
+ const successRef = (0, import_ref.createRef)();
7251
+ const successTitleRef = (0, import_ref.createRef)();
7252
+ const successLinkRef = (0, import_ref.createRef)();
7253
+ const successActionsRef = (0, import_ref.createRef)();
7254
+ const errorRef = (0, import_ref.createRef)();
7255
+ const errorTitleRef = (0, import_ref.createRef)();
7256
+ const errorDescriptionRef = (0, import_ref.createRef)();
7257
+ const retryRef = (0, import_ref.createRef)();
7456
7258
  (0, import_lit_html2.render)(
7457
7259
  import_lit_html2.html`
7458
7260
  <section
@@ -7467,7 +7269,19 @@ function renderFormSurface(surface, ctx, root) {
7467
7269
  novalidate
7468
7270
  data-uidex-form=${surface.id}
7469
7271
  ></form>
7470
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7272
+ ${loadingViewTpl(loadingRef)}
7273
+ ${successViewTpl({
7274
+ root: successRef,
7275
+ title: successTitleRef,
7276
+ link: successLinkRef,
7277
+ actions: successActionsRef
7278
+ })}
7279
+ ${errorViewTpl({
7280
+ root: errorRef,
7281
+ title: errorTitleRef,
7282
+ description: errorDescriptionRef,
7283
+ retry: retryRef
7284
+ })}
7471
7285
  </section>
7472
7286
  `,
7473
7287
  root
@@ -7497,32 +7311,15 @@ function renderFormSurface(surface, ctx, root) {
7497
7311
  }
7498
7312
  });
7499
7313
  form.append(status);
7500
- const sectionEl = root.querySelector(
7501
- "[data-uidex-form-surface]"
7502
- );
7503
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7504
- const successView = sectionEl.querySelector(
7505
- "[data-uidex-success-view]"
7506
- );
7507
- const successTitle = sectionEl.querySelector(
7508
- "[data-uidex-success-title]"
7509
- );
7510
- const successLink = sectionEl.querySelector(
7511
- "[data-uidex-success-link]"
7512
- );
7513
- const successActions = successLink.parentElement;
7514
- const errorView = sectionEl.querySelector(
7515
- "[data-uidex-error-view]"
7516
- );
7517
- const errorTitle = sectionEl.querySelector(
7518
- "[data-uidex-error-title]"
7519
- );
7520
- const errorDescription = sectionEl.querySelector(
7521
- "[data-uidex-error-description]"
7522
- );
7523
- const retryButton = sectionEl.querySelector(
7524
- "[data-uidex-retry-button]"
7525
- );
7314
+ const loadingView = loadingRef.value;
7315
+ const successView = successRef.value;
7316
+ const successTitle = successTitleRef.value;
7317
+ const successLink = successLinkRef.value;
7318
+ const successActions = successActionsRef.value;
7319
+ const errorView = errorRef.value;
7320
+ const errorTitle = errorTitleRef.value;
7321
+ const errorDescription = errorDescriptionRef.value;
7322
+ const retryButton = retryRef.value;
7526
7323
  function clearAllFieldErrors() {
7527
7324
  for (const f of fields) f.setError(null);
7528
7325
  }
@@ -7923,7 +7720,7 @@ function defaultFilter(item, query) {
7923
7720
  function rowTag(item) {
7924
7721
  return item.tag ?? item.value;
7925
7722
  }
7926
- function resolveLeadingTpl(item, ctx) {
7723
+ function resolveLeadingTpl(item) {
7927
7724
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
7928
7725
  if (item.leading) {
7929
7726
  const node = item.leading();
@@ -8028,7 +7825,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8028
7825
  data-uidex-item-value=${item.value}
8029
7826
  >
8030
7827
  ${rowTpl({
8031
- leading: resolveLeadingTpl(item, ctx),
7828
+ leading: resolveLeadingTpl(item),
8032
7829
  label: item.label,
8033
7830
  subtitle: item.subtitle,
8034
7831
  trailing: item.trailing ?? item.shortcut
@@ -8073,7 +7870,7 @@ function renderListSurface(surface, ctx, root) {
8073
7870
  return root.ownerDocument ?? document;
8074
7871
  };
8075
7872
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8076
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7873
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8077
7874
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8078
7875
  const controller = createListController({
8079
7876
  surfaceId: surface.id,
@@ -8769,52 +8566,16 @@ function sameRef2(a, b) {
8769
8566
  if (a === null || b === null) return false;
8770
8567
  return a.kind === b.kind && a.id === b.id;
8771
8568
  }
8772
- function resolveHints(view, ctx) {
8773
- const h = view.hints;
8774
- if (!h) return [];
8775
- if (typeof h === "function") {
8776
- try {
8777
- return h(ctx) ?? [];
8778
- } catch (err) {
8779
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8780
- return [];
8781
- }
8782
- }
8783
- return h;
8784
- }
8785
- function resolveTitle(view, ctx) {
8786
- const t = view.title;
8787
- if (!t) return "";
8788
- if (typeof t === "function") {
8789
- try {
8790
- return t(ctx) ?? "";
8791
- } catch (err) {
8792
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8793
- return "";
8794
- }
8795
- }
8796
- return t;
8797
- }
8798
- function resolveActions(view, ctx, globalActions) {
8799
- const result = [];
8800
- const fn = view.actions;
8801
- if (fn) {
8802
- try {
8803
- const viewActions2 = fn(ctx) ?? [];
8804
- result.push(...viewActions2);
8805
- } catch (err) {
8806
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8807
- }
8808
- }
8809
- if (globalActions) {
8569
+ function resolveProp(view, ctx, value, propName, fallback) {
8570
+ if (typeof value === "function") {
8810
8571
  try {
8811
- const globals = globalActions(ctx) ?? [];
8812
- result.push(...globals);
8572
+ return value(ctx) ?? fallback;
8813
8573
  } catch (err) {
8814
- console.error(`[uidex] globalActions() threw`, err);
8574
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8575
+ return fallback;
8815
8576
  }
8816
8577
  }
8817
- return result;
8578
+ return value ?? fallback;
8818
8579
  }
8819
8580
  function createViewStack(options) {
8820
8581
  const { container, views, session, registry, highlight } = options;
@@ -8882,39 +8643,40 @@ function createViewStack(options) {
8882
8643
  color: KIND_STYLE[top.ctx.ref.kind].color
8883
8644
  });
8884
8645
  }
8885
- function updateChrome() {
8886
- if (!shell) return;
8887
- const top = mounted[mounted.length - 1];
8888
- if (!top) return;
8646
+ function updateNavButtons(shell2, top) {
8889
8647
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8890
- shell.backBtn.hidden = atRoot;
8891
- shell.searchIcon.hidden = !atRoot;
8648
+ shell2.backBtn.hidden = atRoot;
8649
+ shell2.searchIcon.hidden = !atRoot;
8650
+ }
8651
+ function updateTitle(shell2, top) {
8892
8652
  const searchable = top.view.searchable !== false;
8893
- shell.searchInput.hidden = !searchable;
8894
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8895
- shell.headerTitle.textContent = titleText;
8896
- shell.headerTitle.hidden = searchable || !titleText;
8897
- shell.footerLeft.replaceChildren();
8653
+ shell2.searchInput.hidden = !searchable;
8654
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8655
+ shell2.headerTitle.textContent = titleText;
8656
+ shell2.headerTitle.hidden = searchable || !titleText;
8657
+ }
8658
+ function updateFooterChip(shell2, top) {
8659
+ shell2.footerLeft.replaceChildren();
8898
8660
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8899
8661
  if (refEntity) {
8900
- shell.footerLeft.append(
8662
+ shell2.footerLeft.append(
8901
8663
  renderKindChip({
8902
8664
  entity: refEntity,
8903
8665
  withKindName: true
8904
8666
  })
8905
8667
  );
8906
8668
  } else {
8907
- shell.footerLeft.append(shell.logo);
8669
+ shell2.footerLeft.append(shell2.logo);
8908
8670
  }
8909
- hintChangeSub?.();
8910
- hintChangeSub = null;
8911
- highlightActionsSub?.();
8912
- highlightActionsSub = null;
8913
- shell.footerRight.replaceChildren();
8914
- const footerItems = [];
8915
- const enterHint = resolveHints(top.view, top.ctx).find(
8916
- (h) => h.key.includes("\u21B5")
8917
- );
8671
+ }
8672
+ function buildEnterHint(top, footerItems) {
8673
+ const enterHint = resolveProp(
8674
+ top.view,
8675
+ top.ctx,
8676
+ top.view.hints,
8677
+ "hints",
8678
+ []
8679
+ ).find((h) => h.key.includes("\u21B5"));
8918
8680
  const src = top.mounted.submitIntent;
8919
8681
  if (src) {
8920
8682
  const key = enterHint?.key ?? "\u21B5";
@@ -8935,28 +8697,52 @@ function createViewStack(options) {
8935
8697
  } else if (enterHint) {
8936
8698
  footerItems.push(createHint(enterHint.key, enterHint.label));
8937
8699
  }
8700
+ }
8701
+ function buildActions(shell2, top, footerItems) {
8938
8702
  const actionsDivider = createFooterDivider();
8939
- const viewActions2 = resolveActions(top.view, top.ctx);
8703
+ const viewActions2 = resolveProp(
8704
+ top.view,
8705
+ top.ctx,
8706
+ top.view.actions,
8707
+ "actions",
8708
+ []
8709
+ );
8940
8710
  const hlSrc = top.mounted.highlightActions;
8941
8711
  const syncActions = () => {
8942
8712
  const hlActions = hlSrc?.get() ?? [];
8943
8713
  const combined = [...hlActions, ...viewActions2];
8944
- shell.actionsPopup.setActions(combined);
8714
+ shell2.actionsPopup.setActions(combined);
8945
8715
  const visible = combined.length > 0;
8946
- shell.actionsPopup.trigger.hidden = !visible;
8716
+ shell2.actionsPopup.trigger.hidden = !visible;
8947
8717
  actionsDivider.hidden = !visible || footerItems.length === 0;
8948
8718
  };
8949
8719
  if (hlSrc) {
8950
8720
  highlightActionsSub = hlSrc.subscribe(syncActions);
8951
8721
  }
8952
8722
  for (let i = 0; i < footerItems.length; i++) {
8953
- if (i > 0) shell.footerRight.append(createFooterDivider());
8954
- shell.footerRight.append(footerItems[i]);
8723
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8724
+ shell2.footerRight.append(footerItems[i]);
8955
8725
  }
8956
- shell.footerRight.append(actionsDivider);
8957
- shell.footerRight.append(shell.actionsPopup.trigger);
8726
+ shell2.footerRight.append(actionsDivider);
8727
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
8958
8728
  syncActions();
8959
8729
  }
8730
+ function updateChrome() {
8731
+ if (!shell) return;
8732
+ const top = mounted[mounted.length - 1];
8733
+ if (!top) return;
8734
+ updateNavButtons(shell, top);
8735
+ updateTitle(shell, top);
8736
+ updateFooterChip(shell, top);
8737
+ hintChangeSub?.();
8738
+ hintChangeSub = null;
8739
+ highlightActionsSub?.();
8740
+ highlightActionsSub = null;
8741
+ shell.footerRight.replaceChildren();
8742
+ const footerItems = [];
8743
+ buildEnterHint(top, footerItems);
8744
+ buildActions(shell, top, footerItems);
8745
+ }
8960
8746
  function render2() {
8961
8747
  if (!container.isConnected) return;
8962
8748
  const stack = session.getState().stack;
@@ -9053,7 +8839,7 @@ function createViewStack(options) {
9053
8839
 
9054
8840
  // src/browser/views/built-in/ids.ts
9055
8841
  var BUILT_IN_VIEW_IDS = {
9056
- archiveReason: "archive-reason",
8842
+ closeReason: "close-reason",
9057
8843
  commandPalette: "command-palette",
9058
8844
  elements: "elements",
9059
8845
  entityReports: "entity-reports",
@@ -9104,13 +8890,13 @@ function parentDetail(ref2) {
9104
8890
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9105
8891
  }
9106
8892
 
9107
- // src/browser/views/built-in/archive-reason.ts
8893
+ // src/browser/views/built-in/close-reason.ts
9108
8894
  var import_lucide11 = require("lucide");
9109
8895
  var pendingReportId = null;
9110
- var afterArchive = null;
9111
- function setArchiveTarget(reportId, onDone) {
8896
+ var afterClose = null;
8897
+ function setCloseTarget(reportId, onDone) {
9112
8898
  pendingReportId = reportId;
9113
- afterArchive = onDone;
8899
+ afterClose = onDone;
9114
8900
  }
9115
8901
  function leadingIcon(iconNode) {
9116
8902
  return () => {
@@ -9125,16 +8911,16 @@ function spinnerTile() {
9125
8911
  return createIconTile(spinner);
9126
8912
  }
9127
8913
  var REASONS = [
9128
- { value: "fixed", label: "Fixed", icon: import_lucide11.CircleCheck },
8914
+ { value: "fixed", label: "Resolved", icon: import_lucide11.CircleCheck },
9129
8915
  { value: "not_a_bug", label: "Not a bug", icon: import_lucide11.BugOff },
9130
8916
  { value: "duplicate", label: "Duplicate", icon: import_lucide11.Copy },
9131
8917
  { value: "wont_fix", label: "Won't fix", icon: import_lucide11.Ban }
9132
8918
  ];
9133
- var archiveReasonView = {
9134
- id: BUILT_IN_VIEW_IDS.archiveReason,
8919
+ var closeReasonView = {
8920
+ id: BUILT_IN_VIEW_IDS.closeReason,
9135
8921
  matches: () => false,
9136
8922
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9137
- title: "Archive reason",
8923
+ title: "Close reason",
9138
8924
  searchable: false,
9139
8925
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9140
8926
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9147,12 +8933,12 @@ var archiveReasonView = {
9147
8933
  }));
9148
8934
  const surface = {
9149
8935
  kind: "list",
9150
- id: "uidex-archive-reason",
8936
+ id: "uidex-close-reason",
9151
8937
  searchable: false,
9152
8938
  items,
9153
8939
  emptyLabel: "No reasons available",
9154
8940
  onSelect: async (item) => {
9155
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8941
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9156
8942
  busy = true;
9157
8943
  const row = root.querySelector(
9158
8944
  `[data-uidex-item-value="${item.value}"]`
@@ -9164,13 +8950,10 @@ var archiveReasonView = {
9164
8950
  row.style.pointerEvents = "none";
9165
8951
  }
9166
8952
  try {
9167
- await ctx.registry.archiveReport(
9168
- pendingReportId,
9169
- item.value
9170
- );
9171
- const done = afterArchive;
8953
+ await ctx.registry.closeReport(pendingReportId, item.value);
8954
+ const done = afterClose;
9172
8955
  pendingReportId = null;
9173
- afterArchive = null;
8956
+ afterClose = null;
9174
8957
  done?.();
9175
8958
  } catch {
9176
8959
  busy = false;
@@ -9188,7 +8971,7 @@ var archiveReasonView = {
9188
8971
  }
9189
8972
  const error = document.createElement("p");
9190
8973
  error.className = "text-destructive px-4 py-2 text-xs";
9191
- error.textContent = "Archive failed \u2014 try again";
8974
+ error.textContent = "Close failed \u2014 try again";
9192
8975
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9193
8976
  setTimeout(() => error.remove(), 3e3);
9194
8977
  }
@@ -9507,6 +9290,7 @@ function createCommandPaletteView(shortcut) {
9507
9290
 
9508
9291
  // src/browser/internal/screenshot.ts
9509
9292
  var import_modern_screenshot = require("modern-screenshot");
9293
+ var WEBP_QUALITY = 0.85;
9510
9294
  function resolveBackgroundColor(el2) {
9511
9295
  let node = el2;
9512
9296
  while (node) {
@@ -9520,8 +9304,7 @@ function isUidexChrome(node) {
9520
9304
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9521
9305
  const el2 = node;
9522
9306
  if (el2.shadowRoot !== null) return true;
9523
- const cls = el2.classList;
9524
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9307
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9525
9308
  }
9526
9309
  async function captureScreenshot(options = {}) {
9527
9310
  if (typeof document === "undefined") {
@@ -9533,8 +9316,10 @@ async function captureScreenshot(options = {}) {
9533
9316
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9534
9317
  const padding = 16;
9535
9318
  const height = target.scrollHeight || target.clientHeight;
9536
- return (0, import_modern_screenshot.domToPng)(target, {
9319
+ const encode = options.format === "png" ? import_modern_screenshot.domToPng : import_modern_screenshot.domToWebp;
9320
+ return encode(target, {
9537
9321
  scale,
9322
+ quality: WEBP_QUALITY,
9538
9323
  width: width + padding * 2,
9539
9324
  height: height + padding * 2,
9540
9325
  backgroundColor: resolveBackgroundColor(target),
@@ -9590,12 +9375,6 @@ function collectFlowsTouching(ctx, targetId) {
9590
9375
  }
9591
9376
 
9592
9377
  // src/browser/views/builder/detail-builder.ts
9593
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9594
- "element",
9595
- "region",
9596
- "widget",
9597
- "primitive"
9598
- ]);
9599
9378
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9600
9379
  function copyPathAction(ref2, loc) {
9601
9380
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9697,7 +9476,10 @@ function copyScreenshotAction(ref2) {
9697
9476
  const target = resolveEntityElement(ref2) ?? void 0;
9698
9477
  const dataUrl = await captureScreenshot({
9699
9478
  target,
9700
- maxWidth: 1280
9479
+ maxWidth: 1280,
9480
+ // Clipboard write requires PNG — the async Clipboard API rejects
9481
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9482
+ format: "png"
9701
9483
  });
9702
9484
  const res = await fetch(dataUrl);
9703
9485
  const blob = await res.blob();
@@ -9730,12 +9512,11 @@ function createEntityDetailView(config) {
9730
9512
  if (!ctx.ref || ctx.ref.kind !== kind) {
9731
9513
  return { kind: "detail", entityKind: kind };
9732
9514
  }
9733
- const entity = ctx.registry.get(kind, ctx.ref.id);
9734
- if (!entity) {
9735
- return { kind: "detail", entityKind: kind, notFound: ctx.ref };
9736
- }
9737
- const metaEntity = entity;
9738
- const meta = metaEntity.meta;
9515
+ const exactEntity = ctx.registry.get(kind, ctx.ref.id);
9516
+ const patternEntity = exactEntity ? void 0 : ctx.registry.matchPattern?.(kind, ctx.ref.id);
9517
+ const entity = exactEntity ?? patternEntity;
9518
+ const metaEntity = entity ? entity : null;
9519
+ const meta = metaEntity?.meta;
9739
9520
  const actions = [];
9740
9521
  const cloud = ctx.cloud;
9741
9522
  actions.push({ ...reportAction(ctx.ref), group: "Report" });
@@ -9749,18 +9530,20 @@ function createEntityDetailView(config) {
9749
9530
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9750
9531
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9751
9532
  }
9752
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9533
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9753
9534
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9754
9535
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9755
9536
  }
9756
- actions.push({
9757
- ...copyPathAction(ctx.ref, metaEntity.loc),
9758
- group: "Inspect"
9759
- });
9760
- actions.push({
9761
- ...copySnapshotAction(ctx.ref, metaEntity.loc),
9762
- group: "Inspect"
9763
- });
9537
+ if (metaEntity?.loc) {
9538
+ actions.push({
9539
+ ...copyPathAction(ctx.ref, metaEntity.loc),
9540
+ group: "Inspect"
9541
+ });
9542
+ actions.push({
9543
+ ...copySnapshotAction(ctx.ref, metaEntity.loc),
9544
+ group: "Inspect"
9545
+ });
9546
+ }
9764
9547
  const sections = [];
9765
9548
  if (meta?.description) {
9766
9549
  sections.push({ id: "description", text: meta.description });
@@ -9768,10 +9551,12 @@ function createEntityDetailView(config) {
9768
9551
  if (offerAcceptance && meta?.acceptance?.length) {
9769
9552
  sections.push({ id: "acceptance", items: meta.acceptance });
9770
9553
  }
9771
- for (const s of config.extraSections?.(ctx, entity) ?? []) {
9772
- sections.push(s);
9554
+ if (entity) {
9555
+ for (const s of config.extraSections?.(ctx, entity) ?? []) {
9556
+ sections.push(s);
9557
+ }
9773
9558
  }
9774
- if (!DOM_BACKED_KINDS2.has(kind)) {
9559
+ if (!DOM_BACKED_KINDS.has(kind)) {
9775
9560
  sections.push({
9776
9561
  id: "composes",
9777
9562
  label: SECTION_LABELS.contains,
@@ -9779,7 +9564,7 @@ function createEntityDetailView(config) {
9779
9564
  filterable: true
9780
9565
  });
9781
9566
  }
9782
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9567
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9783
9568
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9784
9569
  if (featureEntities.length > 0) {
9785
9570
  sections.push({
@@ -9798,8 +9583,9 @@ function createEntityDetailView(config) {
9798
9583
  return {
9799
9584
  kind: "detail",
9800
9585
  entityKind: kind,
9801
- title: displayName(metaEntity),
9802
- subtitle: config.subtitle?.(ctx, entity),
9586
+ title: patternEntity ? ctx.ref.id : metaEntity ? displayName(metaEntity) : ctx.ref.id,
9587
+ subtitle: exactEntity ? config.subtitle?.(ctx, exactEntity) : void 0,
9588
+ unregistered: !entity,
9803
9589
  actions,
9804
9590
  sections
9805
9591
  };
@@ -9808,35 +9594,14 @@ function createEntityDetailView(config) {
9808
9594
  }
9809
9595
 
9810
9596
  // src/browser/views/built-in/entity-detail.ts
9811
- function collectDomParents(ctx, ref2) {
9812
- const el2 = resolveEntityElement(ref2);
9813
- if (!el2) return [];
9814
- const parents = [];
9815
- const seen = /* @__PURE__ */ new Set();
9816
- let node = el2.parentElement;
9817
- while (node) {
9818
- if (node instanceof HTMLElement) {
9819
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9820
- const id = node.getAttribute(attr);
9821
- if (id) {
9822
- const key = `${kind}:${id}`;
9823
- if (!seen.has(key)) {
9824
- seen.add(key);
9825
- const entity = ctx.registry.get(kind, id);
9826
- if (entity) parents.push(entity);
9827
- }
9828
- }
9829
- }
9830
- }
9831
- node = node.parentElement;
9832
- }
9833
- return parents;
9597
+ function collectFeatureConsumers(ctx, featureId) {
9598
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9834
9599
  }
9835
- function usedBySection(ctx, ref2) {
9600
+ function usedBySection(ctx, featureId) {
9836
9601
  return {
9837
9602
  id: "used-by",
9838
9603
  label: SECTION_LABELS.usedBy,
9839
- entities: collectDomParents(ctx, ref2),
9604
+ entities: collectFeatureConsumers(ctx, featureId),
9840
9605
  filterable: true
9841
9606
  };
9842
9607
  }
@@ -9857,9 +9622,7 @@ var featureDetailView = createEntityDetailView({
9857
9622
  id: "feature-detail",
9858
9623
  kind: "feature",
9859
9624
  fallbackTitle: "Feature",
9860
- extraSections: (ctx, entity) => [
9861
- usedBySection(ctx, { kind: "feature", id: entity.id })
9862
- ]
9625
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9863
9626
  });
9864
9627
  var regionDetailView = createEntityDetailView({
9865
9628
  id: "region-detail",
@@ -9987,6 +9750,15 @@ var reportDetailView = {
9987
9750
  }
9988
9751
  if (report.screenshot) {
9989
9752
  sections.push({ id: "screenshot", url: report.screenshot });
9753
+ } else {
9754
+ const pins = ctx.cloud?.pins;
9755
+ const fetchScreenshot = pins?.screenshot;
9756
+ if (pins && fetchScreenshot) {
9757
+ sections.push({
9758
+ id: "screenshot",
9759
+ load: () => fetchScreenshot.call(pins, report.id)
9760
+ });
9761
+ }
9990
9762
  }
9991
9763
  const metaEntries = [];
9992
9764
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -9997,14 +9769,14 @@ var reportDetailView = {
9997
9769
  sections.push({ id: "metadata", entries: metaEntries });
9998
9770
  }
9999
9771
  const actions = [];
10000
- if (ctx.registry.archiveReport) {
9772
+ if (ctx.registry.closeReport) {
10001
9773
  actions.push({
10002
- id: "archive",
10003
- label: "Archive",
9774
+ id: "close",
9775
+ label: "Close",
10004
9776
  icon: "archive-x",
10005
9777
  run: () => {
10006
- setArchiveTarget(report.id, () => ctx.pop());
10007
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9778
+ setCloseTarget(report.id, () => ctx.pop());
9779
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10008
9780
  }
10009
9781
  });
10010
9782
  }
@@ -10042,13 +9814,13 @@ function reportToItem(report, ctx) {
10042
9814
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10043
9815
  const kind = ctx.ref?.kind ?? "element";
10044
9816
  const actions = [];
10045
- if (ctx.registry.archiveReport) {
9817
+ if (ctx.registry.closeReport) {
10046
9818
  actions.push({
10047
- id: `archive-${report.id}`,
10048
- label: "Archive",
9819
+ id: `close-${report.id}`,
9820
+ label: "Close",
10049
9821
  perform: () => {
10050
- setArchiveTarget(report.id, () => ctx.pop());
10051
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9822
+ setCloseTarget(report.id, () => ctx.pop());
9823
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10052
9824
  }
10053
9825
  });
10054
9826
  }
@@ -10201,7 +9973,7 @@ function capitalize2(s) {
10201
9973
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10202
9974
  }
10203
9975
  function renderPayloadMarkdown(payload) {
10204
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
9976
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10205
9977
  const ctx = payload.context;
10206
9978
  const lines = [
10207
9979
  `# ${heading}`,
@@ -10270,12 +10042,6 @@ var reportFields = [
10270
10042
  ];
10271
10043
 
10272
10044
  // src/browser/views/built-in/report/view-builder.ts
10273
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10274
- "element",
10275
- "region",
10276
- "widget",
10277
- "primitive"
10278
- ]);
10279
10045
  var KIND_TO_ATTR = new Map(
10280
10046
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10281
10047
  );
@@ -10346,7 +10112,7 @@ function createReportView(config) {
10346
10112
  const cloud = resolveCloud(ctx);
10347
10113
  const extra = extraFields ? extraFields(ctx) : [];
10348
10114
  const fields = [...extra, ...baseFields ?? reportFields];
10349
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10115
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10350
10116
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10351
10117
  const screenshotPromise = isDomBacked ? captureScreenshot({
10352
10118
  target: targetEl ?? void 0,
@@ -10923,7 +10689,7 @@ var pinSettingsView = {
10923
10689
  // src/browser/views/built-in/index.ts
10924
10690
  function buildDefaultViews(shortcut) {
10925
10691
  return [
10926
- archiveReasonView,
10692
+ closeReasonView,
10927
10693
  createCommandPaletteView(shortcut),
10928
10694
  explorePageView,
10929
10695
  componentDetailView,
@@ -10951,6 +10717,27 @@ function buildDefaultViews(shortcut) {
10951
10717
  }
10952
10718
 
10953
10719
  // src/browser/create-uidex.ts
10720
+ function getVisibleEntities() {
10721
+ if (typeof document === "undefined") return [];
10722
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10723
+ const nodes = document.querySelectorAll(selector);
10724
+ const ids = [];
10725
+ const seen = /* @__PURE__ */ new Set();
10726
+ for (const node of nodes) {
10727
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10728
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10729
+ const id = node.getAttribute(attr);
10730
+ if (!id) continue;
10731
+ const key = `${kind}:${id}`;
10732
+ if (!seen.has(key)) {
10733
+ seen.add(key);
10734
+ ids.push(key);
10735
+ }
10736
+ break;
10737
+ }
10738
+ }
10739
+ return ids;
10740
+ }
10954
10741
  function createUidex(options = {}) {
10955
10742
  const registry = createRegistry();
10956
10743
  const inspectorRef = { current: null };
@@ -10985,7 +10772,7 @@ function createUidex(options = {}) {
10985
10772
  const views = createRouter({ session });
10986
10773
  const cloud = options.cloud ?? null;
10987
10774
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
10988
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10775
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
10989
10776
  if (options.defaultViews !== false) {
10990
10777
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
10991
10778
  }
@@ -10995,6 +10782,94 @@ function createUidex(options = {}) {
10995
10782
  let shadowRoot = null;
10996
10783
  const mountCleanup = createCleanupStack();
10997
10784
  let mounted = false;
10785
+ function syncReportsToRegistry() {
10786
+ const layer = pinLayerRef.current;
10787
+ if (!layer) return;
10788
+ const byEntity = /* @__PURE__ */ new Map();
10789
+ for (const pin of layer.getAllPins()) {
10790
+ const cid = pin.entity ?? "";
10791
+ if (!cid) continue;
10792
+ const list = byEntity.get(cid);
10793
+ if (list) list.push(pin);
10794
+ else byEntity.set(cid, [pin]);
10795
+ }
10796
+ for (const [cid, pins] of byEntity) {
10797
+ const ref2 = parseComponentRef(cid);
10798
+ registry.setReports(ref2.kind, ref2.id, pins);
10799
+ }
10800
+ }
10801
+ function setupKeyBindings(root, viewStack) {
10802
+ const bindings = bindShadowKeys({
10803
+ shadowRoot: root,
10804
+ session,
10805
+ getActionsPopup: () => viewStack.getActionsPopup(),
10806
+ getPinLayer: () => pinLayerRef.current,
10807
+ shortcut: options.shortcut
10808
+ });
10809
+ mountCleanup.add(bindings);
10810
+ return bindings;
10811
+ }
10812
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10813
+ if (cloud) {
10814
+ registry.closeReport = async (reportId, status) => {
10815
+ pinLayerRef.current?.removePin(reportId);
10816
+ await cloud.pins.close(
10817
+ reportId,
10818
+ status
10819
+ );
10820
+ syncReportsToRegistry();
10821
+ };
10822
+ }
10823
+ const pinLayer = createPinLayer({
10824
+ container: root,
10825
+ onOpenPinDetail: (componentId, reportId) => {
10826
+ const ref2 = parseComponentRef(componentId);
10827
+ setSelectedReportId(reportId);
10828
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10829
+ session.mode.transition.enterViewing(views.buildStack(entry));
10830
+ },
10831
+ onHoverPin: (anchor, componentId) => {
10832
+ const overlay = overlayRef.current;
10833
+ if (!overlay) return;
10834
+ if (!anchor || !componentId) {
10835
+ overlay.hide();
10836
+ return;
10837
+ }
10838
+ overlay.show(anchor, {
10839
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10840
+ });
10841
+ },
10842
+ onPinsChanged: syncReportsToRegistry
10843
+ });
10844
+ shell.menuBar.setPinLayer(pinLayer);
10845
+ pinLayerRef.current = pinLayer;
10846
+ mountCleanup.add(() => {
10847
+ pinLayer.destroy();
10848
+ pinLayerRef.current = null;
10849
+ registry.closeReport = void 0;
10850
+ });
10851
+ if (cloud) {
10852
+ const detach = pinLayer.attachCloud({
10853
+ cloud,
10854
+ channel,
10855
+ getRoute: getCurrentRoute,
10856
+ getPathname,
10857
+ getVisibleEntities,
10858
+ getMatchMode,
10859
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10860
+ });
10861
+ mountCleanup.add(detach);
10862
+ if (channel && typeof window !== "undefined") {
10863
+ const onRouteChange = () => {
10864
+ const route = getCurrentRoute();
10865
+ channel?.joinRoute(route);
10866
+ void pinLayer.refresh();
10867
+ };
10868
+ const detachRoute = bindRouteChange(onRouteChange);
10869
+ mountCleanup.add(detachRoute);
10870
+ }
10871
+ }
10872
+ }
10998
10873
  function mount(target) {
10999
10874
  if (mounted) return;
11000
10875
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11003,28 +10878,7 @@ function createUidex(options = {}) {
11003
10878
  }
11004
10879
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11005
10880
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11006
- const getVisibleEntities = () => {
11007
- if (typeof document === "undefined") return [];
11008
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11009
- const nodes = document.querySelectorAll(selector);
11010
- const ids = [];
11011
- const seen = /* @__PURE__ */ new Set();
11012
- for (const node of nodes) {
11013
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11014
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11015
- const id = node.getAttribute(attr);
11016
- if (!id) continue;
11017
- const key = `${kind}:${id}`;
11018
- if (!seen.has(key)) {
11019
- seen.add(key);
11020
- ids.push(key);
11021
- }
11022
- break;
11023
- }
11024
- }
11025
- return ids;
11026
- };
11027
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10881
+ const getMatchMode = () => readMode();
11028
10882
  let channel = null;
11029
10883
  if (cloud && options.user) {
11030
10884
  channel = cloud.realtime.connect({
@@ -11045,7 +10899,6 @@ function createUidex(options = {}) {
11045
10899
  onSelect: (match) => {
11046
10900
  const route = views.resolve(match.ref);
11047
10901
  if (!route) return;
11048
- session.select(match.ref);
11049
10902
  const entry = { id: route.view.id, ref: match.ref };
11050
10903
  session.mode.transition.enterViewing(views.buildStack(entry));
11051
10904
  }
@@ -11096,92 +10949,15 @@ function createUidex(options = {}) {
11096
10949
  });
11097
10950
  mountCleanup.add(viewStack);
11098
10951
  if (shadowRoot) {
11099
- keyBindings = bindShadowKeys({
10952
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10953
+ setupPinLayer(
11100
10954
  shadowRoot,
11101
- session,
11102
- getActionsPopup: () => viewStack.getActionsPopup(),
11103
- getPinLayer: () => pinLayerRef.current,
11104
- shortcut: options.shortcut
11105
- });
11106
- mountCleanup.add(keyBindings);
11107
- }
11108
- if (shadowRoot) {
11109
- const syncReportsToRegistry = () => {
11110
- const layer = pinLayerRef.current;
11111
- if (!layer) return;
11112
- const byEntity = /* @__PURE__ */ new Map();
11113
- for (const pin of layer.getAllPins()) {
11114
- const cid = pin.entity ?? "";
11115
- if (!cid) continue;
11116
- const list = byEntity.get(cid);
11117
- if (list) list.push(pin);
11118
- else byEntity.set(cid, [pin]);
11119
- }
11120
- for (const [cid, pins] of byEntity) {
11121
- const ref2 = parseComponentRef(cid);
11122
- registry.setReports(ref2.kind, ref2.id, pins);
11123
- }
11124
- };
11125
- if (cloud) {
11126
- registry.archiveReport = async (reportId, reason) => {
11127
- pinLayerRef.current?.removePin(reportId);
11128
- await cloud.pins.archive(
11129
- reportId,
11130
- reason
11131
- );
11132
- syncReportsToRegistry();
11133
- };
11134
- }
11135
- const pinLayer = createPinLayer({
11136
- container: shadowRoot,
11137
- currentBranch: options.git?.branch ?? null,
11138
- onOpenPinDetail: (componentId, reportId) => {
11139
- const ref2 = parseComponentRef(componentId);
11140
- setSelectedReportId(reportId);
11141
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11142
- session.mode.transition.enterViewing(views.buildStack(entry));
11143
- },
11144
- onHoverPin: (anchor, componentId) => {
11145
- const overlay = overlayRef.current;
11146
- if (!overlay) return;
11147
- if (!anchor || !componentId) {
11148
- overlay.hide();
11149
- return;
11150
- }
11151
- overlay.show(anchor, {
11152
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11153
- });
11154
- },
11155
- onPinsChanged: syncReportsToRegistry
11156
- });
11157
- shell.menuBar.setPinLayer(pinLayer);
11158
- pinLayerRef.current = pinLayer;
11159
- mountCleanup.add(() => {
11160
- pinLayer.destroy();
11161
- pinLayerRef.current = null;
11162
- registry.archiveReport = void 0;
11163
- });
11164
- if (cloud) {
11165
- const detach = pinLayer.attachCloud({
11166
- cloud,
11167
- channel,
11168
- getRoute: getCurrentRoute,
11169
- getPathname,
11170
- getVisibleEntities,
11171
- getMatchMode,
11172
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11173
- });
11174
- mountCleanup.add(detach);
11175
- if (channel && typeof window !== "undefined") {
11176
- const onRouteChange = () => {
11177
- const route = getCurrentRoute();
11178
- channel?.joinRoute(route);
11179
- void pinLayer.refresh();
11180
- };
11181
- const detachRoute = bindRouteChange(onRouteChange);
11182
- mountCleanup.add(detachRoute);
11183
- }
11184
- }
10955
+ shell,
10956
+ channel,
10957
+ getCurrentRoute,
10958
+ getMatchMode,
10959
+ getPathname
10960
+ );
11185
10961
  }
11186
10962
  if (ingest) {
11187
10963
  ingest.start();
@@ -11220,18 +10996,23 @@ function UidexProvider({
11220
10996
  config,
11221
10997
  children
11222
10998
  }) {
11223
- const [ownedInstance] = (0, import_react.useState)(() => {
10999
+ const [owned] = (0, import_react.useState)(() => {
11224
11000
  if (externalInstance) return null;
11225
- const resolvedCloud = cloud !== void 0 ? cloud : projectKey ? (0, import_cloud.cloud)({ projectKey }) : null;
11226
- return createUidex({ ...config, cloud: resolvedCloud, user });
11001
+ const ownedCloud = cloud === void 0 && projectKey && typeof window !== "undefined" ? (0, import_cloud.cloud)({ projectKey }) : null;
11002
+ const resolvedCloud = cloud !== void 0 ? cloud : ownedCloud;
11003
+ return {
11004
+ instance: createUidex({ ...config, cloud: resolvedCloud, user }),
11005
+ cloud: ownedCloud
11006
+ };
11227
11007
  });
11228
- const instance = externalInstance ?? ownedInstance;
11008
+ const instance = externalInstance ?? owned.instance;
11229
11009
  (0, import_react.useEffect)(() => {
11230
- if (!ownedInstance) return;
11010
+ if (!owned) return;
11231
11011
  return () => {
11232
- ownedInstance.unmount();
11012
+ owned.instance.unmount();
11013
+ owned.cloud?.dispose?.();
11233
11014
  };
11234
- }, [ownedInstance]);
11015
+ }, [owned]);
11235
11016
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UidexContext.Provider, { value: instance, children });
11236
11017
  }
11237
11018
 
@@ -11357,6 +11138,7 @@ function UidexDevtools({
11357
11138
  setInstance(inst);
11358
11139
  return () => {
11359
11140
  inst.unmount();
11141
+ cloud?.dispose?.();
11360
11142
  };
11361
11143
  }, [projectKey, user?.id]);
11362
11144
  if (!instance) return null;