@tutti-os/workspace-file-reference 0.0.32 → 0.0.34

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,7 +3,7 @@ import {
3
3
  createReferenceSourcePickerController,
4
4
  normalizeDirectoryPath,
5
5
  useWorkspaceFileReferencePickerView
6
- } from "../chunk-BEBHG64Q.js";
6
+ } from "../chunk-SXYVXNMS.js";
7
7
  import {
8
8
  REFERENCE_FILTER_CATEGORIES,
9
9
  WORKSPACE_ROOT_GROUP_NODE_ID,
@@ -935,13 +935,21 @@ function useReferenceSourcePickerView({
935
935
  },
936
936
  [controller]
937
937
  );
938
+ const shouldRefreshChildrenOnEnter = useCallback(
939
+ (sourceId) => snapshot.tabs.find((tab) => tab.sourceId === sourceId)?.capabilities.navigable ?? false,
940
+ [snapshot.tabs]
941
+ );
938
942
  const enterFolder = useCallback(
939
943
  (node) => {
940
944
  const sourceId = node.ref.sourceId;
941
945
  if (node.kind !== "folder" || !sourceId || node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
942
946
  return;
943
947
  }
944
- controller.ensureChildren(node);
948
+ if (shouldRefreshChildrenOnEnter(sourceId)) {
949
+ controller.refreshChildren(node);
950
+ } else {
951
+ controller.ensureChildren(node);
952
+ }
945
953
  setBreadcrumbBySource((current) => {
946
954
  const stack = current[sourceId] ?? [];
947
955
  const index = stack.findIndex(
@@ -952,7 +960,7 @@ function useReferenceSourcePickerView({
952
960
  });
953
961
  setFocusedNode(null);
954
962
  },
955
- [controller]
963
+ [controller, shouldRefreshChildrenOnEnter]
956
964
  );
957
965
  useEffect2(() => {
958
966
  if (!open || !activeSourceId) {
@@ -991,10 +999,19 @@ function useReferenceSourcePickerView({
991
999
  return { ...current, [activeSourceId]: stack.slice(0, index + 1) };
992
1000
  });
993
1001
  const target = (breadcrumbBySource[activeSourceId] ?? [])[index] ?? null;
994
- controller.ensureChildren(target);
1002
+ if (target && shouldRefreshChildrenOnEnter(target.ref.sourceId)) {
1003
+ controller.refreshChildren(target);
1004
+ } else {
1005
+ controller.ensureChildren(target);
1006
+ }
995
1007
  setFocusedNode(null);
996
1008
  },
997
- [activeSourceId, breadcrumbBySource, controller]
1009
+ [
1010
+ activeSourceId,
1011
+ breadcrumbBySource,
1012
+ controller,
1013
+ shouldRefreshChildrenOnEnter
1014
+ ]
998
1015
  );
999
1016
  const navigateToRoot = useCallback(
1000
1017
  (sourceId) => {
@@ -1014,18 +1031,30 @@ function useReferenceSourcePickerView({
1014
1031
  if (!sourceId) {
1015
1032
  return;
1016
1033
  }
1034
+ const nextScopeNodeId = node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID ? null : node.ref.nodeId;
1017
1035
  if (sourceId !== snapshot.activeSourceId) {
1018
- controller.setActiveSource(sourceId);
1036
+ controller.setActiveSource(sourceId, nextScopeNodeId);
1019
1037
  }
1020
1038
  if (node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
1039
+ controller.setSearchScope(null);
1021
1040
  navigateToRoot(sourceId);
1022
1041
  return;
1023
1042
  }
1024
- controller.ensureChildren(node);
1043
+ if (shouldRefreshChildrenOnEnter(sourceId)) {
1044
+ controller.refreshChildren(node);
1045
+ } else {
1046
+ controller.ensureChildren(node);
1047
+ }
1048
+ controller.setSearchScope(nextScopeNodeId);
1025
1049
  setBreadcrumbBySource((current) => ({ ...current, [sourceId]: [node] }));
1026
1050
  setFocusedNode(null);
1027
1051
  },
1028
- [controller, snapshot.activeSourceId, navigateToRoot]
1052
+ [
1053
+ controller,
1054
+ snapshot.activeSourceId,
1055
+ navigateToRoot,
1056
+ shouldRefreshChildrenOnEnter
1057
+ ]
1029
1058
  );
1030
1059
  const isSelected = useCallback(
1031
1060
  (node) => (isNodeSelectable?.(node) ?? true) && snapshot.selection.some(
@@ -1123,6 +1152,8 @@ function useReferenceSourcePickerView({
1123
1152
  setPreviewState({ node, status: "unsupported" });
1124
1153
  return;
1125
1154
  }
1155
+ const previewSizeBytes = preview.bytes.byteLength;
1156
+ const loadedSizeBytes = node.sizeBytes != null && node.sizeBytes > 0 ? node.sizeBytes : previewSizeBytes;
1126
1157
  const loaded = createWorkspaceFilePreviewLoadedState({
1127
1158
  bytes: preview.bytes,
1128
1159
  contentType: preview.contentType,
@@ -1131,7 +1162,7 @@ function useReferenceSourcePickerView({
1131
1162
  name: node.displayName,
1132
1163
  path: node.ref.nodeId,
1133
1164
  mtimeMs: node.mtimeMs ?? null,
1134
- sizeBytes: node.sizeBytes ?? null
1165
+ sizeBytes: loadedSizeBytes
1135
1166
  },
1136
1167
  renderHtml: true,
1137
1168
  target: {
@@ -1139,7 +1170,7 @@ function useReferenceSourcePickerView({
1139
1170
  name: node.displayName,
1140
1171
  path: node.ref.nodeId,
1141
1172
  mtimeMs: node.mtimeMs ?? null,
1142
- sizeBytes: node.sizeBytes ?? null
1173
+ sizeBytes: loadedSizeBytes
1143
1174
  }
1144
1175
  });
1145
1176
  if (cancelled) {
@@ -1150,7 +1181,12 @@ function useReferenceSourcePickerView({
1150
1181
  new Blob([loaded.bytes], { type: loaded.contentType })
1151
1182
  );
1152
1183
  previewObjectUrlRef.current = objectUrl;
1153
- setPreviewState({ node, objectUrl, status: "image" });
1184
+ setPreviewState({
1185
+ node,
1186
+ objectUrl,
1187
+ previewSizeBytes,
1188
+ status: "image"
1189
+ });
1154
1190
  return;
1155
1191
  }
1156
1192
  if (loaded.status === "video") {
@@ -1158,19 +1194,35 @@ function useReferenceSourcePickerView({
1158
1194
  new Blob([loaded.bytes], { type: loaded.contentType })
1159
1195
  );
1160
1196
  previewObjectUrlRef.current = objectUrl;
1161
- setPreviewState({ node, objectUrl, status: "video" });
1197
+ setPreviewState({
1198
+ node,
1199
+ objectUrl,
1200
+ previewSizeBytes,
1201
+ status: "video"
1202
+ });
1162
1203
  return;
1163
1204
  }
1164
1205
  if (loaded.status === "text") {
1165
- setPreviewState({ content: loaded.content, node, status: "text" });
1206
+ setPreviewState({
1207
+ content: loaded.content,
1208
+ node,
1209
+ previewSizeBytes,
1210
+ status: "text"
1211
+ });
1166
1212
  return;
1167
1213
  }
1168
1214
  if (loaded.status === "html") {
1169
- setPreviewState({ content: loaded.content, node, status: "html" });
1215
+ setPreviewState({
1216
+ content: loaded.content,
1217
+ node,
1218
+ previewSizeBytes,
1219
+ status: "html"
1220
+ });
1170
1221
  return;
1171
1222
  }
1172
1223
  setPreviewState({
1173
1224
  node,
1225
+ previewSizeBytes,
1174
1226
  reason: loaded.reason,
1175
1227
  ...loaded.maxSizeBytes == null ? {} : { maxSizeBytes: loaded.maxSizeBytes },
1176
1228
  status: "readonly"
@@ -1259,6 +1311,22 @@ function useReferenceSourcePickerView({
1259
1311
  }
1260
1312
 
1261
1313
  // src/ui/internal/reference/referenceSourcePickerPresentation.ts
1314
+ function formatHierarchyTitle(hierarchy) {
1315
+ if (hierarchy.length === 0) {
1316
+ return null;
1317
+ }
1318
+ return hierarchy.map((crumb) => crumb.displayName).join(" / ");
1319
+ }
1320
+ function formatReferenceNodePathText(node, hierarchy = []) {
1321
+ const decodedPath = decodeReferenceListFileNodeId(node.ref.nodeId);
1322
+ if (decodedPath) {
1323
+ return decodedPath;
1324
+ }
1325
+ if (node.kind === "folder" && node.ref.nodeId.startsWith("g:")) {
1326
+ return formatHierarchyTitle(completeHierarchy(hierarchy, node)) || node.displayName;
1327
+ }
1328
+ return node.contextLabel?.trim() || node.ref.nodeId;
1329
+ }
1262
1330
  function formatReferencePreviewDateTime(ms, options = {}) {
1263
1331
  const timeZone = options.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
1264
1332
  const formatter = new Intl.DateTimeFormat(options.locale, {
@@ -1275,6 +1343,46 @@ function formatReferencePreviewDateTime(ms, options = {}) {
1275
1343
  );
1276
1344
  return `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}`;
1277
1345
  }
1346
+ function resolveReferencePreviewSizeBytes(node, previewState) {
1347
+ const metadataSize = normalizedSizeBytes(node.sizeBytes);
1348
+ if (metadataSize != null && metadataSize > 0) {
1349
+ return metadataSize;
1350
+ }
1351
+ if (previewState.node && previewState.node.ref.sourceId === node.ref.sourceId && previewState.node.ref.nodeId === node.ref.nodeId) {
1352
+ const previewSize = normalizedSizeBytes(previewState.previewSizeBytes);
1353
+ if (previewSize != null && previewSize > 0) {
1354
+ return previewSize;
1355
+ }
1356
+ }
1357
+ return metadataSize;
1358
+ }
1359
+ function completeHierarchy(hierarchy, node) {
1360
+ const last = hierarchy.at(-1);
1361
+ if (last && last.ref.sourceId === node.ref.sourceId && last.ref.nodeId === node.ref.nodeId) {
1362
+ return [...hierarchy];
1363
+ }
1364
+ return [...hierarchy, node];
1365
+ }
1366
+ function decodeReferenceListFileNodeId(nodeId) {
1367
+ if (!nodeId.startsWith("f:")) {
1368
+ return null;
1369
+ }
1370
+ try {
1371
+ const normalized = nodeId.slice(2).replace(/-/g, "+").replace(/_/g, "/");
1372
+ const padded = normalized.padEnd(
1373
+ normalized.length + (4 - normalized.length % 4) % 4,
1374
+ "="
1375
+ );
1376
+ const binary = atob(padded);
1377
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
1378
+ return new TextDecoder().decode(bytes);
1379
+ } catch {
1380
+ return null;
1381
+ }
1382
+ }
1383
+ function normalizedSizeBytes(sizeBytes) {
1384
+ return typeof sizeBytes === "number" && Number.isFinite(sizeBytes) && sizeBytes >= 0 ? sizeBytes : null;
1385
+ }
1278
1386
 
1279
1387
  // src/ui/internal/reference/ReferenceSourcePicker.tsx
1280
1388
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -1503,7 +1611,9 @@ function ReferenceSourcePicker({
1503
1611
  },
1504
1612
  nodeRefKey(node.ref)
1505
1613
  ))
1506
- ) : !hasSelectedGroup ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.selectGroupHint") }) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyDirectory") }) : (
1614
+ ) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t(
1615
+ hasSelectedGroup ? "referencePicker.emptyDirectory" : "referencePicker.selectGroupHint"
1616
+ ) }) : (
1507
1617
  // 浏览:就地递归展开树(复刻 agent 引用面板文件树交互)
1508
1618
  view.currentEntries.map((node) => /* @__PURE__ */ jsx4(
1509
1619
  TreeNodeRow,
@@ -1559,6 +1669,7 @@ function ReferenceSourcePicker({
1559
1669
  PreviewInfoPane,
1560
1670
  {
1561
1671
  copy,
1672
+ hierarchy: view.breadcrumb,
1562
1673
  node: view.focusedNode,
1563
1674
  previewState: view.previewState,
1564
1675
  sourceLabel: view.activeTabLabel
@@ -1853,10 +1964,12 @@ function FullTextTooltip({
1853
1964
  }
1854
1965
  function PreviewInfoPane({
1855
1966
  copy,
1967
+ hierarchy,
1856
1968
  node,
1857
1969
  previewState,
1858
1970
  sourceLabel
1859
1971
  }) {
1972
+ const sizeBytes = node ? resolveReferencePreviewSizeBytes(node, previewState) : null;
1860
1973
  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: [
1861
1974
  /* @__PURE__ */ jsx4(
1862
1975
  WorkspaceFilePreviewSurface,
@@ -1879,7 +1992,7 @@ function PreviewInfoPane({
1879
1992
  ),
1880
1993
  /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1881
1994
  /* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
1882
- /* @__PURE__ */ jsx4(ReferencePathText, { node })
1995
+ /* @__PURE__ */ jsx4(ReferencePathText, { hierarchy, node })
1883
1996
  ] }),
1884
1997
  /* @__PURE__ */ jsxs4("dl", { className: "space-y-2 text-[13px]", children: [
1885
1998
  /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSource"), children: /* @__PURE__ */ jsx4(
@@ -1891,12 +2004,15 @@ function PreviewInfoPane({
1891
2004
  }
1892
2005
  ) }),
1893
2006
  node.mtimeMs != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewModified"), children: formatReferencePreviewDateTime(node.mtimeMs) }) : null,
1894
- node.sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(node.sizeBytes) }) : null
2007
+ sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(sizeBytes) }) : null
1895
2008
  ] })
1896
2009
  ] }) : /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyPreview") }) });
1897
2010
  }
1898
- function ReferencePathText({ node }) {
1899
- const pathText = getReferenceNodePathText(node);
2011
+ function ReferencePathText({
2012
+ hierarchy,
2013
+ node
2014
+ }) {
2015
+ const pathText = getReferenceNodePathText(node, hierarchy);
1900
2016
  const lastSlashIndex = pathText.lastIndexOf("/");
1901
2017
  if (lastSlashIndex <= 0 || lastSlashIndex === pathText.length - 1) {
1902
2018
  return /* @__PURE__ */ jsx4(
@@ -1920,29 +2036,8 @@ function ReferencePathText({ node }) {
1920
2036
  }
1921
2037
  );
1922
2038
  }
1923
- function getReferenceNodePathText(node) {
1924
- const decodedPath = decodeReferenceListFileNodeId(node.ref.nodeId);
1925
- if (decodedPath) {
1926
- return decodedPath;
1927
- }
1928
- return node.contextLabel?.trim() || node.ref.nodeId;
1929
- }
1930
- function decodeReferenceListFileNodeId(nodeId) {
1931
- if (!nodeId.startsWith("f:")) {
1932
- return null;
1933
- }
1934
- try {
1935
- const normalized = nodeId.slice(2).replace(/-/g, "+").replace(/_/g, "/");
1936
- const padded = normalized.padEnd(
1937
- normalized.length + (4 - normalized.length % 4) % 4,
1938
- "="
1939
- );
1940
- const binary = atob(padded);
1941
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
1942
- return new TextDecoder().decode(bytes);
1943
- } catch {
1944
- return null;
1945
- }
2039
+ function getReferenceNodePathText(node, hierarchy) {
2040
+ return formatReferenceNodePathText(node, hierarchy);
1946
2041
  }
1947
2042
  function InfoRow({
1948
2043
  label,
@@ -2171,7 +2266,13 @@ function TreeNodeRow({
2171
2266
  const selectable = view.isSelectable(node);
2172
2267
  const focused = isFocused(view.focusedNode, node);
2173
2268
  const active = selected || focused && selectable;
2269
+ const focusedRowRef = useRef2(null);
2174
2270
  const [shouldRenderChildContent, setShouldRenderChildContent] = useState4(expanded);
2271
+ useEffect3(() => {
2272
+ if (focused) {
2273
+ focusedRowRef.current?.scrollIntoView({ block: "nearest" });
2274
+ }
2275
+ }, [focused]);
2175
2276
  useEffect3(() => {
2176
2277
  if (expanded) {
2177
2278
  setShouldRenderChildContent(true);
@@ -2214,6 +2315,7 @@ function TreeNodeRow({
2214
2315
  /* @__PURE__ */ jsxs4(
2215
2316
  "div",
2216
2317
  {
2318
+ ref: focused ? focusedRowRef : void 0,
2217
2319
  className: cn3(
2218
2320
  "flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
2219
2321
  active ? "bg-transparency-block" : "hover:bg-transparency-block"