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
@@ -74,6 +74,7 @@ function freezeEntity(entity, flows) {
74
74
  function createRegistry() {
75
75
  const store = emptyStore();
76
76
  let flowsCache = null;
77
+ const patternCache = /* @__PURE__ */ new Map();
77
78
  const getFlows = () => {
78
79
  if (flowsCache === null) flowsCache = Array.from(store.flow.values());
79
80
  return flowsCache;
@@ -83,6 +84,7 @@ function createRegistry() {
83
84
  const key = entityKey(entity);
84
85
  store[entity.kind].set(key, entity);
85
86
  flowsCache = null;
87
+ patternCache.delete(entity.kind);
86
88
  };
87
89
  const get = (kind, id) => {
88
90
  assertEntityKind(kind);
@@ -90,6 +92,51 @@ function createRegistry() {
90
92
  if (raw === void 0) return void 0;
91
93
  return freezeEntity(raw, getFlows());
92
94
  };
95
+ const getPatternsForKind = (kind) => {
96
+ const cached = patternCache.get(kind);
97
+ if (cached !== void 0) return cached;
98
+ const patterns = [];
99
+ for (const [key, entity] of store[kind]) {
100
+ if (key.includes("*")) {
101
+ const segments = key.split("*");
102
+ patterns.push({
103
+ segments,
104
+ staticLength: segments.reduce((n, s) => n + s.length, 0),
105
+ entity
106
+ });
107
+ }
108
+ }
109
+ patternCache.set(
110
+ kind,
111
+ patterns
112
+ );
113
+ return patterns;
114
+ };
115
+ const matchesSegments = (segments, id) => {
116
+ const first = segments[0];
117
+ const last = segments[segments.length - 1];
118
+ if (!id.startsWith(first)) return false;
119
+ let pos = first.length;
120
+ for (let i = 1; i < segments.length - 1; i++) {
121
+ const idx = id.indexOf(segments[i], pos);
122
+ if (idx === -1) return false;
123
+ pos = idx + segments[i].length;
124
+ }
125
+ return id.endsWith(last) && id.length - last.length >= pos;
126
+ };
127
+ const matchPattern = (kind, id) => {
128
+ assertEntityKind(kind);
129
+ const patterns = getPatternsForKind(kind);
130
+ if (patterns.length === 0) return void 0;
131
+ let best;
132
+ for (const entry of patterns) {
133
+ if (matchesSegments(entry.segments, id) && (best === void 0 || entry.staticLength > best.staticLength)) {
134
+ best = entry;
135
+ }
136
+ }
137
+ if (best === void 0) return void 0;
138
+ return freezeEntity(best.entity, getFlows());
139
+ };
93
140
  const list = (kind) => {
94
141
  assertEntityKind(kind);
95
142
  const flows = getFlows();
@@ -140,6 +187,7 @@ function createRegistry() {
140
187
  return {
141
188
  add,
142
189
  get,
190
+ matchPattern,
143
191
  list,
144
192
  query,
145
193
  byScope,
@@ -393,7 +441,7 @@ function createNetworkCapture(options = {}) {
393
441
 
394
442
  // src/browser/ingest/index.ts
395
443
  function createIngest(options = {}) {
396
- const { session, ...opts } = options;
444
+ const opts = options;
397
445
  const wantConsole = opts.captureConsole !== false;
398
446
  const wantNetwork = opts.captureNetwork !== false;
399
447
  const consoleCapture = wantConsole ? createConsoleCapture({
@@ -412,14 +460,12 @@ function createIngest(options = {}) {
412
460
  consoleCapture?.start();
413
461
  networkCapture?.start();
414
462
  active = Boolean(consoleCapture?.isActive || networkCapture?.isActive);
415
- if (active) session?.setIngest(true);
416
463
  }
417
464
  function stop() {
418
465
  if (!active) return;
419
466
  consoleCapture?.stop();
420
467
  networkCapture?.stop();
421
468
  active = false;
422
- session?.setIngest(false);
423
469
  }
424
470
  return {
425
471
  start,
@@ -541,8 +587,7 @@ var COMMAND_PALETTE_ENTRY = {
541
587
  function createModeStore(options) {
542
588
  const { nav, bindings } = options;
543
589
  const store = createStore(() => ({
544
- mode: "idle",
545
- inspectorActive: false
590
+ mode: "idle"
546
591
  }));
547
592
  const transition = {
548
593
  openPalette() {
@@ -551,17 +596,17 @@ function createModeStore(options) {
551
596
  bindings?.destroyInspector?.();
552
597
  }
553
598
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
554
- store.setState({ mode: "palette", inspectorActive: false });
599
+ store.setState({ mode: "palette" });
555
600
  },
556
601
  openInspector() {
557
602
  bindings?.mountInspector?.();
558
603
  nav.nav.clear();
559
- store.setState({ mode: "inspecting", inspectorActive: true });
604
+ store.setState({ mode: "inspecting" });
560
605
  },
561
606
  closeInspector() {
562
607
  bindings?.destroyInspector?.();
563
608
  nav.nav.clear();
564
- store.setState({ mode: "idle", inspectorActive: false });
609
+ store.setState({ mode: "idle" });
565
610
  },
566
611
  toggleInspector() {
567
612
  if (store.getState().mode === "inspecting") {
@@ -576,7 +621,7 @@ function createModeStore(options) {
576
621
  bindings?.destroyInspector?.();
577
622
  }
578
623
  nav.nav.reset(initialStack);
579
- store.setState({ mode: "viewing", inspectorActive: false });
624
+ store.setState({ mode: "viewing" });
580
625
  },
581
626
  dismiss() {
582
627
  const prev = store.getState();
@@ -584,7 +629,7 @@ function createModeStore(options) {
584
629
  bindings?.destroyInspector?.();
585
630
  }
586
631
  nav.nav.clear();
587
- store.setState({ mode: "idle", inspectorActive: false });
632
+ store.setState({ mode: "idle" });
588
633
  },
589
634
  popOrTransition() {
590
635
  const { stack } = nav.getState();
@@ -592,12 +637,12 @@ function createModeStore(options) {
592
637
  nav.nav.pop();
593
638
  } else if (stack.length === 2 && stack[0]?.id === "command-palette") {
594
639
  nav.nav.reset([COMMAND_PALETTE_ENTRY]);
595
- store.setState({ mode: "palette", inspectorActive: false });
640
+ store.setState({ mode: "palette" });
596
641
  } else if (stack.length === 2) {
597
642
  nav.nav.pop();
598
643
  } else {
599
644
  nav.nav.clear();
600
- store.setState({ mode: "idle", inspectorActive: false });
645
+ store.setState({ mode: "idle" });
601
646
  }
602
647
  },
603
648
  pushView(entry) {
@@ -614,7 +659,7 @@ function createModeStore(options) {
614
659
  case "inspecting":
615
660
  bindings?.destroyInspector?.();
616
661
  nav.nav.reset([entry]);
617
- store.setState({ mode: "viewing", inspectorActive: false });
662
+ store.setState({ mode: "viewing" });
618
663
  break;
619
664
  case "palette":
620
665
  case "viewing":
@@ -649,14 +694,6 @@ function createNavigationStore() {
649
694
  store.setState({ stack: s.slice(0, -1) });
650
695
  }
651
696
  },
652
- replace(entry) {
653
- const s = store.getState().stack;
654
- if (s.length === 0) {
655
- store.setState({ stack: [entry] });
656
- } else {
657
- store.setState({ stack: [...s.slice(0, -1), entry] });
658
- }
659
- },
660
697
  clear() {
661
698
  store.setState({ stack: [] });
662
699
  },
@@ -671,14 +708,11 @@ function createNavigationStore() {
671
708
 
672
709
  // src/browser/session/store.ts
673
710
  var defaultSnapshot = {
674
- hover: null,
675
- selection: null,
676
711
  stack: [],
677
712
  pinnedHighlight: null,
678
- inspectorActive: false,
713
+ mode: "idle",
679
714
  theme: "auto",
680
715
  resolvedTheme: "light",
681
- ingestActive: false,
682
716
  user: null
683
717
  };
684
718
  function resolveTheme(preference, detect) {
@@ -729,7 +763,6 @@ function createSession(options = {}) {
729
763
  } else if (highlightMode === "transient") {
730
764
  onUpdateOverlay?.(hlCtx);
731
765
  }
732
- store.setState({ hover: ref2 });
733
766
  },
734
767
  unhover() {
735
768
  if (highlightMode === "transient") {
@@ -739,7 +772,6 @@ function createSession(options = {}) {
739
772
  hlCtx.color = null;
740
773
  onHideOverlay?.();
741
774
  }
742
- store.setState({ hover: null });
743
775
  },
744
776
  pin(ref2) {
745
777
  const pinRef = ref2 ?? hlCtx.ref;
@@ -767,14 +799,11 @@ function createSession(options = {}) {
767
799
  };
768
800
  const store = createStore3(() => ({
769
801
  ...defaultSnapshot,
770
- hover: overrides.hover ?? null,
771
- selection: overrides.selection ?? null,
772
802
  stack: [],
773
803
  pinnedHighlight: null,
774
- inspectorActive: false,
804
+ mode: "idle",
775
805
  theme: initialPref,
776
806
  resolvedTheme: initialResolved,
777
- ingestActive: overrides.ingestActive ?? false,
778
807
  user: overrides.user ?? null
779
808
  }));
780
809
  nav.subscribe(() => {
@@ -784,29 +813,21 @@ function createSession(options = {}) {
784
813
  }
785
814
  });
786
815
  modeStore.subscribe(() => {
787
- const { inspectorActive } = modeStore.getState();
788
- if (store.getState().inspectorActive !== inspectorActive) {
789
- store.setState({ inspectorActive });
816
+ const { mode } = modeStore.getState();
817
+ if (store.getState().mode !== mode) {
818
+ store.setState({ mode });
790
819
  }
791
820
  });
792
821
  const session = store;
793
822
  session.nav = nav;
794
823
  session.mode = modeStore;
795
824
  session.highlight = highlightActions;
796
- session.select = (ref2) => {
797
- if (sameRef(store.getState().selection, ref2)) return;
798
- store.setState({ selection: ref2 });
799
- };
800
825
  session.setTheme = (theme, resolved) => {
801
826
  const state = store.getState();
802
827
  const nextResolved = resolved ?? resolveTheme(theme, detectTheme);
803
828
  if (state.theme === theme && state.resolvedTheme === nextResolved) return;
804
829
  store.setState({ theme, resolvedTheme: nextResolved });
805
830
  };
806
- session.setIngest = (active) => {
807
- if (store.getState().ingestActive === active) return;
808
- store.setState({ ingestActive: active });
809
- };
810
831
  if (initialStack.length > 0) {
811
832
  modeStore.transition.openPalette();
812
833
  for (let i = 1; i < initialStack.length; i++) {
@@ -1098,6 +1119,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1098
1119
  .relative {
1099
1120
  position: relative;
1100
1121
  }
1122
+ .static {
1123
+ position: static;
1124
+ }
1101
1125
  .inset-0 {
1102
1126
  inset: calc(var(--spacing) * 0);
1103
1127
  }
@@ -1116,9 +1140,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1116
1140
  .right-0 {
1117
1141
  right: calc(var(--spacing) * 0);
1118
1142
  }
1119
- .right-2 {
1120
- right: calc(var(--spacing) * 2);
1121
- }
1122
1143
  .bottom-full {
1123
1144
  bottom: 100%;
1124
1145
  }
@@ -1161,9 +1182,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1161
1182
  .mx-2 {
1162
1183
  margin-inline: calc(var(--spacing) * 2);
1163
1184
  }
1164
- .my-1 {
1165
- margin-block: calc(var(--spacing) * 1);
1166
- }
1167
1185
  .ms-auto {
1168
1186
  margin-inline-start: auto;
1169
1187
  }
@@ -1200,9 +1218,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1200
1218
  .inline-flex {
1201
1219
  display: inline-flex;
1202
1220
  }
1203
- .list-item {
1204
- display: list-item;
1205
- }
1206
1221
  .table {
1207
1222
  display: table;
1208
1223
  }
@@ -1210,10 +1225,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1210
1225
  width: calc(var(--spacing) * 2);
1211
1226
  height: calc(var(--spacing) * 2);
1212
1227
  }
1213
- .size-3 {
1214
- width: calc(var(--spacing) * 3);
1215
- height: calc(var(--spacing) * 3);
1216
- }
1217
1228
  .size-3\\.5 {
1218
1229
  width: calc(var(--spacing) * 3.5);
1219
1230
  height: calc(var(--spacing) * 3.5);
@@ -1271,15 +1282,9 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1271
1282
  .h-\\[26rem\\] {
1272
1283
  height: 26rem;
1273
1284
  }
1274
- .h-auto {
1275
- height: auto;
1276
- }
1277
1285
  .h-full {
1278
1286
  height: 100%;
1279
1287
  }
1280
- .h-px {
1281
- height: 1px;
1282
- }
1283
1288
  .max-h-32 {
1284
1289
  max-height: calc(var(--spacing) * 32);
1285
1290
  }
@@ -1289,9 +1294,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1289
1294
  .min-h-0 {
1290
1295
  min-height: calc(var(--spacing) * 0);
1291
1296
  }
1292
- .min-h-7 {
1293
- min-height: calc(var(--spacing) * 7);
1294
- }
1295
1297
  .min-h-8 {
1296
1298
  min-height: calc(var(--spacing) * 8);
1297
1299
  }
@@ -1316,9 +1318,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1316
1318
  .w-56 {
1317
1319
  width: calc(var(--spacing) * 56);
1318
1320
  }
1319
- .w-auto {
1320
- width: auto;
1321
- }
1322
1321
  .w-full {
1323
1322
  width: 100%;
1324
1323
  }
@@ -1393,9 +1392,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1393
1392
  .animate-spin {
1394
1393
  animation: var(--animate-spin);
1395
1394
  }
1396
- .cursor-default {
1397
- cursor: default;
1398
- }
1399
1395
  .cursor-pointer {
1400
1396
  cursor: pointer;
1401
1397
  }
@@ -1405,9 +1401,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1405
1401
  .scroll-py-2 {
1406
1402
  scroll-padding-block: calc(var(--spacing) * 2);
1407
1403
  }
1408
- .list-none {
1409
- list-style-type: none;
1410
- }
1411
1404
  .appearance-none {
1412
1405
  appearance: none;
1413
1406
  }
@@ -1429,9 +1422,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1429
1422
  .justify-center {
1430
1423
  justify-content: center;
1431
1424
  }
1432
- .justify-start {
1433
- justify-content: flex-start;
1434
- }
1435
1425
  .gap-0 {
1436
1426
  gap: calc(var(--spacing) * 0);
1437
1427
  }
@@ -1456,9 +1446,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1456
1446
  .gap-6 {
1457
1447
  gap: calc(var(--spacing) * 6);
1458
1448
  }
1459
- .self-start {
1460
- align-self: flex-start;
1461
- }
1462
1449
  .truncate {
1463
1450
  overflow: hidden;
1464
1451
  text-overflow: ellipsis;
@@ -1673,9 +1660,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1673
1660
  .p-6 {
1674
1661
  padding: calc(var(--spacing) * 6);
1675
1662
  }
1676
- .px-0 {
1677
- padding-inline: calc(var(--spacing) * 0);
1678
- }
1679
1663
  .px-1 {
1680
1664
  padding-inline: calc(var(--spacing) * 1);
1681
1665
  }
@@ -1700,9 +1684,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1700
1684
  .px-6 {
1701
1685
  padding-inline: calc(var(--spacing) * 6);
1702
1686
  }
1703
- .py-0 {
1704
- padding-block: calc(var(--spacing) * 0);
1705
- }
1706
1687
  .py-1 {
1707
1688
  padding-block: calc(var(--spacing) * 1);
1708
1689
  }
@@ -1775,9 +1756,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
1775
1756
  .text-\\[9px\\] {
1776
1757
  font-size: 9px;
1777
1758
  }
1778
- .text-\\[10px\\] {
1779
- font-size: 10px;
1780
- }
1781
1759
  .text-\\[11px\\] {
1782
1760
  font-size: 11px;
1783
1761
  }
@@ -2017,10 +1995,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2017
1995
  outline-style: var(--tw-outline-style);
2018
1996
  outline-width: 1px;
2019
1997
  }
2020
- .blur {
2021
- --tw-blur: blur(8px);
2022
- 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,);
2023
- }
2024
1998
  .filter {
2025
1999
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
2026
2000
  }
@@ -2186,25 +2160,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2186
2160
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2187
2161
  }
2188
2162
  }
2189
- .focus-within\\:border-ring {
2190
- &:focus-within {
2191
- border-color: var(--ring);
2192
- }
2193
- }
2194
- .focus-within\\:ring-\\[3px\\] {
2195
- &:focus-within {
2196
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2197
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2198
- }
2199
- }
2200
- .focus-within\\:ring-ring\\/30 {
2201
- &:focus-within {
2202
- --tw-ring-color: var(--ring);
2203
- @supports (color: color-mix(in lab, red, red)) {
2204
- --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
2205
- }
2206
- }
2207
- }
2208
2163
  .hover\\:border-destructive\\/30 {
2209
2164
  &:hover {
2210
2165
  @media (hover: hover) {
@@ -2348,11 +2303,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2348
2303
  pointer-events: none;
2349
2304
  }
2350
2305
  }
2351
- .disabled\\:cursor-not-allowed {
2352
- &:disabled {
2353
- cursor: not-allowed;
2354
- }
2355
- }
2356
2306
  .disabled\\:opacity-50 {
2357
2307
  &:disabled {
2358
2308
  opacity: 50%;
@@ -2363,11 +2313,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2363
2313
  opacity: 60%;
2364
2314
  }
2365
2315
  }
2366
- .has-disabled\\:opacity-60 {
2367
- &:has(*:disabled) {
2368
- opacity: 60%;
2369
- }
2370
- }
2371
2316
  .aria-invalid\\:border-destructive\\/40 {
2372
2317
  &[aria-invalid="true"] {
2373
2318
  border-color: var(--destructive);
@@ -2774,32 +2719,6 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
2774
2719
  height: calc(var(--spacing) * 4.5);
2775
2720
  }
2776
2721
  }
2777
- .\\[\\&\\>svg\\]\\:pointer-events-none {
2778
- &>svg {
2779
- pointer-events: none;
2780
- }
2781
- }
2782
- .\\[\\&\\>svg\\]\\:-mx-0\\.5 {
2783
- &>svg {
2784
- margin-inline: calc(var(--spacing) * -0.5);
2785
- }
2786
- }
2787
- .\\[\\&\\>svg\\]\\:shrink-0 {
2788
- &>svg {
2789
- flex-shrink: 0;
2790
- }
2791
- }
2792
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'opacity-\\'\\]\\)\\]\\:opacity-80 {
2793
- &>svg:not([class*='opacity-']) {
2794
- opacity: 80%;
2795
- }
2796
- }
2797
- .\\[\\&\\>svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 {
2798
- &>svg:not([class*='size-']) {
2799
- width: calc(var(--spacing) * 4);
2800
- height: calc(var(--spacing) * 4);
2801
- }
2802
- }
2803
2722
  .\\[\\[data-kbd-nav\\]_\\&\\]\\:focus-within\\:bg-accent {
2804
2723
  [data-kbd-nav] & {
2805
2724
  &:focus-within {
@@ -3361,18 +3280,23 @@ var tailwind_built_default = `/*! tailwindcss v4.2.2 | MIT License | https://tai
3361
3280
 
3362
3281
  // src/browser/surface/constants.ts
3363
3282
  var SURFACE_HOST_CLASS = "uidex-surface-host";
3364
- var SURFACE_CONTAINER_CLASS = "uidex-container";
3365
3283
  var Z_BASE = 2147483630;
3366
3284
  var Z_OVERLAY = 2147483635;
3367
3285
  var Z_PIN_LAYER = 2147483640;
3368
3286
  var Z_CHROME = 2147483645;
3369
- var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS},.${SURFACE_CONTAINER_CLASS}`;
3287
+ var SURFACE_IGNORE_SELECTOR = `.${SURFACE_HOST_CLASS}`;
3370
3288
  var UIDEX_ATTR_TO_KIND = [
3371
3289
  ["data-uidex", "element"],
3372
3290
  ["data-uidex-region", "region"],
3373
3291
  ["data-uidex-widget", "widget"],
3374
3292
  ["data-uidex-primitive", "primitive"]
3375
3293
  ];
3294
+ var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
3295
+ "element",
3296
+ "region",
3297
+ "widget",
3298
+ "primitive"
3299
+ ]);
3376
3300
 
3377
3301
  // src/browser/surface/host.ts
3378
3302
  function createSurfaceHost(options) {
@@ -3550,7 +3474,7 @@ function createCursorTooltip(deps) {
3550
3474
  // src/browser/surface/inspector.ts
3551
3475
  function entityForRef(ref2, registry) {
3552
3476
  if (registry) {
3553
- const found = registry.get(ref2.kind, ref2.id);
3477
+ const found = registry.get(ref2.kind, ref2.id) ?? registry.matchPattern?.(ref2.kind, ref2.id);
3554
3478
  if (found) return found;
3555
3479
  }
3556
3480
  if (ref2.kind === "route") return { kind: "route", path: ref2.id, page: ref2.id };
@@ -3662,7 +3586,6 @@ function createInspector(options) {
3662
3586
  e.preventDefault();
3663
3587
  e.stopPropagation();
3664
3588
  const match = stack[layerIndex];
3665
- session.select(match.ref);
3666
3589
  onSelect?.(match, { x: e.clientX, y: e.clientY });
3667
3590
  };
3668
3591
  const onContextMenu = (e) => {
@@ -3711,8 +3634,6 @@ function createInspector(options) {
3711
3634
 
3712
3635
  // src/browser/surface/menu-bar.ts
3713
3636
  import {
3714
- ChevronLeft,
3715
- ChevronRight,
3716
3637
  Command,
3717
3638
  Highlighter,
3718
3639
  MapPin,
@@ -3990,49 +3911,12 @@ function createMenuBar(options) {
3990
3911
  },
3991
3912
  pinIcon
3992
3913
  );
3993
- const commitCycler = el("div", {
3994
- class: "relative z-1 inline-flex items-center gap-0.5",
3995
- attrs: { "data-uidex-menubar-commit-cycler": "" }
3996
- });
3997
- commitCycler.hidden = true;
3998
- const prevIcon = createLucideElement2(ChevronLeft);
3999
- prevIcon.setAttribute("class", "size-3");
4000
- prevIcon.setAttribute("aria-hidden", "true");
4001
- const prevBtn = el(
4002
- "button",
4003
- {
4004
- class: BUTTON_CLASS,
4005
- attrs: { type: "button", "aria-label": "Previous commit" },
4006
- style: { width: "18px", height: "18px" }
4007
- },
4008
- prevIcon
4009
- );
4010
- const commitLabel = el("span", {
4011
- class: "relative z-1 whitespace-nowrap px-1 text-[10px] font-mono text-muted-foreground",
4012
- attrs: { "data-uidex-menubar-commit-label": "" }
4013
- });
4014
- const nextIcon = createLucideElement2(ChevronRight);
4015
- nextIcon.setAttribute("class", "size-3");
4016
- nextIcon.setAttribute("aria-hidden", "true");
4017
- const nextBtn = el(
4018
- "button",
4019
- {
4020
- class: BUTTON_CLASS,
4021
- attrs: { type: "button", "aria-label": "Next commit" },
4022
- style: { width: "18px", height: "18px" }
4023
- },
4024
- nextIcon
4025
- );
4026
- commitCycler.appendChild(prevBtn);
4027
- commitCycler.appendChild(commitLabel);
4028
- commitCycler.appendChild(nextBtn);
4029
3914
  const pinWrapper = el("div", {
4030
3915
  class: "relative z-1 inline-flex items-center gap-0.5",
4031
3916
  attrs: { "data-uidex-menubar-pin-wrapper": "" }
4032
3917
  });
4033
3918
  pinWrapper.hidden = true;
4034
3919
  pinWrapper.appendChild(pinBtn);
4035
- pinWrapper.appendChild(commitCycler);
4036
3920
  root.appendChild(pinWrapper);
4037
3921
  const updatePinUI = () => {
4038
3922
  if (!activePinLayer) {
@@ -4040,16 +3924,7 @@ function createMenuBar(options) {
4040
3924
  return;
4041
3925
  }
4042
3926
  const pinsVisible = activePinLayer.visible;
4043
- const state = activePinLayer.filterState;
4044
- const hasCommits = state.commits.length > 0;
4045
3927
  pinWrapper.hidden = false;
4046
- commitCycler.hidden = !pinsVisible || !hasCommits;
4047
- if (state.commitIndex === -1 || !state.commits[state.commitIndex]) {
4048
- commitLabel.textContent = `all (${state.commits.length})`;
4049
- } else {
4050
- const sha = state.commits[state.commitIndex] ?? "";
4051
- commitLabel.textContent = sha.slice(0, 7);
4052
- }
4053
3928
  pinBtn.className = cn(BUTTON_CLASS, pinsVisible && BUTTON_ACTIVE_CLASS);
4054
3929
  };
4055
3930
  pinBtn.addEventListener("click", (e) => {
@@ -4058,14 +3933,6 @@ function createMenuBar(options) {
4058
3933
  activePinLayer.setVisible(!activePinLayer.visible);
4059
3934
  }
4060
3935
  });
4061
- prevBtn.addEventListener("click", (e) => {
4062
- e.stopPropagation();
4063
- activePinLayer?.prevCommit();
4064
- });
4065
- nextBtn.addEventListener("click", (e) => {
4066
- e.stopPropagation();
4067
- activePinLayer?.nextCommit();
4068
- });
4069
3936
  let unsubscribePinFilter = activePinLayer?.onFilterChange(() => updatePinUI());
4070
3937
  const presenceIcon = createLucideElement2(Users);
4071
3938
  presenceIcon.setAttribute("class", "size-3.5");
@@ -4159,7 +4026,7 @@ function createMenuBar(options) {
4159
4026
  container.appendChild(root);
4160
4027
  const syncButtonStates = () => {
4161
4028
  const state = session.getState();
4162
- const inspectActive = state.inspectorActive;
4029
+ const inspectActive = state.mode === "inspecting";
4163
4030
  inspectBtn.setAttribute(
4164
4031
  "data-uidex-menubar-inspect-active",
4165
4032
  inspectActive ? "true" : "false"
@@ -4278,6 +4145,49 @@ function createMenuBar(options) {
4278
4145
  };
4279
4146
  }
4280
4147
 
4148
+ // src/browser/internal/repositioner.ts
4149
+ function createRepositioner(onReflow) {
4150
+ let rafId = null;
4151
+ let attached = false;
4152
+ const schedule = () => {
4153
+ if (rafId !== null) return;
4154
+ rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4155
+ rafId = null;
4156
+ onReflow();
4157
+ }) : setTimeout(() => {
4158
+ rafId = null;
4159
+ onReflow();
4160
+ }, 0);
4161
+ };
4162
+ const cancel = () => {
4163
+ if (rafId === null) return;
4164
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4165
+ else clearTimeout(rafId);
4166
+ rafId = null;
4167
+ };
4168
+ const onScroll = () => schedule();
4169
+ const onResize = () => schedule();
4170
+ const attach = () => {
4171
+ if (attached) return;
4172
+ attached = true;
4173
+ window.addEventListener("resize", onResize);
4174
+ window.addEventListener("scroll", onScroll, {
4175
+ capture: true,
4176
+ passive: true
4177
+ });
4178
+ };
4179
+ const detach = () => {
4180
+ if (!attached) return;
4181
+ attached = false;
4182
+ window.removeEventListener("resize", onResize);
4183
+ window.removeEventListener("scroll", onScroll, {
4184
+ capture: true
4185
+ });
4186
+ cancel();
4187
+ };
4188
+ return { schedule, cancel, attach, detach };
4189
+ }
4190
+
4281
4191
  // src/browser/surface/overlay.ts
4282
4192
  var DEFAULT_COLOR = "#34d399";
4283
4193
  var DEFAULT_BORDER_WIDTH = 2;
@@ -4345,44 +4255,7 @@ function createOverlay(deps) {
4345
4255
  fillOpacity: DEFAULT_FILL_OPACITY,
4346
4256
  backdrop: false
4347
4257
  };
4348
- let rafId = null;
4349
- let attached = false;
4350
- const schedule = () => {
4351
- if (rafId !== null) return;
4352
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4353
- rafId = null;
4354
- updatePosition();
4355
- }) : setTimeout(() => {
4356
- rafId = null;
4357
- updatePosition();
4358
- }, 0);
4359
- };
4360
- const cancelSchedule = () => {
4361
- if (rafId === null) return;
4362
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4363
- else clearTimeout(rafId);
4364
- rafId = null;
4365
- };
4366
- const onScroll = () => schedule();
4367
- const onResize = () => schedule();
4368
- const attach = () => {
4369
- if (attached) return;
4370
- attached = true;
4371
- window.addEventListener("resize", onResize);
4372
- window.addEventListener("scroll", onScroll, {
4373
- capture: true,
4374
- passive: true
4375
- });
4376
- };
4377
- const detach = () => {
4378
- if (!attached) return;
4379
- attached = false;
4380
- window.removeEventListener("resize", onResize);
4381
- window.removeEventListener("scroll", onScroll, {
4382
- capture: true
4383
- });
4384
- cancelSchedule();
4385
- };
4258
+ const repositioner = createRepositioner(() => updatePosition());
4386
4259
  function updatePosition() {
4387
4260
  if (!target) return;
4388
4261
  const rect = target.getBoundingClientRect();
@@ -4446,16 +4319,16 @@ function createOverlay(deps) {
4446
4319
  box.offsetHeight;
4447
4320
  }
4448
4321
  box.style.opacity = "1";
4449
- attach();
4322
+ repositioner.attach();
4450
4323
  },
4451
4324
  hide() {
4452
4325
  target = null;
4453
4326
  box.style.opacity = "0";
4454
4327
  backdrop.style.opacity = "0";
4455
- detach();
4328
+ repositioner.detach();
4456
4329
  },
4457
4330
  destroy() {
4458
- detach();
4331
+ repositioner.detach();
4459
4332
  box.remove();
4460
4333
  backdrop.remove();
4461
4334
  target = null;
@@ -4572,8 +4445,7 @@ function createSurfaceShell(options) {
4572
4445
  const overlay = createOverlay({ container: host.shadowRoot });
4573
4446
  cleanup.add(overlay);
4574
4447
  const tooltip = createCursorTooltip({
4575
- container: host.chromeEl,
4576
- session: options.session
4448
+ container: host.chromeEl
4577
4449
  });
4578
4450
  cleanup.add(tooltip);
4579
4451
  const afterHover = options.inspector?.onAfterHover;
@@ -4765,9 +4637,6 @@ function createPinLayer(options) {
4765
4637
  const seenIds = /* @__PURE__ */ new Set();
4766
4638
  const byComp = /* @__PURE__ */ new Map();
4767
4639
  const indicators = /* @__PURE__ */ new Map();
4768
- let filter = { branch: null, commit: null };
4769
- let commits = [];
4770
- let commitIndex = -1;
4771
4640
  const filterCbs = /* @__PURE__ */ new Set();
4772
4641
  const notifyFilter = () => {
4773
4642
  for (const cb of filterCbs) cb();
@@ -4782,8 +4651,6 @@ function createPinLayer(options) {
4782
4651
  }
4783
4652
  };
4784
4653
  const rerender = () => {
4785
- commits = [];
4786
- commitIndex = -1;
4787
4654
  rebuildFiltered();
4788
4655
  for (const id of Array.from(indicators.keys())) {
4789
4656
  if (!byComp.has(id)) removeIndicator(id);
@@ -4792,45 +4659,8 @@ function createPinLayer(options) {
4792
4659
  notifyFilter();
4793
4660
  onPinsChanged?.();
4794
4661
  };
4795
- let rafId = null;
4796
- let winAttached = false;
4797
4662
  let obs = null;
4798
- const schedulePos = () => {
4799
- if (rafId !== null) return;
4800
- rafId = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
4801
- rafId = null;
4802
- posAll();
4803
- }) : setTimeout(() => {
4804
- rafId = null;
4805
- posAll();
4806
- }, 0);
4807
- };
4808
- const cancelPos = () => {
4809
- if (rafId === null) return;
4810
- if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
4811
- else clearTimeout(rafId);
4812
- rafId = null;
4813
- };
4814
- const onScroll = () => schedulePos();
4815
- const onResize = () => schedulePos();
4816
- const attachWin = () => {
4817
- if (winAttached) return;
4818
- winAttached = true;
4819
- window.addEventListener("scroll", onScroll, {
4820
- capture: true,
4821
- passive: true
4822
- });
4823
- window.addEventListener("resize", onResize);
4824
- };
4825
- const detachWin = () => {
4826
- if (!winAttached) return;
4827
- winAttached = false;
4828
- window.removeEventListener("scroll", onScroll, {
4829
- capture: true
4830
- });
4831
- window.removeEventListener("resize", onResize);
4832
- cancelPos();
4833
- };
4663
+ const repositioner = createRepositioner(() => posAll());
4834
4664
  const attachObs = () => {
4835
4665
  if (obs || typeof MutationObserver === "undefined") return;
4836
4666
  obs = new MutationObserver((recs) => {
@@ -4854,7 +4684,7 @@ function createPinLayer(options) {
4854
4684
  s.anchor = resolveEntityElement(parseComponentRef(s.componentId));
4855
4685
  changed = true;
4856
4686
  }
4857
- if (changed) schedulePos();
4687
+ if (changed) repositioner.schedule();
4858
4688
  };
4859
4689
  const expand = (st) => {
4860
4690
  if (st.expanded) return;
@@ -5096,7 +4926,7 @@ function createPinLayer(options) {
5096
4926
  if (!st) {
5097
4927
  st = buildIndicator(componentId);
5098
4928
  indicators.set(componentId, st);
5099
- attachWin();
4929
+ repositioner.attach();
5100
4930
  attachObs();
5101
4931
  }
5102
4932
  if (st.pinIndex >= pins.length) st.pinIndex = 0;
@@ -5110,7 +4940,7 @@ function createPinLayer(options) {
5110
4940
  st.wrap.remove();
5111
4941
  indicators.delete(componentId);
5112
4942
  if (indicators.size === 0) {
5113
- detachWin();
4943
+ repositioner.detach();
5114
4944
  detachObs();
5115
4945
  }
5116
4946
  };
@@ -5145,17 +4975,22 @@ function createPinLayer(options) {
5145
4975
  seenIds.clear();
5146
4976
  byComp.clear();
5147
4977
  for (const id of Array.from(indicators.keys())) removeIndicator(id);
5148
- commits = [];
5149
- commitIndex = -1;
5150
4978
  notifyFilter();
5151
4979
  },
5152
4980
  getPinsForElement: (id) => byComp.get(id) ?? [],
5153
- getAllPinsForElement: (id) => allPins.filter((p) => (p.entity ?? "") === id),
5154
4981
  getAllPins: () => allPins.slice(),
5155
4982
  attachChannel(channel) {
5156
- return channel.onPin((pin) => {
4983
+ const offPin = channel.onPin((pin) => {
5157
4984
  layer.addPin(pin);
5158
4985
  });
4986
+ const offArchived = channel.onPinArchived?.((reportId) => {
4987
+ layer.removePin(reportId);
4988
+ }) ?? (() => {
4989
+ });
4990
+ return () => {
4991
+ offPin();
4992
+ offArchived();
4993
+ };
5159
4994
  },
5160
4995
  attachCloud(opts) {
5161
4996
  const offCh = opts.channel ? layer.attachChannel(opts.channel) : () => {
@@ -5186,39 +5021,6 @@ function createPinLayer(options) {
5186
5021
  async refresh() {
5187
5022
  if (activeRefresh) await activeRefresh();
5188
5023
  },
5189
- get filterState() {
5190
- return {
5191
- branch: filter.branch,
5192
- commit: filter.commit,
5193
- commits,
5194
- commitIndex
5195
- };
5196
- },
5197
- setFilter(next) {
5198
- filter = {
5199
- branch: next.branch !== void 0 ? next.branch : filter.branch,
5200
- commit: next.commit !== void 0 ? next.commit : filter.commit
5201
- };
5202
- rerender();
5203
- },
5204
- nextCommit() {
5205
- if (!commits.length) return;
5206
- commitIndex = commitIndex >= commits.length - 1 ? -1 : commitIndex + 1;
5207
- filter = {
5208
- ...filter,
5209
- commit: commitIndex === -1 ? null : commits[commitIndex]
5210
- };
5211
- rerender();
5212
- },
5213
- prevCommit() {
5214
- if (!commits.length) return;
5215
- commitIndex = commitIndex <= -1 ? commits.length - 1 : commitIndex - 1;
5216
- filter = {
5217
- ...filter,
5218
- commit: commitIndex === -1 ? null : commits[commitIndex]
5219
- };
5220
- rerender();
5221
- },
5222
5024
  onFilterChange(cb) {
5223
5025
  filterCbs.add(cb);
5224
5026
  return () => {
@@ -5237,7 +5039,7 @@ function createPinLayer(options) {
5237
5039
  },
5238
5040
  destroy() {
5239
5041
  layer.clear();
5240
- detachWin();
5042
+ repositioner.detach();
5241
5043
  detachObs();
5242
5044
  layerEl.remove();
5243
5045
  activeRefresh = null;
@@ -5280,9 +5082,11 @@ function createRouter(options) {
5280
5082
  if (view === null || typeof view !== "object" || typeof view.id !== "string" || view.id.length === 0) {
5281
5083
  throw new ViewValidationError("View must have a non-empty string id");
5282
5084
  }
5283
- if (typeof view.surface !== "function") {
5085
+ const hasSurface = typeof view.surface === "function";
5086
+ const hasRender = typeof view.render === "function";
5087
+ if (!hasSurface && !hasRender) {
5284
5088
  throw new ViewValidationError(
5285
- `View ${view.id}: 'surface' must be a function`
5089
+ `View ${view.id}: a 'surface' function (or a 'render' override) is required`
5286
5090
  );
5287
5091
  }
5288
5092
  if (!view.matches && !view.palette) {
@@ -5340,7 +5144,6 @@ function createRouter(options) {
5340
5144
  if (idx >= 0) recentRefs.splice(idx, 1);
5341
5145
  recentRefs.unshift(ref2);
5342
5146
  if (recentRefs.length > MAX_RECENTS) recentRefs.length = MAX_RECENTS;
5343
- options.session.select(ref2);
5344
5147
  const entry = { id: match.view.id, ref: ref2 };
5345
5148
  const { mode } = options.session.mode.getState();
5346
5149
  if (mode === "idle" || mode === "inspecting") {
@@ -5395,74 +5198,24 @@ function detectDev() {
5395
5198
 
5396
5199
  // src/browser/internal/lit.ts
5397
5200
  import { html, svg, render, nothing } from "lit-html";
5398
- import { repeat } from "lit-html/directives/repeat.js";
5399
5201
  import { ref, createRef } from "lit-html/directives/ref.js";
5400
- import { classMap } from "lit-html/directives/class-map.js";
5401
5202
 
5402
- // src/browser/internal/apply-props.ts
5403
- function applyProps(node, props) {
5404
- const removers = [];
5405
- for (const [key, rawValue] of Object.entries(props)) {
5406
- if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5407
- if (key.startsWith("on") && typeof rawValue === "function") {
5408
- const eventName = key.slice(2).toLowerCase();
5409
- const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5410
- const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5411
- const listener = rawValue;
5412
- node.addEventListener(effectiveEvent, listener);
5413
- removers.push(() => node.removeEventListener(effectiveEvent, listener));
5414
- continue;
5415
- }
5416
- if (rawValue === void 0 || rawValue === null) {
5417
- node.removeAttribute(key);
5418
- continue;
5419
- }
5420
- if (typeof rawValue === "boolean") {
5421
- if (rawValue) node.setAttribute(key, "");
5422
- else node.removeAttribute(key);
5423
- continue;
5424
- }
5425
- if (key === "style" && typeof rawValue === "object") {
5426
- Object.assign(
5427
- node.style,
5428
- rawValue
5429
- );
5430
- continue;
5431
- }
5432
- if (key === "className" || key === "class") {
5433
- node.setAttribute("class", String(rawValue));
5434
- continue;
5435
- }
5436
- if (key === "htmlFor") {
5437
- node.setAttribute("for", String(rawValue));
5438
- continue;
5439
- }
5440
- if (key === "tabIndex") {
5441
- ;
5442
- node.tabIndex = Number(rawValue);
5443
- continue;
5444
- }
5445
- node.setAttribute(key, String(rawValue));
5446
- }
5447
- return () => removers.forEach((fn) => fn());
5448
- }
5449
-
5450
- // src/browser/internal/lit-icon.ts
5451
- import { noChange } from "lit-html";
5452
- import {
5453
- Directive,
5454
- directive,
5455
- PartType
5456
- } from "lit-html/directive.js";
5457
- import { createElement } from "lucide";
5458
- var LucideIconDirective = class extends Directive {
5459
- _icon;
5460
- _class;
5461
- _el;
5462
- constructor(partInfo) {
5463
- super(partInfo);
5464
- if (partInfo.type !== PartType.CHILD) {
5465
- throw new Error("icon() can only be used in a child position");
5203
+ // src/browser/internal/lit-icon.ts
5204
+ import { noChange } from "lit-html";
5205
+ import {
5206
+ Directive,
5207
+ directive,
5208
+ PartType
5209
+ } from "lit-html/directive.js";
5210
+ import { createElement } from "lucide";
5211
+ var LucideIconDirective = class extends Directive {
5212
+ _icon;
5213
+ _class;
5214
+ _el;
5215
+ constructor(partInfo) {
5216
+ super(partInfo);
5217
+ if (partInfo.type !== PartType.CHILD) {
5218
+ throw new Error("icon() can only be used in a child position");
5466
5219
  }
5467
5220
  }
5468
5221
  update(_part, [iconNode, className]) {
@@ -5483,6 +5236,16 @@ var LucideIconDirective = class extends Directive {
5483
5236
  };
5484
5237
  var icon = directive(LucideIconDirective);
5485
5238
 
5239
+ // src/browser/views/primitives/chip.ts
5240
+ var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5241
+ function createChip(options = {}, children = []) {
5242
+ const { class: extra, ...rest } = options;
5243
+ return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5244
+ }
5245
+
5246
+ // src/browser/views/primitives/kind-icon.ts
5247
+ import { createElement as createLucideElement4 } from "lucide";
5248
+
5486
5249
  // src/browser/ui/cva.ts
5487
5250
  function cva(base, config = {}) {
5488
5251
  return (props = {}) => {
@@ -5521,17 +5284,15 @@ var badgeVariants = cva(badgeBase, {
5521
5284
  }
5522
5285
  }
5523
5286
  });
5524
-
5525
- // src/browser/views/primitives/chip.ts
5526
- var CHIP_CLASS = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground";
5527
- function createChip(options = {}, children = []) {
5528
- const { class: extra, ...rest } = options;
5529
- return el("span", { ...rest, class: cn(CHIP_CLASS, extra) }, children);
5287
+ function badgeTpl(content, options = {}) {
5288
+ const { variant, size, class: extra } = options;
5289
+ return html`
5290
+ <span class=${cn(badgeVariants({ variant, size }), extra)} data-slot="badge"
5291
+ >${content}</span
5292
+ >
5293
+ `;
5530
5294
  }
5531
5295
 
5532
- // src/browser/views/primitives/kind-icon.ts
5533
- import { createElement as createLucideElement4 } from "lucide";
5534
-
5535
5296
  // src/browser/views/primitives/icon-tile.ts
5536
5297
  var TILE_CLASS = "inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground";
5537
5298
  var ICON_SIZE_CLASS_RE = /\b(h-\d+|w-\d+|size-\d+)\b/g;
@@ -6039,6 +5800,54 @@ function createScrollArea(props = {}) {
6039
5800
  );
6040
5801
  }
6041
5802
 
5803
+ // src/browser/internal/apply-props.ts
5804
+ function applyProps(node, props) {
5805
+ const removers = [];
5806
+ for (const [key, rawValue] of Object.entries(props)) {
5807
+ if (key === "children" || key === "dangerouslySetInnerHTML") continue;
5808
+ if (key.startsWith("on") && typeof rawValue === "function") {
5809
+ const eventName = key.slice(2).toLowerCase();
5810
+ const isTextControl = node instanceof HTMLInputElement && node.type !== "checkbox" && node.type !== "radio" || node instanceof HTMLTextAreaElement;
5811
+ const effectiveEvent = eventName === "change" && isTextControl ? "input" : eventName;
5812
+ const listener = rawValue;
5813
+ node.addEventListener(effectiveEvent, listener);
5814
+ removers.push(() => node.removeEventListener(effectiveEvent, listener));
5815
+ continue;
5816
+ }
5817
+ if (rawValue === void 0 || rawValue === null) {
5818
+ node.removeAttribute(key);
5819
+ continue;
5820
+ }
5821
+ if (typeof rawValue === "boolean") {
5822
+ if (rawValue) node.setAttribute(key, "");
5823
+ else node.removeAttribute(key);
5824
+ continue;
5825
+ }
5826
+ if (key === "style" && typeof rawValue === "object") {
5827
+ Object.assign(
5828
+ node.style,
5829
+ rawValue
5830
+ );
5831
+ continue;
5832
+ }
5833
+ if (key === "className" || key === "class") {
5834
+ node.setAttribute("class", String(rawValue));
5835
+ continue;
5836
+ }
5837
+ if (key === "htmlFor") {
5838
+ node.setAttribute("for", String(rawValue));
5839
+ continue;
5840
+ }
5841
+ if (key === "tabIndex") {
5842
+ ;
5843
+ node.tabIndex = Number(rawValue);
5844
+ continue;
5845
+ }
5846
+ node.setAttribute(key, String(rawValue));
5847
+ }
5848
+ return () => removers.forEach((fn) => fn());
5849
+ }
5850
+
6042
5851
  // src/browser/views/builder/spread-props.ts
6043
5852
  function spreadProps(node, props) {
6044
5853
  return applyProps(node, props);
@@ -6058,21 +5867,6 @@ function createPersistentSpreads() {
6058
5867
  };
6059
5868
  }
6060
5869
 
6061
- // src/browser/views/render/detail.ts
6062
- import {
6063
- ArchiveX,
6064
- Camera,
6065
- ChevronDown,
6066
- Copy,
6067
- Highlighter as Highlighter2,
6068
- Inbox,
6069
- MessageCircleWarning,
6070
- StickyNote as StickyNote2,
6071
- TicketPlus,
6072
- View,
6073
- createElement as createLucideElement5
6074
- } from "lucide";
6075
-
6076
5870
  // src/browser/internal/arrow-nav.ts
6077
5871
  var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
6078
5872
  function bindArrowNav(options) {
@@ -6144,30 +5938,6 @@ function focusItem(items, idx) {
6144
5938
  items[idx].focus();
6145
5939
  }
6146
5940
 
6147
- // src/browser/views/builder/filter.ts
6148
- function normalizeQuery(query) {
6149
- return query.trim().toLowerCase();
6150
- }
6151
- function matchesQuery(haystack, query) {
6152
- const q = normalizeQuery(query);
6153
- if (!q) return true;
6154
- return haystack.toLowerCase().includes(q);
6155
- }
6156
- function filterEntities(entities, query) {
6157
- const q = normalizeQuery(query);
6158
- if (!q) return entities;
6159
- return entities.filter((e) => {
6160
- const name = displayName(e).toLowerCase();
6161
- const id = entityKey(e).toLowerCase();
6162
- return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6163
- });
6164
- }
6165
- function filterIds(ids, query) {
6166
- const q = normalizeQuery(query);
6167
- if (!q) return ids;
6168
- return ids.filter((id) => id.toLowerCase().includes(q));
6169
- }
6170
-
6171
5941
  // src/browser/views/labels.ts
6172
5942
  var SECTION_LABELS = {
6173
5943
  acceptance: "Acceptance criteria",
@@ -6192,171 +5962,6 @@ var LIST_ITEM_STATE_CLASS = "data-[disabled]:pointer-events-none data-[disabled]
6192
5962
  var LIST_ITEM_INTERACTIVE_CLASS = "uidex-item-interactive [[data-kbd-nav]_&]:focus:bg-accent [[data-kbd-nav]_&]:focus:text-accent-foreground [[data-kbd-nav]_&]:focus-within:bg-accent [[data-kbd-nav]_&]:focus-within:text-accent-foreground";
6193
5963
  var LIST_GROUP_LABEL_CLASS = "text-muted-foreground px-2 py-1.5 text-xs font-medium";
6194
5964
 
6195
- // src/browser/ui/button.ts
6196
- 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";
6197
- var buttonVariants = cva(buttonBase, {
6198
- defaultVariants: { size: "default", variant: "default" },
6199
- variants: {
6200
- size: {
6201
- default: "h-8 px-3",
6202
- sm: "h-7 gap-1.5 px-2.5",
6203
- xs: "h-6 gap-1 rounded-md px-2 text-xs",
6204
- lg: "h-9 px-3.5",
6205
- xl: "h-10 px-4 text-base",
6206
- icon: "size-8",
6207
- "icon-sm": "size-7",
6208
- "icon-lg": "size-9",
6209
- "icon-xs": "size-6 rounded-md"
6210
- },
6211
- variant: {
6212
- default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6213
- destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6214
- "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6215
- ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6216
- link: "text-foreground border-transparent underline-offset-4 hover:underline",
6217
- outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6218
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6219
- }
6220
- }
6221
- });
6222
-
6223
- // src/browser/views/primitives/entity-presence.ts
6224
- var DOM_BACKED_KINDS = /* @__PURE__ */ new Set([
6225
- "element",
6226
- "region",
6227
- "widget",
6228
- "primitive"
6229
- ]);
6230
- var TOUCH_RESOLVE_KINDS = [
6231
- "element",
6232
- "widget",
6233
- "region",
6234
- "primitive"
6235
- ];
6236
- function isAbsentFromPage(ref2, registry) {
6237
- if (DOM_BACKED_KINDS.has(ref2.kind)) {
6238
- return !resolveEntityElement(ref2);
6239
- }
6240
- if (ref2.kind === "flow" && registry) {
6241
- const flow = registry.get("flow", ref2.id);
6242
- if (!flow) return true;
6243
- for (const touchId of flow.touches) {
6244
- for (const kind of TOUCH_RESOLVE_KINDS) {
6245
- const entity = registry.get(kind, touchId);
6246
- if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6247
- }
6248
- }
6249
- return true;
6250
- }
6251
- return false;
6252
- }
6253
-
6254
- // src/browser/ui/kbd.ts
6255
- 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";
6256
- var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6257
- function createCommandShortcut(options = {}, children = []) {
6258
- const { class: extra, attrs, ...rest } = options;
6259
- return el(
6260
- "kbd",
6261
- {
6262
- ...rest,
6263
- class: cn(COMMAND_SHORTCUT_CLASS, extra),
6264
- attrs: { "data-slot": "command-shortcut", ...attrs }
6265
- },
6266
- children
6267
- );
6268
- }
6269
- function kbdTpl(text, className) {
6270
- return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6271
- >${text}</kbd
6272
- >`;
6273
- }
6274
- function commandShortcutTpl(text, className) {
6275
- return html`<kbd
6276
- class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6277
- data-slot="command-shortcut"
6278
- >${text}</kbd
6279
- >`;
6280
- }
6281
-
6282
- // src/browser/views/primitives/row.ts
6283
- var LABEL_CLASS = "min-w-0 flex-1 truncate";
6284
- var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6285
- function fillRowWithHandle(host, content) {
6286
- if (content.leading) host.append(content.leading);
6287
- const label = el("span", { class: LABEL_CLASS, text: content.label });
6288
- host.append(label);
6289
- if (content.subtitle) {
6290
- host.append(
6291
- el("span", {
6292
- class: SUBTITLE_CLASS,
6293
- attrs: { "data-uidex-row-subtitle": "" },
6294
- text: content.subtitle
6295
- })
6296
- );
6297
- }
6298
- if (content.trailing != null) {
6299
- host.append(
6300
- typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6301
- );
6302
- }
6303
- return { label };
6304
- }
6305
- function rowTpl(content) {
6306
- return html`
6307
- ${content.leading ?? nothing}
6308
- <span class=${LABEL_CLASS}>${content.label}</span>
6309
- ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6310
- >${content.subtitle}</span
6311
- >` : nothing}
6312
- ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6313
- `;
6314
- }
6315
-
6316
- // src/browser/views/primitives/entity-link.ts
6317
- var ACTION_CLASS = cn(
6318
- LIST_ITEM_CLASS,
6319
- LIST_ITEM_INTERACTIVE_CLASS,
6320
- "w-full cursor-pointer text-left font-normal"
6321
- );
6322
- function entityLinkTpl(options) {
6323
- const { ctx, target, label, leading, class: extraClass } = options;
6324
- const absent = isAbsentFromPage(target);
6325
- const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6326
- const onClick = () => ctx.views.navigate(target);
6327
- const preview = () => {
6328
- ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6329
- };
6330
- const restoreParent = () => {
6331
- if (ctx.ref) {
6332
- ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6333
- } else {
6334
- ctx.highlight.hide();
6335
- }
6336
- };
6337
- return html`
6338
- <button
6339
- type="button"
6340
- class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6341
- data-slot="list-item-button"
6342
- data-uidex-entity-link
6343
- data-uidex-ref-kind=${target.kind}
6344
- data-uidex-ref-id=${target.id}
6345
- title=${absent ? "Not on this page" : ""}
6346
- @click=${onClick}
6347
- @mouseenter=${preview}
6348
- @mouseleave=${restoreParent}
6349
- @focus=${preview}
6350
- @blur=${restoreParent}
6351
- >
6352
- ${rowTpl({
6353
- leading,
6354
- label: resolvedLabel
6355
- })}
6356
- </button>
6357
- `;
6358
- }
6359
-
6360
5965
  // src/browser/views/primitives/text.ts
6361
5966
  var MUTED_CLASS = "text-sm text-muted-foreground";
6362
5967
  var HEADING_CLASS = LIST_GROUP_LABEL_CLASS;
@@ -6368,7 +5973,20 @@ function headingTpl(text, className) {
6368
5973
  return html`<h3 class=${cn(HEADING_CLASS, className)}>${text}</h3>`;
6369
5974
  }
6370
5975
 
6371
- // src/browser/views/render/detail.ts
5976
+ // src/browser/views/render/detail-actions.ts
5977
+ import {
5978
+ ArchiveX,
5979
+ Camera,
5980
+ ChevronDown,
5981
+ Copy,
5982
+ Highlighter as Highlighter2,
5983
+ Inbox,
5984
+ MessageCircleWarning,
5985
+ StickyNote as StickyNote2,
5986
+ TicketPlus,
5987
+ View,
5988
+ createElement as createLucideElement5
5989
+ } from "lucide";
6372
5990
  var ICON_MAP = {
6373
5991
  "archive-x": ArchiveX,
6374
5992
  copy: Copy,
@@ -6384,23 +6002,6 @@ var ICON_MAP = {
6384
6002
  function iconFor(icon2) {
6385
6003
  return icon2 ? ICON_MAP[icon2] : null;
6386
6004
  }
6387
- function subtitleTpl(subtitle) {
6388
- const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6389
- return html`<p
6390
- class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6391
- data-uidex-detail-subtitle
6392
- >
6393
- ${text}
6394
- </p>`;
6395
- }
6396
- function notFoundTpl(ref2) {
6397
- return html`<p
6398
- class=${cn("text-muted-foreground text-sm", "p-4")}
6399
- data-uidex-detail-missing
6400
- >
6401
- ${ref2.kind}: ${ref2.id} not found in registry
6402
- </p>`;
6403
- }
6404
6005
  function renderActions(actions, ctx, root, heading) {
6405
6006
  const cleanups = [];
6406
6007
  const buttons = [];
@@ -6511,6 +6112,169 @@ function renderActions(actions, ctx, root, heading) {
6511
6112
  }
6512
6113
  return { node: section, buttons, cleanup: composeCleanups(cleanups) };
6513
6114
  }
6115
+
6116
+ // src/browser/views/render/detail-sections.ts
6117
+ import {
6118
+ html as staticHtml,
6119
+ literal
6120
+ } from "lit-html/static.js";
6121
+
6122
+ // src/browser/views/builder/filter.ts
6123
+ function normalizeQuery(query) {
6124
+ return query.trim().toLowerCase();
6125
+ }
6126
+ function matchesQuery(haystack, query) {
6127
+ const q = normalizeQuery(query);
6128
+ if (!q) return true;
6129
+ return haystack.toLowerCase().includes(q);
6130
+ }
6131
+ function filterEntities(entities, query) {
6132
+ const q = normalizeQuery(query);
6133
+ if (!q) return entities;
6134
+ return entities.filter((e) => {
6135
+ const name = displayName(e).toLowerCase();
6136
+ const id = entityKey(e).toLowerCase();
6137
+ return name.includes(q) || id.includes(q) || e.kind.toLowerCase().includes(q);
6138
+ });
6139
+ }
6140
+ function filterIds(ids, query) {
6141
+ const q = normalizeQuery(query);
6142
+ if (!q) return ids;
6143
+ return ids.filter((id) => id.toLowerCase().includes(q));
6144
+ }
6145
+
6146
+ // src/browser/views/primitives/entity-presence.ts
6147
+ var TOUCH_RESOLVE_KINDS = [
6148
+ "element",
6149
+ "widget",
6150
+ "region",
6151
+ "primitive"
6152
+ ];
6153
+ function isAbsentFromPage(ref2, registry) {
6154
+ if (DOM_BACKED_KINDS.has(ref2.kind)) {
6155
+ return !resolveEntityElement(ref2);
6156
+ }
6157
+ if (ref2.kind === "flow" && registry) {
6158
+ const flow = registry.get("flow", ref2.id);
6159
+ if (!flow) return true;
6160
+ for (const touchId of flow.touches) {
6161
+ for (const kind of TOUCH_RESOLVE_KINDS) {
6162
+ const entity = registry.get(kind, touchId);
6163
+ if (entity && resolveEntityElement({ kind, id: touchId })) return false;
6164
+ }
6165
+ }
6166
+ return true;
6167
+ }
6168
+ return false;
6169
+ }
6170
+
6171
+ // src/browser/ui/kbd.ts
6172
+ var KBD_CLASS = "bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3";
6173
+ var COMMAND_SHORTCUT_CLASS = "text-muted-foreground/70 ms-auto font-sans text-xs font-medium tracking-widest";
6174
+ function createCommandShortcut(options = {}, children = []) {
6175
+ const { class: extra, attrs, ...rest } = options;
6176
+ return el(
6177
+ "kbd",
6178
+ {
6179
+ ...rest,
6180
+ class: cn(COMMAND_SHORTCUT_CLASS, extra),
6181
+ attrs: { "data-slot": "command-shortcut", ...attrs }
6182
+ },
6183
+ children
6184
+ );
6185
+ }
6186
+ function kbdTpl(text, className) {
6187
+ return html`<kbd class=${cn(KBD_CLASS, className)} data-slot="kbd"
6188
+ >${text}</kbd
6189
+ >`;
6190
+ }
6191
+ function commandShortcutTpl(text, className) {
6192
+ return html`<kbd
6193
+ class=${cn(COMMAND_SHORTCUT_CLASS, className)}
6194
+ data-slot="command-shortcut"
6195
+ >${text}</kbd
6196
+ >`;
6197
+ }
6198
+
6199
+ // src/browser/views/primitives/row.ts
6200
+ var LABEL_CLASS = "min-w-0 flex-1 truncate";
6201
+ var SUBTITLE_CLASS = "truncate text-xs text-muted-foreground";
6202
+ function fillRowWithHandle(host, content) {
6203
+ if (content.leading) host.append(content.leading);
6204
+ const label = el("span", { class: LABEL_CLASS, text: content.label });
6205
+ host.append(label);
6206
+ if (content.subtitle) {
6207
+ host.append(
6208
+ el("span", {
6209
+ class: SUBTITLE_CLASS,
6210
+ attrs: { "data-uidex-row-subtitle": "" },
6211
+ text: content.subtitle
6212
+ })
6213
+ );
6214
+ }
6215
+ if (content.trailing != null) {
6216
+ host.append(
6217
+ typeof content.trailing === "string" ? createCommandShortcut({ text: content.trailing }) : content.trailing
6218
+ );
6219
+ }
6220
+ return { label };
6221
+ }
6222
+ function rowTpl(content) {
6223
+ return html`
6224
+ ${content.leading ?? nothing}
6225
+ <span class=${LABEL_CLASS}>${content.label}</span>
6226
+ ${content.subtitle ? html`<span class=${SUBTITLE_CLASS} data-uidex-row-subtitle
6227
+ >${content.subtitle}</span
6228
+ >` : nothing}
6229
+ ${content.trailing != null ? typeof content.trailing === "string" ? commandShortcutTpl(content.trailing) : content.trailing : nothing}
6230
+ `;
6231
+ }
6232
+
6233
+ // src/browser/views/primitives/entity-link.ts
6234
+ var ACTION_CLASS = cn(
6235
+ LIST_ITEM_CLASS,
6236
+ LIST_ITEM_INTERACTIVE_CLASS,
6237
+ "w-full cursor-pointer text-left font-normal"
6238
+ );
6239
+ function entityLinkTpl(options) {
6240
+ const { ctx, target, label, leading, class: extraClass } = options;
6241
+ const absent = isAbsentFromPage(target);
6242
+ const resolvedLabel = label ?? `${target.kind}: ${target.id}`;
6243
+ const onClick = () => ctx.views.navigate(target);
6244
+ const preview = () => {
6245
+ ctx.highlight.show(target, { color: KIND_STYLE[target.kind].color });
6246
+ };
6247
+ const restoreParent = () => {
6248
+ if (ctx.ref) {
6249
+ ctx.highlight.show(ctx.ref, { color: KIND_STYLE[ctx.ref.kind].color });
6250
+ } else {
6251
+ ctx.highlight.hide();
6252
+ }
6253
+ };
6254
+ return html`
6255
+ <button
6256
+ type="button"
6257
+ class=${cn(ACTION_CLASS, "w-full", absent && "opacity-50", extraClass)}
6258
+ data-slot="list-item-button"
6259
+ data-uidex-entity-link
6260
+ data-uidex-ref-kind=${target.kind}
6261
+ data-uidex-ref-id=${target.id}
6262
+ title=${absent ? "Not on this page" : ""}
6263
+ @click=${onClick}
6264
+ @mouseenter=${preview}
6265
+ @mouseleave=${restoreParent}
6266
+ @focus=${preview}
6267
+ @blur=${restoreParent}
6268
+ >
6269
+ ${rowTpl({
6270
+ leading,
6271
+ label: resolvedLabel
6272
+ })}
6273
+ </button>
6274
+ `;
6275
+ }
6276
+
6277
+ // src/browser/views/render/detail-sections.ts
6514
6278
  function entityListItems(ctx, entities) {
6515
6279
  return html`${entities.map((entity) => {
6516
6280
  const eRef = { kind: entity.kind, id: entityKey(entity) };
@@ -6524,71 +6288,29 @@ function entityListItems(ctx, entities) {
6524
6288
  </li>`;
6525
6289
  })}`;
6526
6290
  }
6527
- function composesListTpl(ctx, label, entities) {
6528
- if (entities.length === 0) return null;
6529
- return html`
6530
- <section class="flex flex-col" data-uidex-detail-composes>
6531
- ${headingTpl(label)}
6532
- <ul class="flex flex-col">
6533
- ${entityListItems(ctx, entities)}
6534
- </ul>
6535
- </section>
6536
- `;
6537
- }
6538
- function usedByListTpl(ctx, label, entities) {
6539
- if (entities.length === 0) return null;
6540
- return html`
6541
- <section class="flex flex-col" data-uidex-detail-used-by>
6542
- ${headingTpl(label)}
6543
- <ul class="flex flex-col">
6544
- ${entityListItems(ctx, entities)}
6545
- </ul>
6546
- </section>
6547
- `;
6548
- }
6549
- function flowListTpl(ctx, flows) {
6550
- if (flows.length === 0) return null;
6551
- return html`
6552
- <section class="flex flex-col" data-uidex-detail-flows>
6553
- ${headingTpl(SECTION_LABELS.flows)}
6554
- <ul class="flex flex-col">
6555
- ${flows.map(
6556
- (flow) => html`<li>
6557
- ${entityLinkTpl({
6558
- ctx,
6559
- target: { kind: "flow", id: flow.id },
6560
- label: displayName(flow),
6561
- leading: kindIconTileTpl("flow")
6562
- })}
6563
- </li>`
6564
- )}
6565
- </ul>
6566
- </section>
6567
- `;
6568
- }
6569
- function touchesTpl(ctx, entities, query) {
6291
+ var DETAIL_SECTION_ATTRS = {
6292
+ composes: literal`data-uidex-detail-composes`,
6293
+ "used-by": literal`data-uidex-detail-used-by`,
6294
+ flows: literal`data-uidex-detail-flows`,
6295
+ touches: literal`data-uidex-detail-touches`
6296
+ };
6297
+ function entitySectionTpl(ctx, opts) {
6298
+ const { label, entities, dataAttr, emptyText } = opts;
6299
+ const attr = DETAIL_SECTION_ATTRS[dataAttr];
6570
6300
  if (entities.length === 0) {
6571
- return html`
6572
- <section class="flex flex-col gap-2" data-uidex-detail-touches>
6573
- ${headingTpl(SECTION_LABELS.touches)}
6574
- ${mutedTextTpl(query ? "No matches" : "No entities touched")}
6301
+ if (emptyText === void 0) return null;
6302
+ return staticHtml`
6303
+ <section class="flex flex-col gap-2" ${attr}>
6304
+ ${headingTpl(label)}
6305
+ ${mutedTextTpl(emptyText)}
6575
6306
  </section>
6576
6307
  `;
6577
6308
  }
6578
- return html`
6579
- <section class="flex flex-col" data-uidex-detail-touches>
6580
- ${headingTpl(SECTION_LABELS.touches)}
6309
+ return staticHtml`
6310
+ <section class="flex flex-col" ${attr}>
6311
+ ${headingTpl(label)}
6581
6312
  <ul class="flex flex-col">
6582
- ${entities.map(
6583
- (entity) => html`<li>
6584
- ${entityLinkTpl({
6585
- ctx,
6586
- target: { kind: entity.kind, id: entityKey(entity) },
6587
- label: displayName(entity),
6588
- leading: kindIconTileTpl(entity.kind)
6589
- })}
6590
- </li>`
6591
- )}
6313
+ ${entityListItems(ctx, entities)}
6592
6314
  </ul>
6593
6315
  </section>
6594
6316
  `;
@@ -6745,6 +6467,20 @@ function screenshotTpl(url) {
6745
6467
  </section>
6746
6468
  `;
6747
6469
  }
6470
+ function lazyScreenshotEl(load) {
6471
+ const holder = document.createElement("div");
6472
+ holder.hidden = true;
6473
+ void load().then(
6474
+ (url) => {
6475
+ if (!url) return;
6476
+ render(screenshotTpl(url), holder);
6477
+ holder.hidden = false;
6478
+ },
6479
+ () => {
6480
+ }
6481
+ );
6482
+ return holder;
6483
+ }
6748
6484
  function metadataListTpl(entries) {
6749
6485
  return html`
6750
6486
  <div
@@ -6775,28 +6511,30 @@ function sectionTpl(section, ctx, query) {
6775
6511
  return acceptanceChecklistTpl(section.items);
6776
6512
  }
6777
6513
  case "composes":
6778
- return composesListTpl(
6779
- ctx,
6780
- section.label,
6781
- section.filterable ? filterEntities(section.entities, query) : section.entities
6782
- );
6514
+ return entitySectionTpl(ctx, {
6515
+ label: section.label,
6516
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6517
+ dataAttr: "composes"
6518
+ });
6783
6519
  case "used-by":
6784
- return usedByListTpl(
6785
- ctx,
6786
- section.label,
6787
- section.filterable ? filterEntities(section.entities, query) : section.entities
6788
- );
6520
+ return entitySectionTpl(ctx, {
6521
+ label: section.label,
6522
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6523
+ dataAttr: "used-by"
6524
+ });
6789
6525
  case "flows":
6790
- return flowListTpl(
6791
- ctx,
6792
- section.filterable ? filterEntities(section.flows, query) : section.flows
6793
- );
6526
+ return entitySectionTpl(ctx, {
6527
+ label: SECTION_LABELS.flows,
6528
+ entities: section.filterable ? filterEntities(section.flows, query) : section.flows,
6529
+ dataAttr: "flows"
6530
+ });
6794
6531
  case "touches":
6795
- return touchesTpl(
6796
- ctx,
6797
- section.filterable ? filterEntities(section.entities, query) : section.entities,
6798
- query
6799
- );
6532
+ return entitySectionTpl(ctx, {
6533
+ label: SECTION_LABELS.touches,
6534
+ entities: section.filterable ? filterEntities(section.entities, query) : section.entities,
6535
+ dataAttr: "touches",
6536
+ emptyText: query ? "No matches" : "No entities touched"
6537
+ });
6800
6538
  case "steps":
6801
6539
  return stepsTpl(
6802
6540
  ctx,
@@ -6808,11 +6546,32 @@ function sectionTpl(section, ctx, query) {
6808
6546
  section.filterable ? filterIds(section.paths, query) : section.paths
6809
6547
  );
6810
6548
  case "screenshot":
6811
- return screenshotTpl(section.url);
6549
+ if (section.url) return screenshotTpl(section.url);
6550
+ if (section.load) return html`${lazyScreenshotEl(section.load)}`;
6551
+ return null;
6812
6552
  case "metadata":
6813
6553
  return section.entries.length > 0 ? metadataListTpl(section.entries) : null;
6814
6554
  }
6815
6555
  }
6556
+
6557
+ // src/browser/views/render/detail.ts
6558
+ function subtitleTpl(subtitle) {
6559
+ const text = subtitle.extra ? `${subtitle.rawId} \xB7 ${subtitle.extra}` : subtitle.rawId;
6560
+ return html`<p
6561
+ class=${cn("text-muted-foreground font-mono text-xs", "px-2")}
6562
+ data-uidex-detail-subtitle
6563
+ >
6564
+ ${text}
6565
+ </p>`;
6566
+ }
6567
+ function notFoundTpl(ref2) {
6568
+ return html`<p
6569
+ class=${cn("text-muted-foreground text-sm", "p-4")}
6570
+ data-uidex-detail-missing
6571
+ >
6572
+ ${ref2.kind}: ${ref2.id} not found in registry
6573
+ </p>`;
6574
+ }
6816
6575
  function hasFilterableSections(sections) {
6817
6576
  return sections.some((s) => "filterable" in s && s.filterable === true);
6818
6577
  }
@@ -6877,7 +6636,10 @@ function renderDetailSurface(surface, ctx, root) {
6877
6636
  >
6878
6637
  ${surface.title}
6879
6638
  </h2>` : nothing}
6880
- <span class="ml-auto">${kindBadgeTpl(surface.entityKind)}</span>
6639
+ <span class="ml-auto flex items-center gap-1">
6640
+ ${surface.unregistered ? badgeTpl("Unregistered", { variant: "warning", size: "sm" }) : nothing}
6641
+ ${kindBadgeTpl(surface.entityKind)}
6642
+ </span>
6881
6643
  </div>
6882
6644
  ${surface.subtitle ? subtitleTpl(surface.subtitle) : nothing}
6883
6645
  <div
@@ -7170,6 +6932,34 @@ function createScreenshotLightbox(trigger, dataUrl, options) {
7170
6932
  };
7171
6933
  }
7172
6934
 
6935
+ // src/browser/ui/button.ts
6936
+ var buttonBase = "focus-visible:ring-ring focus-visible:ring-offset-background disabled:opacity-60 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border text-sm font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg]:pointer-events-none [&_svg]:shrink-0";
6937
+ var buttonVariants = cva(buttonBase, {
6938
+ defaultVariants: { size: "default", variant: "default" },
6939
+ variants: {
6940
+ size: {
6941
+ default: "h-8 px-3",
6942
+ sm: "h-7 gap-1.5 px-2.5",
6943
+ xs: "h-6 gap-1 rounded-md px-2 text-xs",
6944
+ lg: "h-9 px-3.5",
6945
+ xl: "h-10 px-4 text-base",
6946
+ icon: "size-8",
6947
+ "icon-sm": "size-7",
6948
+ "icon-lg": "size-9",
6949
+ "icon-xs": "size-6 rounded-md"
6950
+ },
6951
+ variant: {
6952
+ default: "border-primary bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
6953
+ destructive: "border-destructive bg-destructive shadow-xs hover:bg-destructive/90 text-white",
6954
+ "destructive-outline": "border-input bg-popover text-destructive-foreground shadow-xs/5 hover:border-destructive/30 hover:bg-destructive/5 dark:bg-input/30",
6955
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground border-transparent",
6956
+ link: "text-foreground border-transparent underline-offset-4 hover:underline",
6957
+ outline: "border-input bg-popover text-foreground shadow-xs/5 hover:bg-accent hover:text-accent-foreground dark:bg-input/30",
6958
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 border-transparent"
6959
+ }
6960
+ }
6961
+ });
6962
+
7173
6963
  // src/browser/views/render/form.ts
7174
6964
  var fieldSeq = 0;
7175
6965
  var nextFieldId = () => `uidex-field-${++fieldSeq}`;
@@ -7366,9 +7156,9 @@ function iconMediaTpl(iconTpl) {
7366
7156
  </div>
7367
7157
  `;
7368
7158
  }
7369
- function loadingViewTpl() {
7159
+ function loadingViewTpl(rootRef) {
7370
7160
  return html`
7371
- <div class=${EMPTY_ROOT} aria-live="polite" hidden>
7161
+ <div class=${EMPTY_ROOT} aria-live="polite" hidden ${ref(rootRef)}>
7372
7162
  ${iconMediaTpl(icon(Loader2, "animate-spin"))}
7373
7163
  <div class="flex max-w-sm flex-col items-center text-center">
7374
7164
  <div class=${SKELETON + " h-6 w-36 rounded-md"}></div>
@@ -7381,42 +7171,47 @@ function loadingViewTpl() {
7381
7171
  </div>
7382
7172
  `;
7383
7173
  }
7384
- function successViewTpl() {
7174
+ function successViewTpl(refs) {
7385
7175
  return html`
7386
- <div class=${EMPTY_ROOT} hidden data-uidex-success-view>
7176
+ <div class=${EMPTY_ROOT} hidden data-uidex-success-view ${ref(refs.root)}>
7387
7177
  ${iconMediaTpl(icon(CircleCheck))}
7388
7178
  <div class="flex max-w-sm flex-col items-center text-center">
7389
7179
  <div
7390
7180
  class="font-heading text-xl font-semibold"
7391
7181
  data-uidex-success-title
7182
+ ${ref(refs.title)}
7392
7183
  ></div>
7393
7184
  </div>
7394
7185
  <div
7395
7186
  class="flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm"
7396
7187
  data-uidex-success-actions
7188
+ ${ref(refs.actions)}
7397
7189
  >
7398
7190
  <a
7399
7191
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7400
7192
  target="_blank"
7401
7193
  rel="noreferrer"
7402
7194
  data-uidex-success-link
7195
+ ${ref(refs.link)}
7403
7196
  ></a>
7404
7197
  </div>
7405
7198
  </div>
7406
7199
  `;
7407
7200
  }
7408
- function errorViewTpl() {
7201
+ function errorViewTpl(refs) {
7409
7202
  return html`
7410
- <div class=${EMPTY_ROOT} hidden data-uidex-error-view>
7203
+ <div class=${EMPTY_ROOT} hidden data-uidex-error-view ${ref(refs.root)}>
7411
7204
  ${iconMediaTpl(icon(CircleX))}
7412
7205
  <div class="flex max-w-sm flex-col items-center gap-1 text-center">
7413
7206
  <div
7414
7207
  class="font-heading text-xl font-semibold"
7415
7208
  data-uidex-error-title
7209
+ ${ref(refs.title)}
7416
7210
  ></div>
7417
7211
  <p
7418
7212
  class="text-muted-foreground text-sm"
7419
7213
  data-uidex-error-description
7214
+ ${ref(refs.description)}
7420
7215
  ></p>
7421
7216
  </div>
7422
7217
  <div
@@ -7427,6 +7222,7 @@ function errorViewTpl() {
7427
7222
  class=${buttonVariants({ variant: "outline", size: "sm" })}
7428
7223
  data-slot="button"
7429
7224
  data-uidex-retry-button
7225
+ ${ref(refs.retry)}
7430
7226
  >
7431
7227
  Try Again
7432
7228
  </button>
@@ -7468,8 +7264,15 @@ function renderFormSurface(surface, ctx, root) {
7468
7264
  });
7469
7265
  }
7470
7266
  const formRef = createRef();
7471
- const statusRef = createRef();
7472
- const submitRef = createRef();
7267
+ const loadingRef = createRef();
7268
+ const successRef = createRef();
7269
+ const successTitleRef = createRef();
7270
+ const successLinkRef = createRef();
7271
+ const successActionsRef = createRef();
7272
+ const errorRef = createRef();
7273
+ const errorTitleRef = createRef();
7274
+ const errorDescriptionRef = createRef();
7275
+ const retryRef = createRef();
7473
7276
  render(
7474
7277
  html`
7475
7278
  <section
@@ -7484,7 +7287,19 @@ function renderFormSurface(surface, ctx, root) {
7484
7287
  novalidate
7485
7288
  data-uidex-form=${surface.id}
7486
7289
  ></form>
7487
- ${loadingViewTpl()} ${successViewTpl()} ${errorViewTpl()}
7290
+ ${loadingViewTpl(loadingRef)}
7291
+ ${successViewTpl({
7292
+ root: successRef,
7293
+ title: successTitleRef,
7294
+ link: successLinkRef,
7295
+ actions: successActionsRef
7296
+ })}
7297
+ ${errorViewTpl({
7298
+ root: errorRef,
7299
+ title: errorTitleRef,
7300
+ description: errorDescriptionRef,
7301
+ retry: retryRef
7302
+ })}
7488
7303
  </section>
7489
7304
  `,
7490
7305
  root
@@ -7514,32 +7329,15 @@ function renderFormSurface(surface, ctx, root) {
7514
7329
  }
7515
7330
  });
7516
7331
  form.append(status);
7517
- const sectionEl = root.querySelector(
7518
- "[data-uidex-form-surface]"
7519
- );
7520
- const loadingView = sectionEl.querySelector("[aria-live='polite'][hidden]") ?? sectionEl.children[1];
7521
- const successView = sectionEl.querySelector(
7522
- "[data-uidex-success-view]"
7523
- );
7524
- const successTitle = sectionEl.querySelector(
7525
- "[data-uidex-success-title]"
7526
- );
7527
- const successLink = sectionEl.querySelector(
7528
- "[data-uidex-success-link]"
7529
- );
7530
- const successActions = successLink.parentElement;
7531
- const errorView = sectionEl.querySelector(
7532
- "[data-uidex-error-view]"
7533
- );
7534
- const errorTitle = sectionEl.querySelector(
7535
- "[data-uidex-error-title]"
7536
- );
7537
- const errorDescription = sectionEl.querySelector(
7538
- "[data-uidex-error-description]"
7539
- );
7540
- const retryButton = sectionEl.querySelector(
7541
- "[data-uidex-retry-button]"
7542
- );
7332
+ const loadingView = loadingRef.value;
7333
+ const successView = successRef.value;
7334
+ const successTitle = successTitleRef.value;
7335
+ const successLink = successLinkRef.value;
7336
+ const successActions = successActionsRef.value;
7337
+ const errorView = errorRef.value;
7338
+ const errorTitle = errorTitleRef.value;
7339
+ const errorDescription = errorDescriptionRef.value;
7340
+ const retryButton = retryRef.value;
7543
7341
  function clearAllFieldErrors() {
7544
7342
  for (const f of fields) f.setError(null);
7545
7343
  }
@@ -7940,7 +7738,7 @@ function defaultFilter(item, query) {
7940
7738
  function rowTag(item) {
7941
7739
  return item.tag ?? item.value;
7942
7740
  }
7943
- function resolveLeadingTpl(item, ctx) {
7741
+ function resolveLeadingTpl(item) {
7944
7742
  if (item.entityChip) return kindIconTileTpl(item.entityChip.entity.kind);
7945
7743
  if (item.leading) {
7946
7744
  const node = item.leading();
@@ -8045,7 +7843,7 @@ function buildItemsDom(surface, ctx, filteredItems, allByValue, content) {
8045
7843
  data-uidex-item-value=${item.value}
8046
7844
  >
8047
7845
  ${rowTpl({
8048
- leading: resolveLeadingTpl(item, ctx),
7846
+ leading: resolveLeadingTpl(item),
8049
7847
  label: item.label,
8050
7848
  subtitle: item.subtitle,
8051
7849
  trailing: item.trailing ?? item.shortcut
@@ -8090,7 +7888,7 @@ function renderListSurface(surface, ctx, root) {
8090
7888
  return root.ownerDocument ?? document;
8091
7889
  };
8092
7890
  const hasDefault = surface.defaultHighlight !== void 0 && allByValue.has(surface.defaultHighlight);
8093
- const { section, scrollRoot, viewport, content } = renderShell(surface, root);
7891
+ const { scrollRoot, viewport, content } = renderShell(surface, root);
8094
7892
  let maps = buildItemsDom(surface, ctx, filteredItems, allByValue, content);
8095
7893
  const controller = createListController({
8096
7894
  surfaceId: surface.id,
@@ -8786,52 +8584,16 @@ function sameRef2(a, b) {
8786
8584
  if (a === null || b === null) return false;
8787
8585
  return a.kind === b.kind && a.id === b.id;
8788
8586
  }
8789
- function resolveHints(view, ctx) {
8790
- const h = view.hints;
8791
- if (!h) return [];
8792
- if (typeof h === "function") {
8793
- try {
8794
- return h(ctx) ?? [];
8795
- } catch (err) {
8796
- console.error(`[uidex] view "${view.id}" hints() threw`, err);
8797
- return [];
8798
- }
8799
- }
8800
- return h;
8801
- }
8802
- function resolveTitle(view, ctx) {
8803
- const t = view.title;
8804
- if (!t) return "";
8805
- if (typeof t === "function") {
8806
- try {
8807
- return t(ctx) ?? "";
8808
- } catch (err) {
8809
- console.error(`[uidex] view "${view.id}" title() threw`, err);
8810
- return "";
8811
- }
8812
- }
8813
- return t;
8814
- }
8815
- function resolveActions(view, ctx, globalActions) {
8816
- const result = [];
8817
- const fn = view.actions;
8818
- if (fn) {
8819
- try {
8820
- const viewActions2 = fn(ctx) ?? [];
8821
- result.push(...viewActions2);
8822
- } catch (err) {
8823
- console.error(`[uidex] view "${view.id}" actions() threw`, err);
8824
- }
8825
- }
8826
- if (globalActions) {
8587
+ function resolveProp(view, ctx, value, propName, fallback) {
8588
+ if (typeof value === "function") {
8827
8589
  try {
8828
- const globals = globalActions(ctx) ?? [];
8829
- result.push(...globals);
8590
+ return value(ctx) ?? fallback;
8830
8591
  } catch (err) {
8831
- console.error(`[uidex] globalActions() threw`, err);
8592
+ console.error(`[uidex] view "${view.id}" ${propName}() threw`, err);
8593
+ return fallback;
8832
8594
  }
8833
8595
  }
8834
- return result;
8596
+ return value ?? fallback;
8835
8597
  }
8836
8598
  function createViewStack(options) {
8837
8599
  const { container, views, session, registry, highlight } = options;
@@ -8899,39 +8661,40 @@ function createViewStack(options) {
8899
8661
  color: KIND_STYLE[top.ctx.ref.kind].color
8900
8662
  });
8901
8663
  }
8902
- function updateChrome() {
8903
- if (!shell) return;
8904
- const top = mounted[mounted.length - 1];
8905
- if (!top) return;
8664
+ function updateNavButtons(shell2, top) {
8906
8665
  const atRoot = mounted.length <= 1 && top.view.id === "command-palette";
8907
- shell.backBtn.hidden = atRoot;
8908
- shell.searchIcon.hidden = !atRoot;
8666
+ shell2.backBtn.hidden = atRoot;
8667
+ shell2.searchIcon.hidden = !atRoot;
8668
+ }
8669
+ function updateTitle(shell2, top) {
8909
8670
  const searchable = top.view.searchable !== false;
8910
- shell.searchInput.hidden = !searchable;
8911
- const titleText = searchable ? "" : resolveTitle(top.view, top.ctx);
8912
- shell.headerTitle.textContent = titleText;
8913
- shell.headerTitle.hidden = searchable || !titleText;
8914
- shell.footerLeft.replaceChildren();
8671
+ shell2.searchInput.hidden = !searchable;
8672
+ const titleText = searchable ? "" : resolveProp(top.view, top.ctx, top.view.title, "title", "");
8673
+ shell2.headerTitle.textContent = titleText;
8674
+ shell2.headerTitle.hidden = searchable || !titleText;
8675
+ }
8676
+ function updateFooterChip(shell2, top) {
8677
+ shell2.footerLeft.replaceChildren();
8915
8678
  const refEntity = top.ctx.ref ? top.ctx.registry.get(top.ctx.ref.kind, top.ctx.ref.id) : null;
8916
8679
  if (refEntity) {
8917
- shell.footerLeft.append(
8680
+ shell2.footerLeft.append(
8918
8681
  renderKindChip({
8919
8682
  entity: refEntity,
8920
8683
  withKindName: true
8921
8684
  })
8922
8685
  );
8923
8686
  } else {
8924
- shell.footerLeft.append(shell.logo);
8687
+ shell2.footerLeft.append(shell2.logo);
8925
8688
  }
8926
- hintChangeSub?.();
8927
- hintChangeSub = null;
8928
- highlightActionsSub?.();
8929
- highlightActionsSub = null;
8930
- shell.footerRight.replaceChildren();
8931
- const footerItems = [];
8932
- const enterHint = resolveHints(top.view, top.ctx).find(
8933
- (h) => h.key.includes("\u21B5")
8934
- );
8689
+ }
8690
+ function buildEnterHint(top, footerItems) {
8691
+ const enterHint = resolveProp(
8692
+ top.view,
8693
+ top.ctx,
8694
+ top.view.hints,
8695
+ "hints",
8696
+ []
8697
+ ).find((h) => h.key.includes("\u21B5"));
8935
8698
  const src = top.mounted.submitIntent;
8936
8699
  if (src) {
8937
8700
  const key = enterHint?.key ?? "\u21B5";
@@ -8952,28 +8715,52 @@ function createViewStack(options) {
8952
8715
  } else if (enterHint) {
8953
8716
  footerItems.push(createHint(enterHint.key, enterHint.label));
8954
8717
  }
8718
+ }
8719
+ function buildActions(shell2, top, footerItems) {
8955
8720
  const actionsDivider = createFooterDivider();
8956
- const viewActions2 = resolveActions(top.view, top.ctx);
8721
+ const viewActions2 = resolveProp(
8722
+ top.view,
8723
+ top.ctx,
8724
+ top.view.actions,
8725
+ "actions",
8726
+ []
8727
+ );
8957
8728
  const hlSrc = top.mounted.highlightActions;
8958
8729
  const syncActions = () => {
8959
8730
  const hlActions = hlSrc?.get() ?? [];
8960
8731
  const combined = [...hlActions, ...viewActions2];
8961
- shell.actionsPopup.setActions(combined);
8732
+ shell2.actionsPopup.setActions(combined);
8962
8733
  const visible = combined.length > 0;
8963
- shell.actionsPopup.trigger.hidden = !visible;
8734
+ shell2.actionsPopup.trigger.hidden = !visible;
8964
8735
  actionsDivider.hidden = !visible || footerItems.length === 0;
8965
8736
  };
8966
8737
  if (hlSrc) {
8967
8738
  highlightActionsSub = hlSrc.subscribe(syncActions);
8968
8739
  }
8969
8740
  for (let i = 0; i < footerItems.length; i++) {
8970
- if (i > 0) shell.footerRight.append(createFooterDivider());
8971
- shell.footerRight.append(footerItems[i]);
8741
+ if (i > 0) shell2.footerRight.append(createFooterDivider());
8742
+ shell2.footerRight.append(footerItems[i]);
8972
8743
  }
8973
- shell.footerRight.append(actionsDivider);
8974
- shell.footerRight.append(shell.actionsPopup.trigger);
8744
+ shell2.footerRight.append(actionsDivider);
8745
+ shell2.footerRight.append(shell2.actionsPopup.trigger);
8975
8746
  syncActions();
8976
8747
  }
8748
+ function updateChrome() {
8749
+ if (!shell) return;
8750
+ const top = mounted[mounted.length - 1];
8751
+ if (!top) return;
8752
+ updateNavButtons(shell, top);
8753
+ updateTitle(shell, top);
8754
+ updateFooterChip(shell, top);
8755
+ hintChangeSub?.();
8756
+ hintChangeSub = null;
8757
+ highlightActionsSub?.();
8758
+ highlightActionsSub = null;
8759
+ shell.footerRight.replaceChildren();
8760
+ const footerItems = [];
8761
+ buildEnterHint(top, footerItems);
8762
+ buildActions(shell, top, footerItems);
8763
+ }
8977
8764
  function render2() {
8978
8765
  if (!container.isConnected) return;
8979
8766
  const stack = session.getState().stack;
@@ -9070,7 +8857,7 @@ function createViewStack(options) {
9070
8857
 
9071
8858
  // src/browser/views/built-in/ids.ts
9072
8859
  var BUILT_IN_VIEW_IDS = {
9073
- archiveReason: "archive-reason",
8860
+ closeReason: "close-reason",
9074
8861
  commandPalette: "command-palette",
9075
8862
  elements: "elements",
9076
8863
  entityReports: "entity-reports",
@@ -9121,7 +8908,7 @@ function parentDetail(ref2) {
9121
8908
  return { id: DETAIL_VIEW_FOR_KIND[ref2.kind], ref: ref2 };
9122
8909
  }
9123
8910
 
9124
- // src/browser/views/built-in/archive-reason.ts
8911
+ // src/browser/views/built-in/close-reason.ts
9125
8912
  import {
9126
8913
  Ban,
9127
8914
  BugOff,
@@ -9131,10 +8918,10 @@ import {
9131
8918
  createElement as createLucideElement6
9132
8919
  } from "lucide";
9133
8920
  var pendingReportId = null;
9134
- var afterArchive = null;
9135
- function setArchiveTarget(reportId, onDone) {
8921
+ var afterClose = null;
8922
+ function setCloseTarget(reportId, onDone) {
9136
8923
  pendingReportId = reportId;
9137
- afterArchive = onDone;
8924
+ afterClose = onDone;
9138
8925
  }
9139
8926
  function leadingIcon(iconNode) {
9140
8927
  return () => {
@@ -9149,16 +8936,16 @@ function spinnerTile() {
9149
8936
  return createIconTile(spinner);
9150
8937
  }
9151
8938
  var REASONS = [
9152
- { value: "fixed", label: "Fixed", icon: CircleCheck2 },
8939
+ { value: "fixed", label: "Resolved", icon: CircleCheck2 },
9153
8940
  { value: "not_a_bug", label: "Not a bug", icon: BugOff },
9154
8941
  { value: "duplicate", label: "Duplicate", icon: Copy2 },
9155
8942
  { value: "wont_fix", label: "Won't fix", icon: Ban }
9156
8943
  ];
9157
- var archiveReasonView = {
9158
- id: BUILT_IN_VIEW_IDS.archiveReason,
8944
+ var closeReasonView = {
8945
+ id: BUILT_IN_VIEW_IDS.closeReason,
9159
8946
  matches: () => false,
9160
8947
  parent: (ref2) => ref2 ? { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 } : null,
9161
- title: "Archive reason",
8948
+ title: "Close reason",
9162
8949
  searchable: false,
9163
8950
  focusTarget: (host) => host.querySelector("[data-uidex-list-content]"),
9164
8951
  surface: () => ({ kind: "list", id: "unused", items: [] }),
@@ -9171,12 +8958,12 @@ var archiveReasonView = {
9171
8958
  }));
9172
8959
  const surface = {
9173
8960
  kind: "list",
9174
- id: "uidex-archive-reason",
8961
+ id: "uidex-close-reason",
9175
8962
  searchable: false,
9176
8963
  items,
9177
8964
  emptyLabel: "No reasons available",
9178
8965
  onSelect: async (item) => {
9179
- if (busy || !pendingReportId || !ctx.registry.archiveReport) return;
8966
+ if (busy || !pendingReportId || !ctx.registry.closeReport) return;
9180
8967
  busy = true;
9181
8968
  const row = root.querySelector(
9182
8969
  `[data-uidex-item-value="${item.value}"]`
@@ -9188,13 +8975,10 @@ var archiveReasonView = {
9188
8975
  row.style.pointerEvents = "none";
9189
8976
  }
9190
8977
  try {
9191
- await ctx.registry.archiveReport(
9192
- pendingReportId,
9193
- item.value
9194
- );
9195
- const done = afterArchive;
8978
+ await ctx.registry.closeReport(pendingReportId, item.value);
8979
+ const done = afterClose;
9196
8980
  pendingReportId = null;
9197
- afterArchive = null;
8981
+ afterClose = null;
9198
8982
  done?.();
9199
8983
  } catch {
9200
8984
  busy = false;
@@ -9212,7 +8996,7 @@ var archiveReasonView = {
9212
8996
  }
9213
8997
  const error = document.createElement("p");
9214
8998
  error.className = "text-destructive px-4 py-2 text-xs";
9215
- error.textContent = "Archive failed \u2014 try again";
8999
+ error.textContent = "Close failed \u2014 try again";
9216
9000
  root.querySelector("[data-uidex-list-content]")?.prepend(error);
9217
9001
  setTimeout(() => error.remove(), 3e3);
9218
9002
  }
@@ -9539,7 +9323,8 @@ function createCommandPaletteView(shortcut) {
9539
9323
  }
9540
9324
 
9541
9325
  // src/browser/internal/screenshot.ts
9542
- import { domToPng } from "modern-screenshot";
9326
+ import { domToPng, domToWebp } from "modern-screenshot";
9327
+ var WEBP_QUALITY = 0.85;
9543
9328
  function resolveBackgroundColor(el2) {
9544
9329
  let node = el2;
9545
9330
  while (node) {
@@ -9553,8 +9338,7 @@ function isUidexChrome(node) {
9553
9338
  if (node.nodeType !== Node.ELEMENT_NODE) return false;
9554
9339
  const el2 = node;
9555
9340
  if (el2.shadowRoot !== null) return true;
9556
- const cls = el2.classList;
9557
- return cls.contains(SURFACE_HOST_CLASS) || cls.contains(SURFACE_CONTAINER_CLASS);
9341
+ return el2.classList.contains(SURFACE_HOST_CLASS);
9558
9342
  }
9559
9343
  async function captureScreenshot(options = {}) {
9560
9344
  if (typeof document === "undefined") {
@@ -9566,8 +9350,10 @@ async function captureScreenshot(options = {}) {
9566
9350
  const scale = options.maxWidth && width > options.maxWidth ? options.maxWidth / width : 1;
9567
9351
  const padding = 16;
9568
9352
  const height = target.scrollHeight || target.clientHeight;
9569
- return domToPng(target, {
9353
+ const encode = options.format === "png" ? domToPng : domToWebp;
9354
+ return encode(target, {
9570
9355
  scale,
9356
+ quality: WEBP_QUALITY,
9571
9357
  width: width + padding * 2,
9572
9358
  height: height + padding * 2,
9573
9359
  backgroundColor: resolveBackgroundColor(target),
@@ -9623,12 +9409,6 @@ function collectFlowsTouching(ctx, targetId) {
9623
9409
  }
9624
9410
 
9625
9411
  // src/browser/views/builder/detail-builder.ts
9626
- var DOM_BACKED_KINDS2 = /* @__PURE__ */ new Set([
9627
- "element",
9628
- "region",
9629
- "widget",
9630
- "primitive"
9631
- ]);
9632
9412
  var DETAIL_HINTS = [{ key: "\u21B5", label: "Open" }];
9633
9413
  function copyPathAction(ref2, loc) {
9634
9414
  const identifier = `${ref2.kind}:${ref2.id}`;
@@ -9730,7 +9510,10 @@ function copyScreenshotAction(ref2) {
9730
9510
  const target = resolveEntityElement(ref2) ?? void 0;
9731
9511
  const dataUrl = await captureScreenshot({
9732
9512
  target,
9733
- maxWidth: 1280
9513
+ maxWidth: 1280,
9514
+ // Clipboard write requires PNG — the async Clipboard API rejects
9515
+ // image/webp (`ClipboardItem` throws on Chrome/Safari).
9516
+ format: "png"
9734
9517
  });
9735
9518
  const res = await fetch(dataUrl);
9736
9519
  const blob = await res.blob();
@@ -9763,12 +9546,11 @@ function createEntityDetailView(config) {
9763
9546
  if (!ctx.ref || ctx.ref.kind !== kind) {
9764
9547
  return { kind: "detail", entityKind: kind };
9765
9548
  }
9766
- const entity = ctx.registry.get(kind, ctx.ref.id);
9767
- if (!entity) {
9768
- return { kind: "detail", entityKind: kind, notFound: ctx.ref };
9769
- }
9770
- const metaEntity = entity;
9771
- const meta = metaEntity.meta;
9549
+ const exactEntity = ctx.registry.get(kind, ctx.ref.id);
9550
+ const patternEntity = exactEntity ? void 0 : ctx.registry.matchPattern?.(kind, ctx.ref.id);
9551
+ const entity = exactEntity ?? patternEntity;
9552
+ const metaEntity = entity ? entity : null;
9553
+ const meta = metaEntity?.meta;
9772
9554
  const actions = [];
9773
9555
  const cloud = ctx.cloud;
9774
9556
  actions.push({ ...reportAction(ctx.ref), group: "Report" });
@@ -9782,18 +9564,20 @@ function createEntityDetailView(config) {
9782
9564
  if (cloud?.integrations.getCachedConfig()?.hasJira) {
9783
9565
  actions.push({ ...jiraAction(ctx.ref), group: "Report" });
9784
9566
  }
9785
- if (DOM_BACKED_KINDS2.has(kind) && resolveEntityElement(ctx.ref)) {
9567
+ if (DOM_BACKED_KINDS.has(kind) && resolveEntityElement(ctx.ref)) {
9786
9568
  actions.push({ ...highlightElementAction(ctx.ref), group: "Inspect" });
9787
9569
  actions.push({ ...copyScreenshotAction(ctx.ref), group: "Inspect" });
9788
9570
  }
9789
- actions.push({
9790
- ...copyPathAction(ctx.ref, metaEntity.loc),
9791
- group: "Inspect"
9792
- });
9793
- actions.push({
9794
- ...copySnapshotAction(ctx.ref, metaEntity.loc),
9795
- group: "Inspect"
9796
- });
9571
+ if (metaEntity?.loc) {
9572
+ actions.push({
9573
+ ...copyPathAction(ctx.ref, metaEntity.loc),
9574
+ group: "Inspect"
9575
+ });
9576
+ actions.push({
9577
+ ...copySnapshotAction(ctx.ref, metaEntity.loc),
9578
+ group: "Inspect"
9579
+ });
9580
+ }
9797
9581
  const sections = [];
9798
9582
  if (meta?.description) {
9799
9583
  sections.push({ id: "description", text: meta.description });
@@ -9801,10 +9585,12 @@ function createEntityDetailView(config) {
9801
9585
  if (offerAcceptance && meta?.acceptance?.length) {
9802
9586
  sections.push({ id: "acceptance", items: meta.acceptance });
9803
9587
  }
9804
- for (const s of config.extraSections?.(ctx, entity) ?? []) {
9805
- sections.push(s);
9588
+ if (entity) {
9589
+ for (const s of config.extraSections?.(ctx, entity) ?? []) {
9590
+ sections.push(s);
9591
+ }
9806
9592
  }
9807
- if (!DOM_BACKED_KINDS2.has(kind)) {
9593
+ if (!DOM_BACKED_KINDS.has(kind)) {
9808
9594
  sections.push({
9809
9595
  id: "composes",
9810
9596
  label: SECTION_LABELS.contains,
@@ -9812,7 +9598,7 @@ function createEntityDetailView(config) {
9812
9598
  filterable: true
9813
9599
  });
9814
9600
  }
9815
- if (DOM_BACKED_KINDS2.has(kind) && meta?.features?.length) {
9601
+ if (DOM_BACKED_KINDS.has(kind) && meta?.features?.length) {
9816
9602
  const featureEntities = meta.features.map((fId) => ctx.registry.get("feature", fId)).filter((e) => !!e);
9817
9603
  if (featureEntities.length > 0) {
9818
9604
  sections.push({
@@ -9831,8 +9617,9 @@ function createEntityDetailView(config) {
9831
9617
  return {
9832
9618
  kind: "detail",
9833
9619
  entityKind: kind,
9834
- title: displayName(metaEntity),
9835
- subtitle: config.subtitle?.(ctx, entity),
9620
+ title: patternEntity ? ctx.ref.id : metaEntity ? displayName(metaEntity) : ctx.ref.id,
9621
+ subtitle: exactEntity ? config.subtitle?.(ctx, exactEntity) : void 0,
9622
+ unregistered: !entity,
9836
9623
  actions,
9837
9624
  sections
9838
9625
  };
@@ -9841,35 +9628,14 @@ function createEntityDetailView(config) {
9841
9628
  }
9842
9629
 
9843
9630
  // src/browser/views/built-in/entity-detail.ts
9844
- function collectDomParents(ctx, ref2) {
9845
- const el2 = resolveEntityElement(ref2);
9846
- if (!el2) return [];
9847
- const parents = [];
9848
- const seen = /* @__PURE__ */ new Set();
9849
- let node = el2.parentElement;
9850
- while (node) {
9851
- if (node instanceof HTMLElement) {
9852
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
9853
- const id = node.getAttribute(attr);
9854
- if (id) {
9855
- const key = `${kind}:${id}`;
9856
- if (!seen.has(key)) {
9857
- seen.add(key);
9858
- const entity = ctx.registry.get(kind, id);
9859
- if (entity) parents.push(entity);
9860
- }
9861
- }
9862
- }
9863
- }
9864
- node = node.parentElement;
9865
- }
9866
- return parents;
9631
+ function collectFeatureConsumers(ctx, featureId) {
9632
+ return ctx.registry.list("page").filter((page) => page.meta?.features?.includes(featureId));
9867
9633
  }
9868
- function usedBySection(ctx, ref2) {
9634
+ function usedBySection(ctx, featureId) {
9869
9635
  return {
9870
9636
  id: "used-by",
9871
9637
  label: SECTION_LABELS.usedBy,
9872
- entities: collectDomParents(ctx, ref2),
9638
+ entities: collectFeatureConsumers(ctx, featureId),
9873
9639
  filterable: true
9874
9640
  };
9875
9641
  }
@@ -9890,9 +9656,7 @@ var featureDetailView = createEntityDetailView({
9890
9656
  id: "feature-detail",
9891
9657
  kind: "feature",
9892
9658
  fallbackTitle: "Feature",
9893
- extraSections: (ctx, entity) => [
9894
- usedBySection(ctx, { kind: "feature", id: entity.id })
9895
- ]
9659
+ extraSections: (ctx, entity) => [usedBySection(ctx, entity.id)]
9896
9660
  });
9897
9661
  var regionDetailView = createEntityDetailView({
9898
9662
  id: "region-detail",
@@ -10020,6 +9784,15 @@ var reportDetailView = {
10020
9784
  }
10021
9785
  if (report.screenshot) {
10022
9786
  sections.push({ id: "screenshot", url: report.screenshot });
9787
+ } else {
9788
+ const pins = ctx.cloud?.pins;
9789
+ const fetchScreenshot = pins?.screenshot;
9790
+ if (pins && fetchScreenshot) {
9791
+ sections.push({
9792
+ id: "screenshot",
9793
+ load: () => fetchScreenshot.call(pins, report.id)
9794
+ });
9795
+ }
10023
9796
  }
10024
9797
  const metaEntries = [];
10025
9798
  if (report.url) metaEntries.push({ label: "URL", value: report.url });
@@ -10030,14 +9803,14 @@ var reportDetailView = {
10030
9803
  sections.push({ id: "metadata", entries: metaEntries });
10031
9804
  }
10032
9805
  const actions = [];
10033
- if (ctx.registry.archiveReport) {
9806
+ if (ctx.registry.closeReport) {
10034
9807
  actions.push({
10035
- id: "archive",
10036
- label: "Archive",
9808
+ id: "close",
9809
+ label: "Close",
10037
9810
  icon: "archive-x",
10038
9811
  run: () => {
10039
- setArchiveTarget(report.id, () => ctx.pop());
10040
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9812
+ setCloseTarget(report.id, () => ctx.pop());
9813
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10041
9814
  }
10042
9815
  });
10043
9816
  }
@@ -10075,13 +9848,13 @@ function reportToItem(report, ctx) {
10075
9848
  const label = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
10076
9849
  const kind = ctx.ref?.kind ?? "element";
10077
9850
  const actions = [];
10078
- if (ctx.registry.archiveReport) {
9851
+ if (ctx.registry.closeReport) {
10079
9852
  actions.push({
10080
- id: `archive-${report.id}`,
10081
- label: "Archive",
9853
+ id: `close-${report.id}`,
9854
+ label: "Close",
10082
9855
  perform: () => {
10083
- setArchiveTarget(report.id, () => ctx.pop());
10084
- ctx.push({ id: BUILT_IN_VIEW_IDS.archiveReason });
9856
+ setCloseTarget(report.id, () => ctx.pop());
9857
+ ctx.push({ id: BUILT_IN_VIEW_IDS.closeReason });
10085
9858
  }
10086
9859
  });
10087
9860
  }
@@ -10234,7 +10007,7 @@ function capitalize2(s) {
10234
10007
  return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
10235
10008
  }
10236
10009
  function renderPayloadMarkdown(payload) {
10237
- const heading = payload.title ?? `${capitalize2(payload.type)} Report`;
10010
+ const heading = payload.title ?? `${capitalize2(payload.type ?? "")} Report`;
10238
10011
  const ctx = payload.context;
10239
10012
  const lines = [
10240
10013
  `# ${heading}`,
@@ -10303,12 +10076,6 @@ var reportFields = [
10303
10076
  ];
10304
10077
 
10305
10078
  // src/browser/views/built-in/report/view-builder.ts
10306
- var DOM_BACKED_KINDS3 = /* @__PURE__ */ new Set([
10307
- "element",
10308
- "region",
10309
- "widget",
10310
- "primitive"
10311
- ]);
10312
10079
  var KIND_TO_ATTR = new Map(
10313
10080
  UIDEX_ATTR_TO_KIND.map(([attr, kind]) => [kind, attr])
10314
10081
  );
@@ -10379,7 +10146,7 @@ function createReportView(config) {
10379
10146
  const cloud = resolveCloud(ctx);
10380
10147
  const extra = extraFields ? extraFields(ctx) : [];
10381
10148
  const fields = [...extra, ...baseFields ?? reportFields];
10382
- const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS3.has(ctx.ref.kind);
10149
+ const isDomBacked = ctx.ref != null && DOM_BACKED_KINDS.has(ctx.ref.kind);
10383
10150
  const targetEl = ctx.ref && isDomBacked ? resolveElement(ctx.ref) : null;
10384
10151
  const screenshotPromise = isDomBacked ? captureScreenshot({
10385
10152
  target: targetEl ?? void 0,
@@ -10962,7 +10729,7 @@ var pinSettingsView = {
10962
10729
  // src/browser/views/built-in/index.ts
10963
10730
  function buildDefaultViews(shortcut) {
10964
10731
  return [
10965
- archiveReasonView,
10732
+ closeReasonView,
10966
10733
  createCommandPaletteView(shortcut),
10967
10734
  explorePageView,
10968
10735
  componentDetailView,
@@ -10990,6 +10757,27 @@ function buildDefaultViews(shortcut) {
10990
10757
  }
10991
10758
 
10992
10759
  // src/browser/create-uidex.ts
10760
+ function getVisibleEntities() {
10761
+ if (typeof document === "undefined") return [];
10762
+ const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
10763
+ const nodes = document.querySelectorAll(selector);
10764
+ const ids = [];
10765
+ const seen = /* @__PURE__ */ new Set();
10766
+ for (const node of nodes) {
10767
+ if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
10768
+ for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
10769
+ const id = node.getAttribute(attr);
10770
+ if (!id) continue;
10771
+ const key = `${kind}:${id}`;
10772
+ if (!seen.has(key)) {
10773
+ seen.add(key);
10774
+ ids.push(key);
10775
+ }
10776
+ break;
10777
+ }
10778
+ }
10779
+ return ids;
10780
+ }
10993
10781
  function createUidex(options = {}) {
10994
10782
  const registry = createRegistry();
10995
10783
  const inspectorRef = { current: null };
@@ -11024,7 +10812,7 @@ function createUidex(options = {}) {
11024
10812
  const views = createRouter({ session });
11025
10813
  const cloud = options.cloud ?? null;
11026
10814
  const ingestOpts = resolveIngestOptions(options.ingest, cloud !== null);
11027
- const ingest = ingestOpts ? createIngest({ session, ...ingestOpts }) : null;
10815
+ const ingest = ingestOpts ? createIngest(ingestOpts) : null;
11028
10816
  if (options.defaultViews !== false) {
11029
10817
  for (const view of buildDefaultViews(options.shortcut)) views.add(view);
11030
10818
  }
@@ -11034,6 +10822,94 @@ function createUidex(options = {}) {
11034
10822
  let shadowRoot = null;
11035
10823
  const mountCleanup = createCleanupStack();
11036
10824
  let mounted = false;
10825
+ function syncReportsToRegistry() {
10826
+ const layer = pinLayerRef.current;
10827
+ if (!layer) return;
10828
+ const byEntity = /* @__PURE__ */ new Map();
10829
+ for (const pin of layer.getAllPins()) {
10830
+ const cid = pin.entity ?? "";
10831
+ if (!cid) continue;
10832
+ const list = byEntity.get(cid);
10833
+ if (list) list.push(pin);
10834
+ else byEntity.set(cid, [pin]);
10835
+ }
10836
+ for (const [cid, pins] of byEntity) {
10837
+ const ref2 = parseComponentRef(cid);
10838
+ registry.setReports(ref2.kind, ref2.id, pins);
10839
+ }
10840
+ }
10841
+ function setupKeyBindings(root, viewStack) {
10842
+ const bindings = bindShadowKeys({
10843
+ shadowRoot: root,
10844
+ session,
10845
+ getActionsPopup: () => viewStack.getActionsPopup(),
10846
+ getPinLayer: () => pinLayerRef.current,
10847
+ shortcut: options.shortcut
10848
+ });
10849
+ mountCleanup.add(bindings);
10850
+ return bindings;
10851
+ }
10852
+ function setupPinLayer(root, shell, channel, getCurrentRoute, getMatchMode, getPathname) {
10853
+ if (cloud) {
10854
+ registry.closeReport = async (reportId, status) => {
10855
+ pinLayerRef.current?.removePin(reportId);
10856
+ await cloud.pins.close(
10857
+ reportId,
10858
+ status
10859
+ );
10860
+ syncReportsToRegistry();
10861
+ };
10862
+ }
10863
+ const pinLayer = createPinLayer({
10864
+ container: root,
10865
+ onOpenPinDetail: (componentId, reportId) => {
10866
+ const ref2 = parseComponentRef(componentId);
10867
+ setSelectedReportId(reportId);
10868
+ const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
10869
+ session.mode.transition.enterViewing(views.buildStack(entry));
10870
+ },
10871
+ onHoverPin: (anchor, componentId) => {
10872
+ const overlay = overlayRef.current;
10873
+ if (!overlay) return;
10874
+ if (!anchor || !componentId) {
10875
+ overlay.hide();
10876
+ return;
10877
+ }
10878
+ overlay.show(anchor, {
10879
+ color: isDarkMode() ? "#e4e4e7" : "#27272a"
10880
+ });
10881
+ },
10882
+ onPinsChanged: syncReportsToRegistry
10883
+ });
10884
+ shell.menuBar.setPinLayer(pinLayer);
10885
+ pinLayerRef.current = pinLayer;
10886
+ mountCleanup.add(() => {
10887
+ pinLayer.destroy();
10888
+ pinLayerRef.current = null;
10889
+ registry.closeReport = void 0;
10890
+ });
10891
+ if (cloud) {
10892
+ const detach = pinLayer.attachCloud({
10893
+ cloud,
10894
+ channel,
10895
+ getRoute: getCurrentRoute,
10896
+ getPathname,
10897
+ getVisibleEntities,
10898
+ getMatchMode,
10899
+ onError: (err) => console.warn("[uidex] pin fetch failed", err)
10900
+ });
10901
+ mountCleanup.add(detach);
10902
+ if (channel && typeof window !== "undefined") {
10903
+ const onRouteChange = () => {
10904
+ const route = getCurrentRoute();
10905
+ channel?.joinRoute(route);
10906
+ void pinLayer.refresh();
10907
+ };
10908
+ const detachRoute = bindRouteChange(onRouteChange);
10909
+ mountCleanup.add(detachRoute);
10910
+ }
10911
+ }
10912
+ }
11037
10913
  function mount(target) {
11038
10914
  if (mounted) return;
11039
10915
  const mountTarget = target ?? (typeof document !== "undefined" ? document.body : null);
@@ -11042,28 +10918,7 @@ function createUidex(options = {}) {
11042
10918
  }
11043
10919
  const getPathname = () => typeof location !== "undefined" ? location.pathname : "/";
11044
10920
  const getCurrentRoute = () => options.getRoute?.() ?? findCurrentRoutePath(registry) ?? getPathname();
11045
- const getVisibleEntities = () => {
11046
- if (typeof document === "undefined") return [];
11047
- const selector = UIDEX_ATTR_TO_KIND.map(([a]) => `[${a}]`).join(",");
11048
- const nodes = document.querySelectorAll(selector);
11049
- const ids = [];
11050
- const seen = /* @__PURE__ */ new Set();
11051
- for (const node of nodes) {
11052
- if (node.closest(SURFACE_IGNORE_SELECTOR)) continue;
11053
- for (const [attr, kind] of UIDEX_ATTR_TO_KIND) {
11054
- const id = node.getAttribute(attr);
11055
- if (!id) continue;
11056
- const key = `${kind}:${id}`;
11057
- if (!seen.has(key)) {
11058
- seen.add(key);
11059
- ids.push(key);
11060
- }
11061
- break;
11062
- }
11063
- }
11064
- return ids;
11065
- };
11066
- const getMatchMode = () => (typeof localStorage !== "undefined" ? localStorage.getItem("uidex:pin-match-mode") : null) ?? "route";
10921
+ const getMatchMode = () => readMode();
11067
10922
  let channel = null;
11068
10923
  if (cloud && options.user) {
11069
10924
  channel = cloud.realtime.connect({
@@ -11084,7 +10939,6 @@ function createUidex(options = {}) {
11084
10939
  onSelect: (match) => {
11085
10940
  const route = views.resolve(match.ref);
11086
10941
  if (!route) return;
11087
- session.select(match.ref);
11088
10942
  const entry = { id: route.view.id, ref: match.ref };
11089
10943
  session.mode.transition.enterViewing(views.buildStack(entry));
11090
10944
  }
@@ -11135,92 +10989,15 @@ function createUidex(options = {}) {
11135
10989
  });
11136
10990
  mountCleanup.add(viewStack);
11137
10991
  if (shadowRoot) {
11138
- keyBindings = bindShadowKeys({
10992
+ keyBindings = setupKeyBindings(shadowRoot, viewStack);
10993
+ setupPinLayer(
11139
10994
  shadowRoot,
11140
- session,
11141
- getActionsPopup: () => viewStack.getActionsPopup(),
11142
- getPinLayer: () => pinLayerRef.current,
11143
- shortcut: options.shortcut
11144
- });
11145
- mountCleanup.add(keyBindings);
11146
- }
11147
- if (shadowRoot) {
11148
- const syncReportsToRegistry = () => {
11149
- const layer = pinLayerRef.current;
11150
- if (!layer) return;
11151
- const byEntity = /* @__PURE__ */ new Map();
11152
- for (const pin of layer.getAllPins()) {
11153
- const cid = pin.entity ?? "";
11154
- if (!cid) continue;
11155
- const list = byEntity.get(cid);
11156
- if (list) list.push(pin);
11157
- else byEntity.set(cid, [pin]);
11158
- }
11159
- for (const [cid, pins] of byEntity) {
11160
- const ref2 = parseComponentRef(cid);
11161
- registry.setReports(ref2.kind, ref2.id, pins);
11162
- }
11163
- };
11164
- if (cloud) {
11165
- registry.archiveReport = async (reportId, reason) => {
11166
- pinLayerRef.current?.removePin(reportId);
11167
- await cloud.pins.archive(
11168
- reportId,
11169
- reason
11170
- );
11171
- syncReportsToRegistry();
11172
- };
11173
- }
11174
- const pinLayer = createPinLayer({
11175
- container: shadowRoot,
11176
- currentBranch: options.git?.branch ?? null,
11177
- onOpenPinDetail: (componentId, reportId) => {
11178
- const ref2 = parseComponentRef(componentId);
11179
- setSelectedReportId(reportId);
11180
- const entry = { id: BUILT_IN_VIEW_IDS.reportDetail, ref: ref2 };
11181
- session.mode.transition.enterViewing(views.buildStack(entry));
11182
- },
11183
- onHoverPin: (anchor, componentId) => {
11184
- const overlay = overlayRef.current;
11185
- if (!overlay) return;
11186
- if (!anchor || !componentId) {
11187
- overlay.hide();
11188
- return;
11189
- }
11190
- overlay.show(anchor, {
11191
- color: isDarkMode() ? "#e4e4e7" : "#27272a"
11192
- });
11193
- },
11194
- onPinsChanged: syncReportsToRegistry
11195
- });
11196
- shell.menuBar.setPinLayer(pinLayer);
11197
- pinLayerRef.current = pinLayer;
11198
- mountCleanup.add(() => {
11199
- pinLayer.destroy();
11200
- pinLayerRef.current = null;
11201
- registry.archiveReport = void 0;
11202
- });
11203
- if (cloud) {
11204
- const detach = pinLayer.attachCloud({
11205
- cloud,
11206
- channel,
11207
- getRoute: getCurrentRoute,
11208
- getPathname,
11209
- getVisibleEntities,
11210
- getMatchMode,
11211
- onError: (err) => console.warn("[uidex] pin fetch failed", err)
11212
- });
11213
- mountCleanup.add(detach);
11214
- if (channel && typeof window !== "undefined") {
11215
- const onRouteChange = () => {
11216
- const route = getCurrentRoute();
11217
- channel?.joinRoute(route);
11218
- void pinLayer.refresh();
11219
- };
11220
- const detachRoute = bindRouteChange(onRouteChange);
11221
- mountCleanup.add(detachRoute);
11222
- }
11223
- }
10995
+ shell,
10996
+ channel,
10997
+ getCurrentRoute,
10998
+ getMatchMode,
10999
+ getPathname
11000
+ );
11224
11001
  }
11225
11002
  if (ingest) {
11226
11003
  ingest.start();
@@ -11259,18 +11036,23 @@ function UidexProvider({
11259
11036
  config,
11260
11037
  children
11261
11038
  }) {
11262
- const [ownedInstance] = useState(() => {
11039
+ const [owned] = useState(() => {
11263
11040
  if (externalInstance) return null;
11264
- const resolvedCloud = cloud !== void 0 ? cloud : projectKey ? createCloud({ projectKey }) : null;
11265
- return createUidex({ ...config, cloud: resolvedCloud, user });
11041
+ const ownedCloud = cloud === void 0 && projectKey && typeof window !== "undefined" ? createCloud({ projectKey }) : null;
11042
+ const resolvedCloud = cloud !== void 0 ? cloud : ownedCloud;
11043
+ return {
11044
+ instance: createUidex({ ...config, cloud: resolvedCloud, user }),
11045
+ cloud: ownedCloud
11046
+ };
11266
11047
  });
11267
- const instance = externalInstance ?? ownedInstance;
11048
+ const instance = externalInstance ?? owned.instance;
11268
11049
  useEffect(() => {
11269
- if (!ownedInstance) return;
11050
+ if (!owned) return;
11270
11051
  return () => {
11271
- ownedInstance.unmount();
11052
+ owned.instance.unmount();
11053
+ owned.cloud?.dispose?.();
11272
11054
  };
11273
- }, [ownedInstance]);
11055
+ }, [owned]);
11274
11056
  return /* @__PURE__ */ jsx(UidexContext.Provider, { value: instance, children });
11275
11057
  }
11276
11058
 
@@ -11405,6 +11187,7 @@ function UidexDevtools({
11405
11187
  setInstance(inst);
11406
11188
  return () => {
11407
11189
  inst.unmount();
11190
+ cloud?.dispose?.();
11408
11191
  };
11409
11192
  }, [projectKey, user?.id]);
11410
11193
  if (!instance) return null;