@tutti-os/workspace-file-reference 0.0.15 → 0.0.16

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.
package/dist/ui/index.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  createReferenceSourcePickerController,
4
4
  normalizeDirectoryPath,
5
5
  useWorkspaceFileReferencePickerView
6
- } from "../chunk-KSK6PILG.js";
6
+ } from "../chunk-LJZYN4MH.js";
7
7
  import {
8
8
  REFERENCE_FILTER_CATEGORIES,
9
9
  WORKSPACE_ROOT_GROUP_NODE_ID,
10
10
  nodeRefKey,
11
11
  selectedReferenceToWorkspaceFileReference
12
- } from "../chunk-HRC2MZ5H.js";
12
+ } from "../chunk-JURSITRX.js";
13
13
 
14
14
  // src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
15
15
  import {
@@ -21,6 +21,7 @@ import {
21
21
  } from "@tutti-os/ui-system";
22
22
 
23
23
  // src/ui/internal/reference/WorkspaceFileReferencePickerSections.tsx
24
+ import { useId, useState as useState2 } from "react";
24
25
  import {
25
26
  Badge,
26
27
  Button as Button2,
@@ -44,6 +45,9 @@ import {
44
45
  ScrollArea,
45
46
  SearchIcon,
46
47
  Spinner,
48
+ Tooltip,
49
+ TooltipContent,
50
+ TooltipTrigger,
47
51
  cn
48
52
  } from "@tutti-os/ui-system";
49
53
  import { AddIcon as AddLinedIcon } from "@tutti-os/ui-system/icons";
@@ -324,7 +328,17 @@ function WorkspaceFileReferencePickerSearchEntry({
324
328
  /* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
325
329
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
326
330
  /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
327
- /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path })
331
+ /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 300, children: [
332
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path }) }),
333
+ /* @__PURE__ */ jsx(
334
+ TooltipContent,
335
+ {
336
+ className: "max-w-[min(520px,calc(100vw-32px))] whitespace-normal text-left [overflow-wrap:anywhere]",
337
+ side: "top",
338
+ children: entry.path
339
+ }
340
+ )
341
+ ] })
328
342
  ] })
329
343
  ] }),
330
344
  /* @__PURE__ */ jsx(
@@ -392,6 +406,8 @@ function WorkspaceFileReferencePreviewSurface({
392
406
  directoryMessage: copy.t("referencePicker.previewFolder"),
393
407
  emptyMessage: copy.t("referencePicker.previewUnavailable"),
394
408
  frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
409
+ htmlFrameClassName: "items-stretch justify-stretch bg-white",
410
+ htmlTitle: resolveWorkspaceFileReferenceLabel,
395
411
  imageAlt: resolveWorkspaceFileReferenceLabel,
396
412
  imageFrameClassName: "p-3",
397
413
  loadingIndicator: /* @__PURE__ */ jsx2("span", { className: "mx-auto grid size-11 place-items-center rounded-[6px] bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) }),
@@ -425,7 +441,9 @@ function resolveWorkspaceFileReferenceSurfaceState(copy, focusedEntry, previewSt
425
441
  switch (previewState.status) {
426
442
  case "loading":
427
443
  case "image":
444
+ case "video":
428
445
  case "text":
446
+ case "html":
429
447
  return {
430
448
  ...previewState,
431
449
  entry: focusedEntry
@@ -486,6 +504,9 @@ function WorkspaceFileReferencePickerFooter({
486
504
  onConfirm,
487
505
  selectedRefs
488
506
  }) {
507
+ const selectedRefsTooltipId = useId();
508
+ const [selectedRefsTooltipOpen, setSelectedRefsTooltipOpen] = useState2(false);
509
+ const selectedRefsLabel = selectedRefs.map((ref) => resolveWorkspaceFileReferenceLabel(ref)).join("\n");
489
510
  return /* @__PURE__ */ jsxs2("div", { className: "nodrag flex flex-col gap-3 border-t border-[var(--line-1)] px-4 py-4 [-webkit-app-region:no-drag] sm:px-6 lg:flex-row lg:items-center lg:justify-between", children: [
490
511
  /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-wrap items-center gap-2 lg:flex-1", children: [
491
512
  /* @__PURE__ */ jsx2("span", { className: "text-[13px] text-[var(--text-secondary)]", children: copy.t("referencePicker.selectedCount", {
@@ -501,13 +522,48 @@ function WorkspaceFileReferencePickerFooter({
501
522
  ref.path
502
523
  )),
503
524
  selectedRefs.length > 2 ? /* @__PURE__ */ jsxs2(
504
- Badge,
525
+ "span",
505
526
  {
506
- className: workspaceFileReferencePickerSelectedBadgeClassName,
507
- variant: "secondary",
527
+ className: "relative inline-flex",
528
+ onBlur: () => setSelectedRefsTooltipOpen(false),
529
+ onFocus: () => setSelectedRefsTooltipOpen(true),
530
+ onMouseEnter: () => setSelectedRefsTooltipOpen(true),
531
+ onMouseLeave: () => setSelectedRefsTooltipOpen(false),
508
532
  children: [
509
- "+",
510
- selectedRefs.length - 2
533
+ /* @__PURE__ */ jsx2(
534
+ Badge,
535
+ {
536
+ asChild: true,
537
+ className: `${workspaceFileReferencePickerSelectedBadgeClassName} cursor-default`,
538
+ variant: "secondary",
539
+ children: /* @__PURE__ */ jsxs2(
540
+ "button",
541
+ {
542
+ "aria-describedby": selectedRefsTooltipId,
543
+ "aria-label": selectedRefsLabel,
544
+ type: "button",
545
+ children: [
546
+ "+",
547
+ selectedRefs.length - 2
548
+ ]
549
+ }
550
+ )
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx2(
554
+ "span",
555
+ {
556
+ "aria-hidden": !selectedRefsTooltipOpen,
557
+ className: "pointer-events-none absolute bottom-[calc(100%+8px)] left-0 z-[var(--z-tooltip,100700)] max-h-[min(20rem,calc(100vh-96px))] w-max max-w-[min(28rem,calc(100vw-32px))] overflow-auto whitespace-pre-line rounded-md border border-[var(--border-1)] bg-[var(--background-fronted)] px-2 py-1 text-left text-[13px] leading-[1.3] text-[var(--text-primary)] shadow-soft transition-opacity duration-100 [overflow-wrap:anywhere]",
558
+ id: selectedRefsTooltipId,
559
+ role: "tooltip",
560
+ style: {
561
+ opacity: selectedRefsTooltipOpen ? 1 : 0,
562
+ visibility: selectedRefsTooltipOpen ? "visible" : "hidden"
563
+ },
564
+ children: selectedRefsLabel
565
+ }
566
+ )
511
567
  ]
512
568
  }
513
569
  ) : null
@@ -647,9 +703,9 @@ function WorkspaceFileReferencePicker({
647
703
  // src/ui/internal/reference/ReferenceSourcePicker.tsx
648
704
  import {
649
705
  useEffect as useEffect3,
650
- useId,
706
+ useId as useId2,
651
707
  useRef as useRef2,
652
- useState as useState3
708
+ useState as useState4
653
709
  } from "react";
654
710
  import { createPortal } from "react-dom";
655
711
  import {
@@ -673,15 +729,18 @@ import {
673
729
  ScrollArea as ScrollArea2,
674
730
  SearchIcon as SearchIcon2,
675
731
  Spinner as Spinner2,
732
+ Tooltip as Tooltip2,
733
+ TooltipContent as TooltipContent2,
734
+ TooltipTrigger as TooltipTrigger2,
676
735
  cn as cn3
677
736
  } from "@tutti-os/ui-system";
678
- import { AddIcon } from "@tutti-os/ui-system/icons";
737
+ import { AddLinedIcon as AddLinedIcon2 } from "@tutti-os/ui-system/icons";
679
738
  import {
680
739
  WorkspaceFilePreviewSurface
681
740
  } from "@tutti-os/workspace-file-preview/react";
682
741
 
683
742
  // src/react/internal/reference/useReferenceSourcePickerView.ts
684
- import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
743
+ import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState3 } from "react";
685
744
  import { useSnapshot } from "valtio";
686
745
  import {
687
746
  sortWorkspaceFileEntriesForArrangeMode
@@ -706,9 +765,9 @@ function useReferenceSourcePickerView({
706
765
  [aggregator, scope]
707
766
  );
708
767
  const snapshot = readSnapshot(controller.store);
709
- const [breadcrumbBySource, setBreadcrumbBySource] = useState2({});
710
- const [focusedNode, setFocusedNode] = useState2(null);
711
- const [arrangeMode, setArrangeMode] = useState2("none");
768
+ const [breadcrumbBySource, setBreadcrumbBySource] = useState3({});
769
+ const [focusedNode, setFocusedNode] = useState3(null);
770
+ const [arrangeMode, setArrangeMode] = useState3("none");
712
771
  const sortNodes = useCallback(
713
772
  (nodes) => {
714
773
  if (arrangeMode === "none") {
@@ -770,6 +829,9 @@ function useReferenceSourcePickerView({
770
829
  [group.ref.sourceId]: [group]
771
830
  }));
772
831
  controller.ensureChildren(group);
832
+ for (const node of path.slice(1)) {
833
+ controller.expandNode(node);
834
+ }
773
835
  const deepest = path[path.length - 1];
774
836
  setFocusedNode(path.length > 1 && deepest ? deepest : null);
775
837
  }).catch(() => {
@@ -969,7 +1031,7 @@ function useReferenceSourcePickerView({
969
1031
  ),
970
1032
  [snapshot.selection]
971
1033
  );
972
- const [isConfirming, setIsConfirming] = useState2(false);
1034
+ const [isConfirming, setIsConfirming] = useState3(false);
973
1035
  const confirm = useCallback(async () => {
974
1036
  if (isConfirming) {
975
1037
  return;
@@ -985,7 +1047,9 @@ function useReferenceSourcePickerView({
985
1047
  nodeId: bundle.root.ref.nodeId,
986
1048
  displayName: bundle.root.displayName,
987
1049
  iconUrl: bundle.root.iconUrl ?? null,
988
- files: bundle.files.map(selectedReferenceToWorkspaceFileReference)
1050
+ handle: bundle.handle,
1051
+ // 展示用文件数:取节点 childCount(不再展开文件);缺省回退 0。
1052
+ fileCount: bundle.root.childCount ?? 0
989
1053
  }))
990
1054
  });
991
1055
  } else {
@@ -997,7 +1061,7 @@ function useReferenceSourcePickerView({
997
1061
  setIsConfirming(false);
998
1062
  }
999
1063
  }, [controller, isConfirming, onClose, onConfirm, onConfirmBundles]);
1000
- const [previewState, setPreviewState] = useState2({
1064
+ const [previewState, setPreviewState] = useState3({
1001
1065
  status: "empty"
1002
1066
  });
1003
1067
  const previewObjectUrlRef = useRef(null);
@@ -1048,6 +1112,7 @@ function useReferenceSourcePickerView({
1048
1112
  mtimeMs: node.mtimeMs ?? null,
1049
1113
  sizeBytes: node.sizeBytes ?? null
1050
1114
  },
1115
+ renderHtml: true,
1051
1116
  target: {
1052
1117
  fileKind: preview.kind,
1053
1118
  name: node.displayName,
@@ -1067,10 +1132,22 @@ function useReferenceSourcePickerView({
1067
1132
  setPreviewState({ node, objectUrl, status: "image" });
1068
1133
  return;
1069
1134
  }
1135
+ if (loaded.status === "video") {
1136
+ const objectUrl = URL.createObjectURL(
1137
+ new Blob([loaded.bytes], { type: loaded.contentType })
1138
+ );
1139
+ previewObjectUrlRef.current = objectUrl;
1140
+ setPreviewState({ node, objectUrl, status: "video" });
1141
+ return;
1142
+ }
1070
1143
  if (loaded.status === "text") {
1071
1144
  setPreviewState({ content: loaded.content, node, status: "text" });
1072
1145
  return;
1073
1146
  }
1147
+ if (loaded.status === "html") {
1148
+ setPreviewState({ content: loaded.content, node, status: "html" });
1149
+ return;
1150
+ }
1074
1151
  setPreviewState({
1075
1152
  node,
1076
1153
  reason: loaded.reason,
@@ -1147,7 +1224,7 @@ function useReferenceSourcePickerView({
1147
1224
 
1148
1225
  // src/ui/internal/reference/ReferenceSourcePicker.tsx
1149
1226
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1150
- var SIDEBAR_GROUP_PAGE_SIZE = 20;
1227
+ var SIDEBAR_GROUP_PAGE_SIZE = 5;
1151
1228
  function autoFitPanelWidth(groupEl, contentEl, panel, trailingPx) {
1152
1229
  if (!groupEl || !contentEl || !panel) {
1153
1230
  return;
@@ -1183,7 +1260,7 @@ function ReferenceSourcePicker({
1183
1260
  open,
1184
1261
  workspaceId
1185
1262
  }) {
1186
- const titleId = useId();
1263
+ const titleId = useId2();
1187
1264
  const view = useReferenceSourcePickerView({
1188
1265
  aggregator,
1189
1266
  workspaceId,
@@ -1419,8 +1496,7 @@ function ReferenceSourcePicker({
1419
1496
  copy,
1420
1497
  node: view.focusedNode,
1421
1498
  previewState: view.previewState,
1422
- sourceLabel: view.activeTabLabel,
1423
- hierarchy: resolveHierarchyPath(view)
1499
+ sourceLabel: view.activeTabLabel
1424
1500
  }
1425
1501
  )
1426
1502
  }
@@ -1471,105 +1547,113 @@ function SourceSidebar({
1471
1547
  useEffect3(() => {
1472
1548
  selectedGroupRef.current?.scrollIntoView({ block: "nearest" });
1473
1549
  }, [view.selectedGroupKey]);
1474
- const [shownBySource, setShownBySource] = useState3(
1550
+ const [shownBySource, setShownBySource] = useState4(
1475
1551
  {}
1476
1552
  );
1477
1553
  const loadMoreGroups = (sourceId) => {
1478
1554
  const groups = view.sidebarGroupsBySource[sourceId] ?? [];
1479
1555
  const limit = shownBySource[sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
1480
- const selectedIndex = groups.findIndex(
1481
- (group) => nodeRefKey(group.ref) === view.selectedGroupKey
1482
- );
1483
- const visibleCount = selectedIndex >= limit ? selectedIndex + 1 : limit;
1484
- const next = visibleCount + SIDEBAR_GROUP_PAGE_SIZE;
1485
- setShownBySource((prev) => ({ ...prev, [sourceId]: next }));
1486
- if (next > groups.length && (view.sidebarHasMoreBySource[sourceId] ?? false)) {
1556
+ const visibleCount = Math.max(groups.length, limit);
1557
+ if (groups.length > limit) {
1558
+ setShownBySource((prev) => ({ ...prev, [sourceId]: visibleCount }));
1559
+ return;
1560
+ }
1561
+ if (view.sidebarHasMoreBySource[sourceId] ?? false) {
1487
1562
  view.loadMoreSidebarGroups(sourceId);
1488
1563
  }
1489
1564
  };
1490
- return /* @__PURE__ */ jsx4(ScrollArea2, { className: "h-full min-h-0 w-full", children: /* @__PURE__ */ jsx4("div", { ref: contentRef, className: "flex flex-col gap-1 p-2", children: view.tabs.map((tab) => {
1491
- const groups = view.sidebarGroupsBySource[tab.sourceId] ?? [];
1492
- const limit = shownBySource[tab.sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
1493
- const selectedIndex = groups.findIndex(
1494
- (group) => nodeRefKey(group.ref) === view.selectedGroupKey
1495
- );
1496
- const effectiveLimit = selectedIndex >= limit ? selectedIndex + 1 : limit;
1497
- const visibleGroups = groups.slice(0, effectiveLimit);
1498
- const loadingMore = view.sidebarLoadingMoreBySource[tab.sourceId] ?? false;
1499
- const hasMore = groups.length > effectiveLimit || (view.sidebarHasMoreBySource[tab.sourceId] ?? false);
1500
- return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-0.5", children: [
1501
- /* @__PURE__ */ jsx4(
1502
- "p",
1503
- {
1504
- className: "px-2 pt-1.5 pb-0.5 text-[11px] font-semibold text-[var(--text-tertiary)]",
1505
- "data-autofit-label": true,
1506
- children: tab.label
1507
- }
1508
- ),
1509
- groups.length === 0 ? view.isLoadingTabs ? /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[12px] text-[var(--text-tertiary)]", children: "\u2026" }) : null : visibleGroups.map((group) => {
1510
- const key = nodeRefKey(group.ref);
1511
- const selected = key === view.selectedGroupKey;
1512
- return /* @__PURE__ */ jsxs4(
1565
+ return /* @__PURE__ */ jsx4(ScrollArea2, { className: "h-full min-h-0 w-full", children: /* @__PURE__ */ jsxs4("div", { ref: contentRef, className: "flex flex-col gap-0.5 p-2", children: [
1566
+ /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[11px] font-semibold text-[var(--text-tertiary)]", children: copy.t("referencePicker.sourceColumn") }),
1567
+ view.tabs.map((tab) => {
1568
+ const groups = view.sidebarGroupsBySource[tab.sourceId] ?? [];
1569
+ const limit = shownBySource[tab.sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
1570
+ const selectedIndex = groups.findIndex(
1571
+ (group) => nodeRefKey(group.ref) === view.selectedGroupKey
1572
+ );
1573
+ const effectiveLimit = selectedIndex >= limit ? selectedIndex + 1 : limit;
1574
+ const visibleGroups = groups.slice(0, effectiveLimit);
1575
+ const loadingMore = view.sidebarLoadingMoreBySource[tab.sourceId] ?? false;
1576
+ const hasMore = groups.length > effectiveLimit || (view.sidebarHasMoreBySource[tab.sourceId] ?? false);
1577
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-0.5", children: [
1578
+ /* @__PURE__ */ jsx4(
1579
+ "p",
1580
+ {
1581
+ className: "px-2 pt-1.5 pb-0.5 text-[11px] font-semibold text-[var(--text-tertiary)]",
1582
+ "data-autofit-label": true,
1583
+ children: tab.label
1584
+ }
1585
+ ),
1586
+ groups.length === 0 ? view.isLoadingTabs ? /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[12px] text-[var(--text-tertiary)]", children: "\u2026" }) : null : visibleGroups.map((group) => {
1587
+ const key = nodeRefKey(group.ref);
1588
+ const selected = key === view.selectedGroupKey;
1589
+ return /* @__PURE__ */ jsxs4(
1590
+ "button",
1591
+ {
1592
+ ref: selected ? selectedGroupRef : void 0,
1593
+ "aria-current": selected ? "true" : void 0,
1594
+ className: cn3(
1595
+ "flex items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] transition-colors",
1596
+ selected ? "bg-primary/10 font-medium text-[var(--text-primary)] hover:bg-primary/15" : "text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]"
1597
+ ),
1598
+ type: "button",
1599
+ onClick: () => view.selectGroup(group),
1600
+ children: [
1601
+ group.iconUrl ? /* @__PURE__ */ jsx4(
1602
+ "img",
1603
+ {
1604
+ alt: "",
1605
+ className: "size-4 shrink-0 rounded-[3px] object-cover",
1606
+ src: group.iconUrl
1607
+ }
1608
+ ) : /* @__PURE__ */ jsx4(
1609
+ GroupFallbackIcon,
1610
+ {
1611
+ className: "size-4 shrink-0 text-[var(--rich-text-folder)]",
1612
+ icon: tab.icon
1613
+ }
1614
+ ),
1615
+ /* @__PURE__ */ jsx4(FullTextTooltip, { content: group.displayName, children: /* @__PURE__ */ jsx4(
1616
+ "span",
1617
+ {
1618
+ className: "min-w-0 flex-1 truncate",
1619
+ "data-autofit-label": true,
1620
+ children: group.displayName
1621
+ }
1622
+ ) }),
1623
+ group.childCount != null ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-[var(--text-tertiary)]", children: group.childCount }) : null
1624
+ ]
1625
+ },
1626
+ key
1627
+ );
1628
+ }),
1629
+ hasMore ? /* @__PURE__ */ jsxs4(
1513
1630
  "button",
1514
1631
  {
1515
- ref: selected ? selectedGroupRef : void 0,
1516
- "aria-current": selected ? "true" : void 0,
1517
- className: cn3(
1518
- "flex items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] transition-colors",
1519
- selected ? "bg-primary/10 font-medium text-[var(--text-primary)] hover:bg-primary/15" : "text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]"
1520
- ),
1632
+ className: "flex items-center gap-1.5 rounded-[6px] px-2 py-1.5 text-left text-[12px] text-[var(--text-secondary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)] disabled:opacity-60",
1633
+ disabled: loadingMore,
1521
1634
  type: "button",
1522
- onClick: () => view.selectGroup(group),
1635
+ onClick: () => loadMoreGroups(tab.sourceId),
1523
1636
  children: [
1524
- group.iconUrl ? /* @__PURE__ */ jsx4(
1525
- "img",
1637
+ loadingMore ? /* @__PURE__ */ jsx4(
1638
+ Spinner2,
1526
1639
  {
1527
- alt: "",
1528
- className: "size-4 shrink-0 rounded-[3px] object-cover",
1529
- src: group.iconUrl
1640
+ className: "text-[var(--text-secondary)]",
1641
+ size: 12
1530
1642
  }
1531
1643
  ) : /* @__PURE__ */ jsx4(
1532
- GroupFallbackIcon,
1533
- {
1534
- className: "size-4 shrink-0 text-[var(--rich-text-folder)]",
1535
- icon: tab.icon
1536
- }
1537
- ),
1538
- /* @__PURE__ */ jsx4(
1539
- "span",
1644
+ ChevronDownIcon,
1540
1645
  {
1541
- className: "min-w-0 flex-1 truncate",
1542
- "data-autofit-label": true,
1543
- children: group.displayName
1646
+ className: "shrink-0 text-[var(--text-secondary)]",
1647
+ size: 12
1544
1648
  }
1545
1649
  ),
1546
- group.childCount != null ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-[var(--text-tertiary)]", children: group.childCount }) : null
1650
+ /* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loadMoreGroups") })
1547
1651
  ]
1548
- },
1549
- key
1550
- );
1551
- }),
1552
- hasMore ? /* @__PURE__ */ jsxs4(
1553
- "button",
1554
- {
1555
- className: "flex items-center gap-1.5 rounded-[6px] px-2 py-1.5 text-left text-[12px] text-[var(--text-secondary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)] disabled:opacity-60",
1556
- disabled: loadingMore,
1557
- type: "button",
1558
- onClick: () => loadMoreGroups(tab.sourceId),
1559
- children: [
1560
- loadingMore ? /* @__PURE__ */ jsx4(
1561
- Spinner2,
1562
- {
1563
- className: "text-[var(--text-secondary)]",
1564
- size: 12
1565
- }
1566
- ) : null,
1567
- /* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loadMoreGroups") })
1568
- ]
1569
- }
1570
- ) : null
1571
- ] }, tab.sourceId);
1572
- }) }) });
1652
+ }
1653
+ ) : null
1654
+ ] }, tab.sourceId);
1655
+ })
1656
+ ] }) });
1573
1657
  }
1574
1658
  function SearchResultRow({
1575
1659
  node,
@@ -1579,6 +1663,7 @@ function SearchResultRow({
1579
1663
  onToggle
1580
1664
  }) {
1581
1665
  const isFolder = node.kind === "folder";
1666
+ const contextLabel = node.contextLabel ?? node.ref.nodeId;
1582
1667
  return /* @__PURE__ */ jsxs4(
1583
1668
  "div",
1584
1669
  {
@@ -1591,8 +1676,8 @@ function SearchResultRow({
1591
1676
  /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3 text-left", children: [
1592
1677
  /* @__PURE__ */ jsx4("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 text-[var(--text-tertiary)]" }) }),
1593
1678
  /* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
1594
- /* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }),
1595
- /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: node.contextLabel ?? node.ref.nodeId })
1679
+ /* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }) }),
1680
+ /* @__PURE__ */ jsx4(FullTextTooltip, { content: contextLabel, children: /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: contextLabel }) })
1596
1681
  ] })
1597
1682
  ] }),
1598
1683
  /* @__PURE__ */ jsx4(
@@ -1608,7 +1693,7 @@ function SearchResultRow({
1608
1693
  onFocus(node);
1609
1694
  onToggle(node);
1610
1695
  },
1611
- children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
1696
+ children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddLinedIcon2, { className: "text-[var(--text-secondary)]", size: 16 })
1612
1697
  }
1613
1698
  )
1614
1699
  ]
@@ -1630,8 +1715,16 @@ function toPreviewSurfaceState(node, previewState, copy) {
1630
1715
  objectUrl: previewState.objectUrl,
1631
1716
  status: "image"
1632
1717
  };
1718
+ case "video":
1719
+ return {
1720
+ entry: node,
1721
+ objectUrl: previewState.objectUrl,
1722
+ status: "video"
1723
+ };
1633
1724
  case "text":
1634
1725
  return { content: previewState.content, entry: node, status: "text" };
1726
+ case "html":
1727
+ return { content: previewState.content, entry: node, status: "html" };
1635
1728
  case "readonly":
1636
1729
  return {
1637
1730
  entry: node,
@@ -1660,12 +1753,38 @@ var SOURCE_BADGE_CLASSES = {
1660
1753
  "app-artifact": "bg-[color-mix(in_srgb,var(--rich-text-mention-app)_12%,transparent)] text-[var(--rich-text-mention-app)]",
1661
1754
  "issue-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-issue)_12%,transparent)] text-[var(--rich-text-mention-issue)]"
1662
1755
  };
1756
+ function FullTextTooltip({
1757
+ children,
1758
+ content
1759
+ }) {
1760
+ return /* @__PURE__ */ jsxs4(Tooltip2, { delayDuration: 300, children: [
1761
+ /* @__PURE__ */ jsx4(TooltipTrigger2, { asChild: true, children }),
1762
+ /* @__PURE__ */ jsx4(
1763
+ TooltipContent2,
1764
+ {
1765
+ className: "max-w-[min(520px,calc(100vw-32px))] whitespace-normal text-left [overflow-wrap:anywhere]",
1766
+ side: "top",
1767
+ style: {
1768
+ maxWidth: "min(520px, calc(100vw - 32px))",
1769
+ overflowWrap: "anywhere",
1770
+ whiteSpace: "normal",
1771
+ backgroundColor: "var(--background-fronted)",
1772
+ border: "1px solid var(--border-1)",
1773
+ borderRadius: 6,
1774
+ boxShadow: "var(--shadow-soft)",
1775
+ color: "var(--text-primary)",
1776
+ padding: "4px 8px"
1777
+ },
1778
+ children: content
1779
+ }
1780
+ )
1781
+ ] });
1782
+ }
1663
1783
  function PreviewInfoPane({
1664
1784
  copy,
1665
1785
  node,
1666
1786
  previewState,
1667
- sourceLabel,
1668
- hierarchy
1787
+ sourceLabel
1669
1788
  }) {
1670
1789
  return /* @__PURE__ */ jsx4("aside", { className: "flex h-full min-h-0 w-full flex-col bg-[var(--background-fronted)]", children: node ? /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-1 flex-col gap-4 p-4", children: [
1671
1790
  /* @__PURE__ */ jsx4(
@@ -1675,6 +1794,8 @@ function PreviewInfoPane({
1675
1794
  emptyMessage: copy.t("referencePicker.emptyPreview"),
1676
1795
  frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
1677
1796
  imageAlt: (entry) => entry.displayName,
1797
+ htmlFrameClassName: "items-stretch justify-stretch bg-white",
1798
+ htmlTitle: (entry) => entry.displayName,
1678
1799
  imageFrameClassName: "p-3",
1679
1800
  loadingIndicator: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }),
1680
1801
  loadingMessage: copy.t("referencePicker.previewLoading"),
@@ -1685,7 +1806,10 @@ function PreviewInfoPane({
1685
1806
  textFrameClassName: "items-stretch justify-stretch"
1686
1807
  }
1687
1808
  ),
1688
- /* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
1809
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1810
+ /* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
1811
+ /* @__PURE__ */ jsx4(ReferencePathText, { node })
1812
+ ] }),
1689
1813
  /* @__PURE__ */ jsxs4("dl", { className: "space-y-2 text-[13px]", children: [
1690
1814
  /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSource"), children: /* @__PURE__ */ jsx4(
1691
1815
  Badge2,
@@ -1697,30 +1821,58 @@ function PreviewInfoPane({
1697
1821
  ) }),
1698
1822
  node.mtimeMs != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewModified"), children: formatDateTime(node.mtimeMs) }) : null,
1699
1823
  node.sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(node.sizeBytes) }) : null
1700
- ] }),
1701
- hierarchy.length > 0 ? /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
1702
- /* @__PURE__ */ jsx4("p", { className: "text-[11px] font-semibold text-[var(--text-tertiary)]", children: copy.t("referencePicker.previewHierarchy") }),
1703
- /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap items-center gap-y-1 text-[12px] leading-5", children: hierarchy.map((crumb, index) => /* @__PURE__ */ jsxs4(
1704
- "span",
1705
- {
1706
- className: "flex min-w-0 items-center",
1707
- children: [
1708
- index > 0 ? /* @__PURE__ */ jsx4("span", { className: "mx-1 shrink-0 text-[var(--text-tertiary)]", children: "/" }) : null,
1709
- /* @__PURE__ */ jsx4(
1710
- "span",
1711
- {
1712
- className: "max-w-[14ch] truncate text-[var(--text-secondary)]",
1713
- title: crumb.displayName,
1714
- children: crumb.displayName
1715
- }
1716
- )
1717
- ]
1718
- },
1719
- nodeRefKey(crumb.ref)
1720
- )) })
1721
- ] }) : null
1824
+ ] })
1722
1825
  ] }) : /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyPreview") }) });
1723
1826
  }
1827
+ function ReferencePathText({ node }) {
1828
+ const pathText = getReferenceNodePathText(node);
1829
+ const lastSlashIndex = pathText.lastIndexOf("/");
1830
+ if (lastSlashIndex <= 0 || lastSlashIndex === pathText.length - 1) {
1831
+ return /* @__PURE__ */ jsx4(
1832
+ "p",
1833
+ {
1834
+ className: "truncate text-[12px] leading-5 text-[var(--text-tertiary)]",
1835
+ title: pathText,
1836
+ children: pathText
1837
+ }
1838
+ );
1839
+ }
1840
+ return /* @__PURE__ */ jsxs4(
1841
+ "p",
1842
+ {
1843
+ className: "flex min-w-0 items-center text-[12px] leading-5 text-[var(--text-tertiary)]",
1844
+ title: pathText,
1845
+ children: [
1846
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: pathText.slice(0, lastSlashIndex + 1) }),
1847
+ /* @__PURE__ */ jsx4("span", { className: "max-w-[65%] shrink-0 truncate", children: pathText.slice(lastSlashIndex + 1) })
1848
+ ]
1849
+ }
1850
+ );
1851
+ }
1852
+ function getReferenceNodePathText(node) {
1853
+ const decodedPath = decodeReferenceListFileNodeId(node.ref.nodeId);
1854
+ if (decodedPath) {
1855
+ return decodedPath;
1856
+ }
1857
+ return node.contextLabel?.trim() || node.ref.nodeId;
1858
+ }
1859
+ function decodeReferenceListFileNodeId(nodeId) {
1860
+ if (!nodeId.startsWith("f:")) {
1861
+ return null;
1862
+ }
1863
+ try {
1864
+ const normalized = nodeId.slice(2).replace(/-/g, "+").replace(/_/g, "/");
1865
+ const padded = normalized.padEnd(
1866
+ normalized.length + (4 - normalized.length % 4) % 4,
1867
+ "="
1868
+ );
1869
+ const binary = atob(padded);
1870
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
1871
+ return new TextDecoder().decode(bytes);
1872
+ } catch {
1873
+ return null;
1874
+ }
1875
+ }
1724
1876
  function InfoRow({
1725
1877
  label,
1726
1878
  children
@@ -1740,6 +1892,9 @@ function Footer({
1740
1892
  onClose,
1741
1893
  onConfirm
1742
1894
  }) {
1895
+ const selectionTooltipId = useId2();
1896
+ const [selectionTooltipOpen, setSelectionTooltipOpen] = useState4(false);
1897
+ const selectionTooltipLabel = selection.map((node) => node.displayName).join("\n");
1743
1898
  return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--line-1)] px-4 py-3 sm:px-6", children: [
1744
1899
  /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-2", children: [
1745
1900
  /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[13px] text-[var(--text-secondary)]", children: countLabel }),
@@ -1748,14 +1903,56 @@ function Footer({
1748
1903
  {
1749
1904
  className: "min-w-0 max-w-[12rem]",
1750
1905
  variant: "secondary",
1751
- children: /* @__PURE__ */ jsx4("span", { className: "truncate", children: node.displayName })
1906
+ children: /* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4("span", { className: "truncate", children: node.displayName }) })
1752
1907
  },
1753
1908
  nodeRefKey(node.ref)
1754
1909
  )),
1755
- selection.length > 2 ? /* @__PURE__ */ jsxs4(Badge2, { className: "shrink-0", variant: "secondary", children: [
1756
- "+",
1757
- selection.length - 2
1758
- ] }) : null
1910
+ selection.length > 2 ? /* @__PURE__ */ jsxs4(
1911
+ "span",
1912
+ {
1913
+ className: "relative inline-flex shrink-0",
1914
+ onBlur: () => setSelectionTooltipOpen(false),
1915
+ onFocus: () => setSelectionTooltipOpen(true),
1916
+ onMouseEnter: () => setSelectionTooltipOpen(true),
1917
+ onMouseLeave: () => setSelectionTooltipOpen(false),
1918
+ children: [
1919
+ /* @__PURE__ */ jsx4(
1920
+ Badge2,
1921
+ {
1922
+ asChild: true,
1923
+ className: "shrink-0 cursor-default",
1924
+ variant: "secondary",
1925
+ children: /* @__PURE__ */ jsxs4(
1926
+ "button",
1927
+ {
1928
+ "aria-describedby": selectionTooltipId,
1929
+ "aria-label": selectionTooltipLabel,
1930
+ type: "button",
1931
+ children: [
1932
+ "+",
1933
+ selection.length - 2
1934
+ ]
1935
+ }
1936
+ )
1937
+ }
1938
+ ),
1939
+ /* @__PURE__ */ jsx4(
1940
+ "span",
1941
+ {
1942
+ "aria-hidden": !selectionTooltipOpen,
1943
+ className: "pointer-events-none absolute bottom-[calc(100%+8px)] left-0 z-[var(--z-tooltip,100700)] max-h-[min(20rem,calc(100vh-96px))] w-max max-w-[min(28rem,calc(100vw-32px))] overflow-auto whitespace-pre-line rounded-md border border-[var(--border-1)] bg-[var(--background-fronted)] px-2 py-1 text-left text-[13px] leading-[1.3] text-[var(--text-primary)] shadow-soft transition-opacity duration-100 [overflow-wrap:anywhere]",
1944
+ id: selectionTooltipId,
1945
+ role: "tooltip",
1946
+ style: {
1947
+ opacity: selectionTooltipOpen ? 1 : 0,
1948
+ visibility: selectionTooltipOpen ? "visible" : "hidden"
1949
+ },
1950
+ children: selectionTooltipLabel
1951
+ }
1952
+ )
1953
+ ]
1954
+ }
1955
+ ) : null
1759
1956
  ] }),
1760
1957
  /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1761
1958
  /* @__PURE__ */ jsx4(Button3, { type: "button", variant: "secondary", onClick: onClose, children: cancelLabel }),
@@ -1784,7 +1981,7 @@ function FilterCategoryFilter({
1784
1981
  onClear,
1785
1982
  onToggle
1786
1983
  }) {
1787
- const [open, setOpen] = useState3(false);
1984
+ const [open, setOpen] = useState4(false);
1788
1985
  const containerRef = useRef2(null);
1789
1986
  useEffect3(() => {
1790
1987
  if (!open) {
@@ -1888,39 +2085,6 @@ var TREE_COLLAPSE_DURATION_MS = 200;
1888
2085
  function isFocused(focused, node) {
1889
2086
  return focused ? nodeRefKey(focused.ref) === nodeRefKey(node.ref) : false;
1890
2087
  }
1891
- function resolveHierarchyPath(view) {
1892
- const focused = view.focusedNode;
1893
- if (!focused) {
1894
- return view.breadcrumb;
1895
- }
1896
- const trail = findFocusedTrail(
1897
- view.currentEntries,
1898
- view.childrenByKey,
1899
- nodeRefKey(focused.ref),
1900
- []
1901
- );
1902
- return trail ? [...view.breadcrumb, ...trail] : view.breadcrumb;
1903
- }
1904
- function findFocusedTrail(roots, childrenByKey, targetKey, trail) {
1905
- for (const node of roots) {
1906
- if (nodeRefKey(node.ref) === targetKey) {
1907
- return [...trail];
1908
- }
1909
- if (node.kind === "folder") {
1910
- const children = childrenByKey[nodeRefKey(node.ref)]?.entries ?? [];
1911
- if (children.length > 0) {
1912
- const found = findFocusedTrail(children, childrenByKey, targetKey, [
1913
- ...trail,
1914
- node
1915
- ]);
1916
- if (found) {
1917
- return found;
1918
- }
1919
- }
1920
- }
1921
- }
1922
- return null;
1923
- }
1924
2088
  function TreeNodeRow({
1925
2089
  node,
1926
2090
  depth,
@@ -1934,7 +2098,7 @@ function TreeNodeRow({
1934
2098
  const childEntries = view.sortNodes(childState?.entries ?? []);
1935
2099
  const selected = view.isSelected(node);
1936
2100
  const focused = isFocused(view.focusedNode, node);
1937
- const [shouldRenderChildContent, setShouldRenderChildContent] = useState3(expanded);
2101
+ const [shouldRenderChildContent, setShouldRenderChildContent] = useState4(expanded);
1938
2102
  useEffect3(() => {
1939
2103
  if (expanded) {
1940
2104
  setShouldRenderChildContent(true);
@@ -2012,14 +2176,14 @@ function TreeNodeRow({
2012
2176
  }
2013
2177
  ) : null,
2014
2178
  isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
2015
- /* @__PURE__ */ jsx4(
2179
+ /* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4(
2016
2180
  "span",
2017
2181
  {
2018
2182
  className: "min-w-0 flex-1 truncate text-[13px] text-[var(--text-primary)]",
2019
2183
  "data-autofit-label": true,
2020
2184
  children: node.displayName
2021
2185
  }
2022
- ),
2186
+ ) }),
2023
2187
  /* @__PURE__ */ jsx4(
2024
2188
  Button3,
2025
2189
  {
@@ -2034,7 +2198,7 @@ function TreeNodeRow({
2034
2198
  view.setFocusedNode(node);
2035
2199
  view.toggleSelection(node);
2036
2200
  },
2037
- children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
2201
+ children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddLinedIcon2, { className: "text-[var(--text-secondary)]", size: 16 })
2038
2202
  }
2039
2203
  )
2040
2204
  ]