@mcp-b/react-components 0.17.0 → 0.18.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 (292) hide show
  1. package/dist/{AgentActivity-BNUNrP9O.d.ts → AgentActivity-D2SHSGFX.d.ts} +1 -1
  2. package/dist/{AgentChat-CRuMi1Pb.d.ts → AgentChat-ClxViuUM.d.ts} +1 -20
  3. package/dist/{Button-FX_hpqxm.d.ts → Button-DLGzHsjc.d.ts} +13 -7
  4. package/dist/{CodeBlock-Co1noLbC.js → CodeBlock-Bfp5PAg3.js} +9 -6
  5. package/dist/{CodeBlock-Dnpr88ZQ.d.ts → CodeBlock-gV4kZeCI.d.ts} +2 -9
  6. package/dist/{FileTree-B7bBsUyU.js → FileTree-3zYtrddG.js} +42 -16
  7. package/dist/{Message-C4eYRogZ.js → Message-BXtmfc9Q.js} +13 -20
  8. package/dist/{ModelSelector-BNrZlY_Y.js → ModelSelector-Cqw_qrUG.js} +15 -22
  9. package/dist/{PromptInput-OLfXPuHM.d.ts → PromptInput-Bdh9pqlo.d.ts} +4 -6
  10. package/dist/{Select-CwSqP07Y.d.ts → Select-B_13gg_J.d.ts} +2 -2
  11. package/dist/{Tabs-9zddXDQo.d.ts → Tabs-ColWK-XA.d.ts} +10 -2
  12. package/dist/{ThinkChat-DInElus-.js → ThinkChat-15EhfvYJ.js} +84 -61
  13. package/dist/{ThinkChatActivity-BgesTfZ4.d.ts → ThinkChatActivity-Bzot45kX.d.ts} +4 -7
  14. package/dist/{ThinkChatActivitySummary-BPmu9a-c.d.ts → ThinkChatActivitySummary-D1xOrNrq.d.ts} +1 -1
  15. package/dist/{Toggle-DaXpe1Lc.d.ts → Toggle-JKZ43D5S.d.ts} +1 -1
  16. package/dist/{ToggleGroup-yseEWkOC.d.ts → ToggleGroup-DNd6cDJZ.d.ts} +2 -2
  17. package/dist/Workspace-Jez8ffUj.d.ts +274 -0
  18. package/dist/{WorkspaceExplorer-sZewzT2O.d.ts → WorkspaceExplorer-B9dQWNVK.d.ts} +2 -2
  19. package/dist/{WorkspaceFile-C8UzuSH4.d.ts → WorkspaceFile-CzyHnESE.d.ts} +1 -1
  20. package/dist/{WorkspaceFileView-DNdGAEiX.js → WorkspaceFileView-C68Ub8S8.js} +120 -127
  21. package/dist/{WorkspaceLink-B1ykV1dS.js → WorkspaceLink-BYYeTQLf.js} +6 -27
  22. package/dist/{WorkspaceLink-DK1DWMHe.d.ts → WorkspaceLink-O-EBKUJ8.d.ts} +6 -9
  23. package/dist/{class-names-Bk-QLWWI.js → class-names-BiMDVpUo.js} +4 -1
  24. package/dist/components/Accordion.js +7 -2
  25. package/dist/components/Activity.js +2 -2
  26. package/dist/components/AgentActivity.d.ts +1 -1
  27. package/dist/components/AgentActivity.js +4 -5
  28. package/dist/components/AgentActivityPreview.d.ts +9 -9
  29. package/dist/components/AgentActivityPreview.js +8 -12
  30. package/dist/components/AgentAttention.d.ts +1 -1
  31. package/dist/components/AgentAttention.js +1 -1
  32. package/dist/components/AgentChat.d.ts +1 -1
  33. package/dist/components/AgentChat.js +94 -62
  34. package/dist/components/AgentChatMessageEditor.js +2 -2
  35. package/dist/components/AgentChatTurnError.js +2 -2
  36. package/dist/components/AgentConnection.js +3 -3
  37. package/dist/components/AgentMessageParts.js +1 -1
  38. package/dist/components/AgentToolDetail.d.ts +6 -4
  39. package/dist/components/AgentToolDetail.js +12 -10
  40. package/dist/components/AlertDialog.js +1 -1
  41. package/dist/components/AppTopBar.d.ts +24 -7
  42. package/dist/components/AppTopBar.js +32 -8
  43. package/dist/components/Approval.js +4 -4
  44. package/dist/components/Artifact.d.ts +1 -1
  45. package/dist/components/Artifact.js +6 -4
  46. package/dist/components/Autocomplete.js +1 -1
  47. package/dist/components/Avatar.d.ts +1 -1
  48. package/dist/components/Avatar.js +2 -2
  49. package/dist/components/Badge.js +3 -0
  50. package/dist/components/BrowserLiveView.js +27 -12
  51. package/dist/components/BrowserReplay.js +4 -3
  52. package/dist/components/BrowserRunPanel.js +5 -5
  53. package/dist/components/Button.d.ts +1 -1
  54. package/dist/components/Button.js +14 -11
  55. package/dist/components/Card.d.ts +7 -8
  56. package/dist/components/Card.js +19 -10
  57. package/dist/components/ChainOfThought.d.ts +1 -1
  58. package/dist/components/ChainOfThought.js +2 -2
  59. package/dist/components/ChatGptSignInScreen.js +8 -8
  60. package/dist/components/Checkbox.js +2 -2
  61. package/dist/components/CheckboxGroup.js +1 -1
  62. package/dist/components/Checkpoint.js +2 -2
  63. package/dist/components/CodeBlock.d.ts +1 -1
  64. package/dist/components/CodeBlock.js +1 -1
  65. package/dist/components/CodexModelSelector.js +2 -2
  66. package/dist/components/Collapsible.js +2 -2
  67. package/dist/components/Combobox.js +1 -1
  68. package/dist/components/Commit.d.ts +1 -1
  69. package/dist/components/Commit.js +3 -3
  70. package/dist/components/Context.js +1 -1
  71. package/dist/components/ContextMenu.js +2 -2
  72. package/dist/components/Conversation.d.ts +1 -1
  73. package/dist/components/Conversation.js +5 -4
  74. package/dist/components/DescriptionListParts.js +1 -1
  75. package/dist/components/Dialog.js +1 -1
  76. package/dist/components/EnvironmentVariables.d.ts +1 -1
  77. package/dist/components/EnvironmentVariables.js +3 -3
  78. package/dist/components/Field.js +5 -2
  79. package/dist/components/Fieldset.js +1 -1
  80. package/dist/components/FileTree.d.ts +6 -0
  81. package/dist/components/FileTree.js +1 -1
  82. package/dist/components/Form.js +1 -1
  83. package/dist/components/GithubMotionMark.js +1 -1
  84. package/dist/components/IconButton.d.ts +2 -3
  85. package/dist/components/IconButton.js +2 -1
  86. package/dist/components/InlineCitation.js +1 -1
  87. package/dist/components/Input.js +1 -1
  88. package/dist/components/Marker.js +1 -1
  89. package/dist/components/McpConnectionRequest.js +2 -2
  90. package/dist/components/McpServerPicker.js +2 -2
  91. package/dist/components/MediaPreviewCard.d.ts +103 -0
  92. package/dist/components/MediaPreviewCard.js +193 -0
  93. package/dist/components/Menu.js +1 -1
  94. package/dist/components/Menubar.js +2 -2
  95. package/dist/components/Message.d.ts +4 -1
  96. package/dist/components/Message.js +1 -1
  97. package/dist/components/MessageScroller.d.ts +1 -1
  98. package/dist/components/MessageScroller.js +5 -4
  99. package/dist/components/Meter.js +1 -1
  100. package/dist/components/ModelSelector.d.ts +4 -3
  101. package/dist/components/ModelSelector.js +1 -1
  102. package/dist/components/NavigationMenu.js +1 -1
  103. package/dist/components/NumberField.js +1 -1
  104. package/dist/components/Plan.js +2 -2
  105. package/dist/components/Popover.js +1 -1
  106. package/dist/components/PreviewCard.js +1 -1
  107. package/dist/components/PricingBillingToggle.js +1 -1
  108. package/dist/components/PricingFAQ.js +1 -1
  109. package/dist/components/PricingPage.js +2 -1
  110. package/dist/components/PricingPlanCard.js +4 -3
  111. package/dist/components/PricingUsageCalculator.js +2 -2
  112. package/dist/components/Progress.js +1 -1
  113. package/dist/components/PromptInput.d.ts +1 -1
  114. package/dist/components/PromptInput.js +33 -20
  115. package/dist/components/Queue.d.ts +1 -1
  116. package/dist/components/Queue.js +2 -3
  117. package/dist/components/RadioGroup.js +1 -1
  118. package/dist/components/Reasoning.js +3 -6
  119. package/dist/components/ResizableSidePanel.js +1 -1
  120. package/dist/components/ResponsiveSidebarShell.js +16 -10
  121. package/dist/components/ScrollArea.js +2 -1
  122. package/dist/components/SearchField.d.ts +43 -0
  123. package/dist/components/SearchField.js +52 -0
  124. package/dist/components/Select.d.ts +1 -1
  125. package/dist/components/Select.js +5 -6
  126. package/dist/components/Separator.js +1 -1
  127. package/dist/components/Slider.js +1 -1
  128. package/dist/components/Sources.js +2 -2
  129. package/dist/components/Stepper.js +16 -9
  130. package/dist/components/SubagentRunDetails.js +1 -1
  131. package/dist/components/SubagentRunIndicator.js +2 -2
  132. package/dist/components/SubagentRunList.d.ts +3 -5
  133. package/dist/components/SubagentRunList.js +3 -4
  134. package/dist/components/SubagentRunSelector.d.ts +8 -12
  135. package/dist/components/SubagentRunSelector.js +17 -20
  136. package/dist/components/Switch.d.ts +2 -2
  137. package/dist/components/Switch.js +4 -5
  138. package/dist/components/Tabs.d.ts +2 -2
  139. package/dist/components/Tabs.js +12 -3
  140. package/dist/components/Task.js +2 -2
  141. package/dist/components/Terminal.d.ts +2 -2
  142. package/dist/components/Terminal.js +5 -4
  143. package/dist/components/TestResults.js +10 -6
  144. package/dist/components/Textarea.js +1 -1
  145. package/dist/components/ThemeModeControl.js +1 -1
  146. package/dist/components/ThinkChat.d.ts +33 -20
  147. package/dist/components/ThinkChat.js +4 -4
  148. package/dist/components/ThinkChatActivity.d.ts +2 -2
  149. package/dist/components/ThinkChatActivity.js +8 -10
  150. package/dist/components/ThinkChatActivitySummary.d.ts +1 -1
  151. package/dist/components/ThinkChatActivitySummary.js +7 -3
  152. package/dist/components/ThinkChatToolPanels.js +26 -16
  153. package/dist/components/ThinkExecutionDetail.js +2 -2
  154. package/dist/components/ThinkExecutionStatus.js +2 -2
  155. package/dist/components/ThinkExecutionWorkbench.d.ts +30 -8
  156. package/dist/components/ThinkExecutionWorkbench.js +70 -134
  157. package/dist/components/ThinkFullApp.d.ts +36 -35
  158. package/dist/components/ThinkFullApp.js +156 -109
  159. package/dist/components/ThinkFullAppViewSwitch.d.ts +1 -1
  160. package/dist/components/ThinkFullAppViewSwitch.js +1 -1
  161. package/dist/components/ThinkRecoveryNotice.js +18 -12
  162. package/dist/components/ThinkRoutineBrowser.d.ts +17 -9
  163. package/dist/components/ThinkRoutineBrowser.js +33 -40
  164. package/dist/components/ThinkRoutineDetail.d.ts +5 -2
  165. package/dist/components/ThinkRoutineDetail.js +4 -4
  166. package/dist/components/ThinkRoutineForm.d.ts +3 -2
  167. package/dist/components/ThinkRoutineForm.js +11 -10
  168. package/dist/components/ThinkRoutineLibrary.d.ts +24 -31
  169. package/dist/components/ThinkRoutineLibrary.js +57 -80
  170. package/dist/components/ThinkRoutineRuns.d.ts +3 -2
  171. package/dist/components/ThinkRoutineRuns.js +31 -24
  172. package/dist/components/ThinkThreadPicker.d.ts +17 -11
  173. package/dist/components/ThinkThreadPicker.js +163 -39
  174. package/dist/components/Toast.js +1 -1
  175. package/dist/components/Toggle.d.ts +1 -1
  176. package/dist/components/Toggle.js +9 -7
  177. package/dist/components/ToggleGroup.d.ts +1 -1
  178. package/dist/components/ToggleGroup.js +15 -13
  179. package/dist/components/ToolInvocation.js +1 -1
  180. package/dist/components/Toolbar.js +1 -1
  181. package/dist/components/Tooltip.d.ts +8 -1
  182. package/dist/components/Tooltip.js +11 -2
  183. package/dist/components/TransitionSurface.d.ts +27 -0
  184. package/dist/components/TransitionSurface.js +20 -0
  185. package/dist/components/WebPreview.d.ts +5 -5
  186. package/dist/components/WebPreview.js +68 -54
  187. package/dist/components/Workspace.d.ts +2 -2
  188. package/dist/components/Workspace.js +358 -328
  189. package/dist/components/WorkspaceBrowser.d.ts +4 -7
  190. package/dist/components/WorkspaceBrowser.js +15 -25
  191. package/dist/components/WorkspaceExplorer.d.ts +5 -5
  192. package/dist/components/WorkspaceExplorer.js +19 -32
  193. package/dist/components/WorkspaceFile.d.ts +1 -1
  194. package/dist/components/WorkspaceFileView.d.ts +14 -2
  195. package/dist/components/WorkspaceFileView.js +1 -1
  196. package/dist/components/WorkspaceHeader.d.ts +0 -8
  197. package/dist/components/WorkspaceHeader.js +3 -15
  198. package/dist/components/WorkspaceLink.d.ts +1 -1
  199. package/dist/components/WorkspaceLink.js +1 -1
  200. package/dist/components/icons.d.ts +2 -2
  201. package/dist/components/icons.js +2 -2
  202. package/dist/{copy-button-CQx3LQAC.d.ts → copy-button-D7aAYA-2.d.ts} +1 -1
  203. package/dist/{copy-button-cYsJBauI.js → copy-button-DeK6KXEV.js} +3 -2
  204. package/dist/{icons--FqCZEc3.js → icons-CQJ-aMnI.js} +6 -3
  205. package/dist/{icons-DpUqY8Mp.d.ts → icons-CYXmvKlJ.d.ts} +17 -2
  206. package/dist/navigation-link-Cob7KBlM.js +19 -0
  207. package/dist/styles/accordion.css +4 -19
  208. package/dist/styles/activity.css +48 -56
  209. package/dist/styles/agent-activity-preview.css +2 -0
  210. package/dist/styles/agent-chat.css +14 -22
  211. package/dist/styles/alert-dialog.css +16 -18
  212. package/dist/styles/app-top-bar.css +17 -9
  213. package/dist/styles/approval.css +11 -6
  214. package/dist/styles/artifact.css +6 -6
  215. package/dist/styles/autocomplete.css +2 -1
  216. package/dist/styles/avatar.css +35 -30
  217. package/dist/styles/badge.css +10 -10
  218. package/dist/styles/base-ui-motion.css +94 -39
  219. package/dist/styles/browser-control-overlay.css +0 -1
  220. package/dist/styles/browser-live-view.css +39 -22
  221. package/dist/styles/browser-replay.css +1 -1
  222. package/dist/styles/browser-run-panel.css +8 -4
  223. package/dist/styles/button.css +36 -33
  224. package/dist/styles/card.css +5 -6
  225. package/dist/styles/chain-of-thought.css +22 -16
  226. package/dist/styles/checkbox.css +18 -5
  227. package/dist/styles/codex-model-selector.css +3 -7
  228. package/dist/styles/collapsible.css +10 -10
  229. package/dist/styles/combobox.css +3 -2
  230. package/dist/styles/commit.css +19 -18
  231. package/dist/styles/composition-motion.css +83 -0
  232. package/dist/styles/context-menu.css +3 -3
  233. package/dist/styles/context.css +11 -10
  234. package/dist/styles/conversation.css +18 -23
  235. package/dist/styles/dialog.css +15 -15
  236. package/dist/styles/field.css +32 -12
  237. package/dist/styles/file-tree.css +69 -9
  238. package/dist/styles/form.css +1 -1
  239. package/dist/styles/index.css +5 -0
  240. package/dist/styles/input.css +2 -2
  241. package/dist/styles/marker.css +14 -13
  242. package/dist/styles/mcp-server-picker.css +20 -17
  243. package/dist/styles/media-preview-card.css +160 -0
  244. package/dist/styles/menu.css +11 -11
  245. package/dist/styles/menubar.css +12 -11
  246. package/dist/styles/message.css +27 -21
  247. package/dist/styles/meter.css +2 -2
  248. package/dist/styles/model-selector.css +16 -14
  249. package/dist/styles/motion-reduction.css +29 -0
  250. package/dist/styles/navigation-menu.css +14 -30
  251. package/dist/styles/number-field.css +22 -4
  252. package/dist/styles/plan.css +5 -0
  253. package/dist/styles/popover.css +15 -16
  254. package/dist/styles/preview-card.css +2 -32
  255. package/dist/styles/progress.css +8 -3
  256. package/dist/styles/prompt-input.css +75 -75
  257. package/dist/styles/queue.css +48 -32
  258. package/dist/styles/radio.css +19 -12
  259. package/dist/styles/reasoning.css +2 -1
  260. package/dist/styles/recipes.css +109 -37
  261. package/dist/styles/resizable-side-panel.css +4 -10
  262. package/dist/styles/responsive-sidebar-shell.css +16 -15
  263. package/dist/styles/scroll-area.css +32 -15
  264. package/dist/styles/search-field.css +21 -0
  265. package/dist/styles/select.css +19 -12
  266. package/dist/styles/slider.css +25 -29
  267. package/dist/styles/sources.css +3 -3
  268. package/dist/styles/stepper.css +36 -26
  269. package/dist/styles/subagent-run.css +1 -1
  270. package/dist/styles/switch.css +32 -29
  271. package/dist/styles/tabs.css +79 -23
  272. package/dist/styles/task.css +13 -9
  273. package/dist/styles/test-results.css +29 -23
  274. package/dist/styles/theme-mode-control.css +8 -0
  275. package/dist/styles/think-execution-workbench.css +18 -15
  276. package/dist/styles/think-full-app.css +45 -68
  277. package/dist/styles/think-routine-library.css +43 -24
  278. package/dist/styles/think-thread-picker.css +193 -51
  279. package/dist/styles/toast.css +48 -27
  280. package/dist/styles/toggle-group.css +2 -36
  281. package/dist/styles/toggle.css +29 -5
  282. package/dist/styles/toolbar.css +25 -27
  283. package/dist/styles/tooltip.css +2 -32
  284. package/dist/styles/transition-surface.css +55 -0
  285. package/dist/styles/workspace-explorer.css +99 -44
  286. package/dist/summarize-tool-input-CqTcGRYy.js +15 -0
  287. package/dist/utils/mcp-server-catalog.d.ts +132 -0
  288. package/dist/utils/mcp-server-catalog.js +90 -0
  289. package/dist/utils/structured-code.d.ts +1 -1
  290. package/package.json +13 -7
  291. package/dist/Workspace-C9RFoxcu.d.ts +0 -62
  292. package/dist/summarize-tool-input-BUJsENSn.js +0 -12
@@ -1,18 +1,29 @@
1
1
  import { n as isWorkspacePathWithinRoot, t as canonicalizeAbsoluteWorkspacePath } from "../workspace-path-L_KmjRjq.js";
2
2
  import { getWorkspaceBasename, inferWorkspaceMedia } from "./WorkspaceFile.js";
3
3
  import * as React from "react";
4
+ import { queryOptions, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
4
5
  //#region src/components/Workspace.ts
5
6
  const DEFAULT_MAX_INLINE_ASSET_BYTES = 32 * 1024 * 1024;
6
7
  const WORKSPACE_DIRECTORY_PAGE_SIZE = 1e3;
8
+ const ACTIVE_FILE_GC_TIME = 2e3;
7
9
  const EMPTY_ENTRIES_BY_DIRECTORY = Object.freeze({});
8
10
  const EMPTY_ENTRY_BY_PATH = /* @__PURE__ */ new Map();
9
11
  const EMPTY_PATHS = /* @__PURE__ */ new Set();
12
+ const INACTIVE_FILE_INFO = {
13
+ path: "",
14
+ name: "",
15
+ type: "file",
16
+ mimeType: "text/plain",
17
+ size: 0,
18
+ createdAt: 0,
19
+ updatedAt: 0
20
+ };
21
+ const INACTIVE_FILE_ASSET_RESOLVER = () => null;
10
22
  function toErrorMessage(error) {
11
23
  return error instanceof Error ? error.message : String(error);
12
24
  }
13
- function getWorkspaceParentPath(path) {
14
- const separatorIndex = path.lastIndexOf("/");
15
- return separatorIndex <= 0 ? "/" : path.slice(0, separatorIndex);
25
+ function throwIfAborted(signal) {
26
+ if (signal.aborted) throw signal.reason ?? new DOMException("The query was cancelled.", "AbortError");
16
27
  }
17
28
  function getAssetDataSize(asset) {
18
29
  if (asset.kind === "url") return void 0;
@@ -27,10 +38,7 @@ function normalizeWorkspaceAsset(asset, path, inferredMedia, info) {
27
38
  size: asset.size ?? getAssetDataSize(asset) ?? info.size
28
39
  };
29
40
  return {
30
- asset: asset.kind === "url" ? {
31
- ...asset,
32
- ...metadata
33
- } : {
41
+ asset: {
34
42
  ...asset,
35
43
  ...metadata
36
44
  },
@@ -50,360 +58,382 @@ async function readWorkspaceAssetBytes(source, path, info, maxInlineAssetBytes)
50
58
  } : null;
51
59
  }
52
60
  /**
53
- * Headless state for browsing a Think workspace: lazy directory loading,
54
- * cached file reads, expansion/selection, and refresh. Feed the result to
55
- * `WorkspaceExplorer`, or compose `WorkspaceBrowser` and `WorkspaceFileView`.
61
+ * Source/root prefix for cache inspection, cancellation, or removal.
62
+ * Do not broadly invalidate this prefix: active file queries must refresh in
63
+ * stat → resolver → content order. Use a resource option key or `refresh()`.
56
64
  */
57
- function useWorkspace({ source, root = "/", resolveFileAsset, maxInlineAssetBytes = DEFAULT_MAX_INLINE_ASSET_BYTES }) {
58
- const [entriesByDirectory, setEntriesByDirectory] = React.useState({});
59
- const [filesByPath, setFilesByPath] = React.useState({});
60
- const [expandedPaths, setExpandedPathsState] = React.useState(/* @__PURE__ */ new Set());
61
- const [selectedPath, setSelectedPath] = React.useState(null);
62
- const [isLoading, setIsLoading] = React.useState(false);
63
- const [loadingPaths, setLoadingPaths] = React.useState(/* @__PURE__ */ new Set());
64
- const [directoryErrorsByPath, setDirectoryErrorsByPath] = React.useState({});
65
- const [info, setInfo] = React.useState(null);
66
- const [activeAssetFile, setActiveAssetFile] = React.useState(null);
67
- const [stateGeneration, setStateGeneration] = React.useState(0);
68
- const loadedDirectoriesRef = React.useRef(/* @__PURE__ */ new Set());
69
- const directoryRequestVersionsRef = React.useRef(/* @__PURE__ */ new Map());
70
- const nextDirectoryRequestVersionRef = React.useRef(0);
71
- const nextInfoRequestVersionRef = React.useRef(0);
72
- const refreshRequestVersionRef = React.useRef(0);
73
- const loadingOperationVersionRef = React.useRef(0);
74
- const selectedPathRef = React.useRef(null);
75
- const fileRequestRef = React.useRef(null);
76
- const resolveFileAssetRef = React.useRef(resolveFileAsset);
77
- resolveFileAssetRef.current = resolveFileAsset;
78
- const maxInlineAssetBytesRef = React.useRef(DEFAULT_MAX_INLINE_ASSET_BYTES);
79
- maxInlineAssetBytesRef.current = Number.isFinite(maxInlineAssetBytes) && maxInlineAssetBytes >= 0 ? maxInlineAssetBytes : DEFAULT_MAX_INLINE_ASSET_BYTES;
80
- const sourceIdentityRef = React.useRef({
81
- source,
82
- root,
83
- generation: 0
84
- });
85
- if (sourceIdentityRef.current.source !== source || sourceIdentityRef.current.root !== root) sourceIdentityRef.current = {
86
- source,
87
- root,
88
- generation: sourceIdentityRef.current.generation + 1
65
+ function workspaceQueryKey(sourceKey, root) {
66
+ return [
67
+ "workspace",
68
+ sourceKey,
69
+ root
70
+ ];
71
+ }
72
+ function workspaceFileQueryKey(sourceKey, root, requestedPath) {
73
+ return [
74
+ ...workspaceQueryKey(sourceKey, root),
75
+ "file",
76
+ requestedPath
77
+ ];
78
+ }
79
+ function workspaceFileVersion(target) {
80
+ return {
81
+ path: target.readPath,
82
+ name: target.info.name,
83
+ type: target.info.type,
84
+ mimeType: target.info.mimeType ?? null,
85
+ size: target.info.size,
86
+ createdAt: target.info.createdAt,
87
+ updatedAt: target.info.updatedAt,
88
+ target: target.info.target ?? null
89
89
  };
90
- const entryByPath = React.useMemo(() => {
91
- const map = /* @__PURE__ */ new Map();
92
- for (const entries of Object.values(entriesByDirectory)) for (const entry of entries) map.set(entry.path, entry);
93
- return map;
94
- }, [entriesByDirectory]);
95
- const markLoading = React.useCallback((path, loading) => {
96
- setLoadingPaths((current) => {
97
- const next = new Set(current);
98
- if (loading) next.add(path);
99
- else next.delete(path);
100
- return next;
101
- });
102
- }, []);
103
- const setDirectoryError = React.useCallback((path, message) => {
104
- setDirectoryErrorsByPath((current) => {
105
- if (message !== null) {
106
- if (current[path] === message) return current;
107
- return {
108
- ...current,
109
- [path]: message
110
- };
111
- }
112
- if (!Object.hasOwn(current, path)) return current;
113
- const next = { ...current };
114
- delete next[path];
115
- return next;
116
- });
117
- }, []);
118
- const activateAssetFile = React.useCallback((file) => {
119
- setFilesByPath((current) => {
120
- if (!Object.hasOwn(current, file.path)) return current;
121
- const next = { ...current };
122
- delete next[file.path];
123
- return next;
124
- });
125
- setActiveAssetFile(file);
126
- }, []);
127
- const cacheTextFile = React.useCallback((file) => {
128
- setActiveAssetFile((current) => current?.path === file.path ? null : current);
129
- setFilesByPath((current) => ({
130
- ...current,
131
- [file.path]: file
132
- }));
133
- }, []);
134
- const cancelFileLoad = React.useCallback(() => {
135
- const request = fileRequestRef.current;
136
- if (!request) return;
137
- request.controller.abort();
138
- fileRequestRef.current = null;
139
- markLoading(request.path, false);
140
- }, [markLoading]);
141
- const loadDirectory = React.useCallback(async (path, { force = false } = {}) => {
142
- if (!force && loadedDirectoriesRef.current.has(path)) return;
143
- const identity = sourceIdentityRef.current;
144
- if (identity.source !== source || identity.root !== root) return;
145
- const generation = identity.generation;
146
- const requestVersion = nextDirectoryRequestVersionRef.current + 1;
147
- nextDirectoryRequestVersionRef.current = requestVersion;
148
- directoryRequestVersionsRef.current.set(path, requestVersion);
149
- const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && directoryRequestVersionsRef.current.get(path) === requestVersion;
150
- markLoading(path, true);
151
- try {
90
+ }
91
+ /** Shared directory options for workspace components and route loaders. */
92
+ function workspaceDirectoryQueryOptions(source, sourceKey, root, path) {
93
+ return queryOptions({
94
+ queryKey: [
95
+ ...workspaceQueryKey(sourceKey, root),
96
+ "directory",
97
+ path
98
+ ],
99
+ queryFn: async ({ signal }) => {
152
100
  const entries = [];
153
101
  let offset = 0;
154
102
  for (;;) {
103
+ throwIfAborted(signal);
155
104
  const page = await source.readDir(path, {
156
105
  limit: WORKSPACE_DIRECTORY_PAGE_SIZE,
157
106
  offset
158
107
  });
159
- if (!requestIsCurrent()) return;
108
+ throwIfAborted(signal);
160
109
  entries.push(...page);
161
- if (page.length < WORKSPACE_DIRECTORY_PAGE_SIZE) break;
110
+ if (page.length < WORKSPACE_DIRECTORY_PAGE_SIZE) return entries;
162
111
  offset += page.length;
163
112
  }
164
- loadedDirectoriesRef.current.add(path);
165
- setEntriesByDirectory((current) => ({
166
- ...current,
167
- [path]: entries
168
- }));
169
- setDirectoryError(path, null);
170
- return entries;
171
- } catch (listError) {
172
- if (!requestIsCurrent()) return;
173
- setDirectoryError(path, toErrorMessage(listError));
174
- return null;
175
- } finally {
176
- if (requestIsCurrent()) markLoading(path, false);
177
- }
178
- }, [
179
- source,
180
- root,
181
- markLoading,
182
- setDirectoryError
183
- ]);
184
- const loadFile = React.useCallback(async (path, { force = false, refreshRequestVersion } = {}) => {
185
- const listedFileInfo = entryByPath.get(path) ?? null;
186
- const cachedFile = filesByPath[path];
187
- if (!force && cachedFile && !inferWorkspaceMedia(path, listedFileInfo?.mimeType)) return;
188
- const identity = sourceIdentityRef.current;
189
- if (identity.source !== source || identity.root !== root) return;
190
- const generation = identity.generation;
191
- cancelFileLoad();
192
- const controller = new AbortController();
193
- const request = {
194
- path,
195
- controller
196
- };
197
- fileRequestRef.current = request;
198
- markLoading(path, true);
199
- let fileInfo = listedFileInfo;
200
- let media = inferWorkspaceMedia(path, fileInfo?.mimeType);
201
- const requestIsCurrent = () => !controller.signal.aborted && generation === sourceIdentityRef.current.generation && selectedPathRef.current === path && (refreshRequestVersion === void 0 || refreshRequestVersionRef.current === refreshRequestVersion);
202
- try {
203
- if (!isWorkspacePathWithinRoot(path, root)) throw new Error("This file is outside the configured workspace root.");
204
- const resolvedInfo = await source.stat(path);
205
- if (!requestIsCurrent()) return;
206
- if (!resolvedInfo) {
207
- fileInfo = null;
208
- throw new Error("File metadata is unavailable because source.stat() returned null.");
209
- }
210
- fileInfo = resolvedInfo;
211
- const readPath = canonicalizeAbsoluteWorkspacePath(resolvedInfo.path);
113
+ },
114
+ staleTime: Infinity
115
+ });
116
+ }
117
+ /** Shared workspace-summary options for workspace components and route loaders. */
118
+ function workspaceInfoQueryOptions(source, sourceKey, root) {
119
+ return queryOptions({
120
+ queryKey: [...workspaceQueryKey(sourceKey, root), "info"],
121
+ queryFn: async ({ signal }) => {
122
+ if (!("getWorkspaceInfo" in source) || typeof source.getWorkspaceInfo !== "function") return null;
123
+ const info = await source.getWorkspaceInfo();
124
+ throwIfAborted(signal);
125
+ return info;
126
+ },
127
+ staleTime: Infinity
128
+ });
129
+ }
130
+ /** Re-stat and validate a requested path before any resolver or file read. */
131
+ function workspaceFileStatQueryOptions(source, sourceKey, root, requestedPath) {
132
+ return queryOptions({
133
+ queryKey: [...workspaceFileQueryKey(sourceKey, root, requestedPath), "stat"],
134
+ queryFn: async ({ signal }) => {
135
+ if (!isWorkspacePathWithinRoot(requestedPath, root)) throw new Error("This file is outside the configured workspace root.");
136
+ const info = await source.stat(requestedPath);
137
+ throwIfAborted(signal);
138
+ if (!info) throw new Error("File metadata is unavailable because source.stat() returned null.");
139
+ const readPath = canonicalizeAbsoluteWorkspacePath(info.path);
212
140
  if (!readPath) throw new Error("The workspace source returned an invalid file path from stat().");
213
- if (readPath !== resolvedInfo.path) throw new Error("The workspace source returned a non-canonical file path from stat().");
141
+ if (readPath !== info.path) throw new Error("The workspace source returned a non-canonical file path from stat().");
214
142
  if (!isWorkspacePathWithinRoot(readPath, root)) throw new Error("This file's resolved path is outside the configured workspace root.");
215
- media = inferWorkspaceMedia(readPath, resolvedInfo.mimeType);
216
- const resolver = resolveFileAssetRef.current;
217
- const resolvedAsset = resolver ? await resolver({
218
- path: readPath,
219
- requestedPath: path,
143
+ return {
144
+ requestedPath,
145
+ readPath,
146
+ info,
147
+ media: inferWorkspaceMedia(readPath, info.mimeType)
148
+ };
149
+ },
150
+ staleTime: 0,
151
+ refetchOnMount: "always",
152
+ refetchOnWindowFocus: false,
153
+ refetchOnReconnect: false,
154
+ retry: false
155
+ });
156
+ }
157
+ /** Optional host asset resolution, scoped to the current stat result. */
158
+ function workspaceFileAssetQueryOptions(sourceKey, root, target, resolveFileAsset) {
159
+ return queryOptions({
160
+ queryKey: [
161
+ ...workspaceFileQueryKey(sourceKey, root, target.requestedPath),
162
+ "asset",
163
+ workspaceFileVersion(target)
164
+ ],
165
+ queryFn: async ({ signal }) => {
166
+ const resolvedAsset = await resolveFileAsset({
167
+ path: target.readPath,
168
+ requestedPath: target.requestedPath,
220
169
  root: canonicalizeAbsoluteWorkspacePath(root) ?? root,
221
- info: resolvedInfo,
222
- signal: controller.signal
223
- }) : null;
224
- if (!requestIsCurrent()) return;
225
- if (resolvedAsset) {
226
- const normalized = normalizeWorkspaceAsset(resolvedAsset, readPath, media, resolvedInfo);
227
- if (!normalized) throw new Error("The host returned an asset without a supported image, PDF, audio, or video MIME type.");
228
- media = normalized.media;
229
- activateAssetFile({
230
- path,
231
- name: getWorkspaceBasename(path),
232
- content: null,
233
- asset: normalized.asset,
234
- info: resolvedInfo,
235
- error: null
236
- });
237
- return;
238
- }
239
- if (media) {
240
- const byteAsset = await readWorkspaceAssetBytes(source, readPath, resolvedInfo, maxInlineAssetBytesRef.current);
241
- if (!requestIsCurrent()) return;
242
- const normalized = byteAsset ? normalizeWorkspaceAsset(byteAsset, readPath, media, resolvedInfo) : null;
243
- activateAssetFile({
244
- path,
245
- name: getWorkspaceBasename(path),
246
- content: null,
247
- asset: normalized?.asset ?? null,
248
- info: resolvedInfo,
249
- error: null
250
- });
251
- return;
252
- }
253
- const content = await source.readFile(readPath);
254
- if (!requestIsCurrent()) return;
255
- cacheTextFile({
256
- path,
257
- name: getWorkspaceBasename(path),
258
- content,
259
- asset: null,
260
- info: resolvedInfo,
261
- error: null
170
+ info: target.info,
171
+ signal
262
172
  });
263
- } catch (readError) {
264
- if (!requestIsCurrent()) return;
265
- const failedFile = {
266
- path,
267
- name: getWorkspaceBasename(path),
268
- content: null,
269
- asset: null,
270
- info: fileInfo,
271
- error: toErrorMessage(readError)
272
- };
273
- if (media) activateAssetFile(failedFile);
274
- else cacheTextFile(failedFile);
275
- } finally {
276
- if (fileRequestRef.current === request && generation === sourceIdentityRef.current.generation) {
277
- fileRequestRef.current = null;
278
- markLoading(path, false);
279
- }
280
- }
281
- }, [
282
- activateAssetFile,
283
- cacheTextFile,
284
- cancelFileLoad,
285
- entryByPath,
286
- filesByPath,
287
- markLoading,
288
- root,
289
- source
290
- ]);
291
- const loadInfo = React.useCallback(async () => {
292
- if (!("getWorkspaceInfo" in source)) return;
293
- if (typeof source.getWorkspaceInfo !== "function") return;
294
- const identity = sourceIdentityRef.current;
295
- if (identity.source !== source || identity.root !== root) return;
296
- const generation = identity.generation;
297
- const requestVersion = nextInfoRequestVersionRef.current + 1;
298
- nextInfoRequestVersionRef.current = requestVersion;
299
- const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && nextInfoRequestVersionRef.current === requestVersion;
300
- try {
301
- const nextInfo = await source.getWorkspaceInfo();
302
- if (requestIsCurrent()) setInfo(nextInfo);
303
- } catch {
304
- if (requestIsCurrent()) setInfo(null);
305
- }
306
- }, [source, root]);
307
- React.useEffect(() => {
308
- const generation = sourceIdentityRef.current.generation;
309
- const loadingOperationVersion = loadingOperationVersionRef.current + 1;
310
- loadingOperationVersionRef.current = loadingOperationVersion;
311
- refreshRequestVersionRef.current += 1;
312
- cancelFileLoad();
313
- setEntriesByDirectory({});
314
- setFilesByPath({});
315
- setActiveAssetFile(null);
316
- setExpandedPathsState(/* @__PURE__ */ new Set());
317
- setSelectedPath(null);
318
- selectedPathRef.current = null;
319
- setLoadingPaths(/* @__PURE__ */ new Set());
320
- setDirectoryErrorsByPath({});
321
- setInfo(null);
322
- setStateGeneration(generation);
323
- loadedDirectoriesRef.current = /* @__PURE__ */ new Set();
324
- directoryRequestVersionsRef.current = /* @__PURE__ */ new Map();
325
- setIsLoading(true);
326
- Promise.all([loadDirectory(root, { force: true }), loadInfo()]).finally(() => {
327
- if (generation === sourceIdentityRef.current.generation && loadingOperationVersionRef.current === loadingOperationVersion) setIsLoading(false);
173
+ if (!resolvedAsset) return null;
174
+ const normalized = normalizeWorkspaceAsset(resolvedAsset, target.readPath, target.media, target.info);
175
+ if (!normalized) throw new Error("The host returned an asset without a supported image, PDF, audio, or video MIME type.");
176
+ return normalized;
177
+ },
178
+ staleTime: 0,
179
+ gcTime: ACTIVE_FILE_GC_TIME,
180
+ refetchOnMount: "always",
181
+ refetchOnWindowFocus: false,
182
+ refetchOnReconnect: false,
183
+ retry: false
184
+ });
185
+ }
186
+ /** Cached text read options, scoped to the validated stat result. */
187
+ function workspaceTextFileQueryOptions(source, sourceKey, root, target) {
188
+ return queryOptions({
189
+ queryKey: [
190
+ ...workspaceFileQueryKey(sourceKey, root, target.requestedPath),
191
+ "text",
192
+ workspaceFileVersion(target)
193
+ ],
194
+ queryFn: async ({ signal }) => {
195
+ const content = await source.readFile(target.readPath);
196
+ throwIfAborted(signal);
197
+ return content;
198
+ },
199
+ staleTime: Infinity
200
+ });
201
+ }
202
+ /** Active-file-only buffered asset options, scoped to the validated stat result. */
203
+ function workspaceBinaryFileQueryOptions(source, sourceKey, root, target, maxInlineAssetBytes) {
204
+ return queryOptions({
205
+ queryKey: [
206
+ ...workspaceFileQueryKey(sourceKey, root, target.requestedPath),
207
+ "bytes",
208
+ workspaceFileVersion(target),
209
+ { maxInlineAssetBytes }
210
+ ],
211
+ queryFn: async ({ signal }) => {
212
+ const asset = await readWorkspaceAssetBytes(source, target.readPath, target.info, maxInlineAssetBytes);
213
+ throwIfAborted(signal);
214
+ return asset ? normalizeWorkspaceAsset(asset, target.readPath, target.media, target.info) : null;
215
+ },
216
+ staleTime: 0,
217
+ gcTime: ACTIVE_FILE_GC_TIME,
218
+ refetchOnMount: "always",
219
+ refetchOnWindowFocus: false,
220
+ refetchOnReconnect: false,
221
+ retry: false
222
+ });
223
+ }
224
+ async function refreshWorkspaceFile(queryClient, { source, sourceKey, root, selectedPath, resolveFileAsset, maxInlineAssetBytes }) {
225
+ if (!selectedPath) return;
226
+ const target = await queryClient.fetchQuery({
227
+ ...workspaceFileStatQueryOptions(source, sourceKey, root, selectedPath),
228
+ staleTime: 0
229
+ });
230
+ if (resolveFileAsset) {
231
+ const options = workspaceFileAssetQueryOptions(sourceKey, root, target, resolveFileAsset);
232
+ if (!queryClient.getQueryState(options.queryKey)) return;
233
+ if (await queryClient.fetchQuery({
234
+ ...options,
235
+ staleTime: 0
236
+ })) return;
237
+ }
238
+ if (target.media) {
239
+ const options = workspaceBinaryFileQueryOptions(source, sourceKey, root, target, maxInlineAssetBytes);
240
+ if (queryClient.getQueryState(options.queryKey)) await queryClient.fetchQuery({
241
+ ...options,
242
+ staleTime: 0
328
243
  });
244
+ return;
245
+ }
246
+ const options = workspaceTextFileQueryOptions(source, sourceKey, root, target);
247
+ if (queryClient.getQueryState(options.queryKey)) await queryClient.fetchQuery({
248
+ ...options,
249
+ staleTime: 0
250
+ });
251
+ }
252
+ /**
253
+ * Headless state for browsing a Think workspace. TanStack Query owns remote
254
+ * directory, metadata, resolver, text, and byte lifecycles; the route owns
255
+ * selection, while this hook only keeps local tree expansion state.
256
+ */
257
+ function useWorkspace({ source, sourceKey, root = "/", selectedPath, onSelectedPathChange, resolveFileAsset, maxInlineAssetBytes = DEFAULT_MAX_INLINE_ASSET_BYTES }) {
258
+ const queryClient = useQueryClient();
259
+ const scopeKey = JSON.stringify([sourceKey, root]);
260
+ const [expandedPathsByScope, setExpandedPathsByScope] = React.useState({});
261
+ const expandedPaths = expandedPathsByScope[scopeKey] ?? EMPTY_PATHS;
262
+ const latestExpansionRef = React.useRef({
263
+ scopeKey,
264
+ paths: expandedPaths
265
+ });
266
+ const normalizedMaxInlineAssetBytes = Number.isFinite(maxInlineAssetBytes) && maxInlineAssetBytes >= 0 ? maxInlineAssetBytes : DEFAULT_MAX_INLINE_ASSET_BYTES;
267
+ const directoryPaths = React.useMemo(() => [.../* @__PURE__ */ new Set([root, ...expandedPaths])].sort(), [expandedPaths, root]);
268
+ const directoryQueries = useQueries({ queries: directoryPaths.map((path) => workspaceDirectoryQueryOptions(source, sourceKey, root, path)) });
269
+ const infoQuery = useQuery({
270
+ ...workspaceInfoQueryOptions(source, sourceKey, root),
271
+ enabled: "getWorkspaceInfo" in source && typeof source.getWorkspaceInfo === "function"
272
+ });
273
+ const statQuery = useQuery({
274
+ ...workspaceFileStatQueryOptions(source, sourceKey, root, selectedPath ?? root),
275
+ enabled: selectedPath !== null
276
+ });
277
+ const target = selectedPath === null ? void 0 : statQuery.data;
278
+ const statSettled = target !== void 0 && statQuery.isSuccess && !statQuery.isFetching;
279
+ const queryTarget = target ?? {
280
+ requestedPath: selectedPath ?? root,
281
+ readPath: selectedPath ?? root,
282
+ info: INACTIVE_FILE_INFO,
283
+ media: null
284
+ };
285
+ const assetQuery = useQuery({
286
+ ...workspaceFileAssetQueryOptions(sourceKey, root, queryTarget, resolveFileAsset ?? INACTIVE_FILE_ASSET_RESOLVER),
287
+ enabled: statSettled && resolveFileAsset !== void 0
288
+ });
289
+ const resolvedAsset = assetQuery.data ?? null;
290
+ const resolverSettled = !resolveFileAsset || assetQuery.isSuccess && !assetQuery.isFetching;
291
+ const textQuery = useQuery({
292
+ ...workspaceTextFileQueryOptions(source, sourceKey, root, queryTarget),
293
+ enabled: statSettled && resolverSettled && !resolvedAsset && !target.media
294
+ });
295
+ const binaryQuery = useQuery({
296
+ ...workspaceBinaryFileQueryOptions(source, sourceKey, root, queryTarget, normalizedMaxInlineAssetBytes),
297
+ enabled: statSettled && resolverSettled && !resolvedAsset && Boolean(target.media)
298
+ });
299
+ const entriesByDirectory = React.useMemo(() => {
300
+ const entries = {};
301
+ for (const [index, path] of directoryPaths.entries()) {
302
+ const data = directoryQueries[index]?.data;
303
+ if (data) entries[path] = data;
304
+ }
305
+ return entries;
306
+ }, [directoryPaths, directoryQueries]);
307
+ const entryByPath = React.useMemo(() => {
308
+ const entries = /* @__PURE__ */ new Map();
309
+ for (const directoryEntries of Object.values(entriesByDirectory)) for (const entry of directoryEntries) entries.set(entry.path, entry);
310
+ return entries;
311
+ }, [entriesByDirectory]);
312
+ const setExpandedPaths = React.useCallback((next) => {
313
+ latestExpansionRef.current = {
314
+ scopeKey,
315
+ paths: next
316
+ };
317
+ setExpandedPathsByScope((current) => ({
318
+ ...current,
319
+ [scopeKey]: next
320
+ }));
321
+ }, [scopeKey]);
322
+ const selectFile = React.useCallback((path) => onSelectedPathChange?.(path), [onSelectedPathChange]);
323
+ const selectedError = statQuery.error ?? assetQuery.error ?? textQuery.error ?? binaryQuery.error;
324
+ let selectedFile = null;
325
+ if (selectedPath && selectedError) selectedFile = {
326
+ path: selectedPath,
327
+ name: getWorkspaceBasename(selectedPath),
328
+ content: null,
329
+ asset: null,
330
+ info: target?.info ?? null,
331
+ error: toErrorMessage(selectedError)
332
+ };
333
+ else if (selectedPath && statSettled && target && resolverSettled) {
334
+ if (resolvedAsset) selectedFile = {
335
+ path: selectedPath,
336
+ name: getWorkspaceBasename(selectedPath),
337
+ content: null,
338
+ asset: resolvedAsset.asset,
339
+ info: target.info,
340
+ error: null
341
+ };
342
+ else if (target.media && binaryQuery.isSuccess && !binaryQuery.isFetching) selectedFile = {
343
+ path: selectedPath,
344
+ name: getWorkspaceBasename(selectedPath),
345
+ content: null,
346
+ asset: binaryQuery.data?.asset ?? null,
347
+ info: target.info,
348
+ error: null
349
+ };
350
+ else if (!target.media && textQuery.isSuccess) selectedFile = {
351
+ path: selectedPath,
352
+ name: getWorkspaceBasename(selectedPath),
353
+ content: textQuery.data,
354
+ asset: null,
355
+ info: target.info,
356
+ error: null
357
+ };
358
+ }
359
+ const loadingPaths = React.useMemo(() => {
360
+ const paths = /* @__PURE__ */ new Set();
361
+ for (const [index, path] of directoryPaths.entries()) if (directoryQueries[index]?.isFetching) paths.add(path);
362
+ if (selectedPath && [
363
+ statQuery,
364
+ assetQuery,
365
+ textQuery,
366
+ binaryQuery
367
+ ].some((query) => query.isFetching)) paths.add(selectedPath);
368
+ return paths;
329
369
  }, [
330
- cancelFileLoad,
331
- loadDirectory,
332
- loadInfo,
333
- root
370
+ assetQuery,
371
+ binaryQuery,
372
+ directoryPaths,
373
+ directoryQueries,
374
+ selectedPath,
375
+ statQuery,
376
+ textQuery
334
377
  ]);
335
- React.useEffect(() => () => {
336
- fileRequestRef.current?.controller.abort();
337
- }, []);
338
- const setExpandedPaths = React.useCallback((next) => {
339
- setExpandedPathsState((current) => {
340
- for (const path of next) if (!current.has(path)) loadDirectory(path);
341
- return next;
342
- });
343
- }, [loadDirectory]);
344
- const selectFile = React.useCallback((path) => {
345
- cancelFileLoad();
346
- selectedPathRef.current = path;
347
- setActiveAssetFile(null);
348
- setSelectedPath(path);
349
- if (path !== null) loadFile(path);
350
- }, [cancelFileLoad, loadFile]);
378
+ const rootDirectoryIndex = directoryPaths.indexOf(root);
379
+ const isLoading = Boolean(directoryQueries[rootDirectoryIndex]?.isFetching) || infoQuery.isFetching;
380
+ const error = [...new Set(directoryQueries.map((query) => query.error).filter((queryError) => queryError !== null).map(toErrorMessage))].join("; ") || null;
351
381
  const refresh = React.useCallback(() => {
352
382
  (async () => {
353
- const identity = sourceIdentityRef.current;
354
- if (identity.source !== source || identity.root !== root) return;
355
- const generation = identity.generation;
356
- const refreshRequestVersion = refreshRequestVersionRef.current + 1;
357
- refreshRequestVersionRef.current = refreshRequestVersion;
358
- const loadingOperationVersion = loadingOperationVersionRef.current + 1;
359
- loadingOperationVersionRef.current = loadingOperationVersion;
360
- const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && refreshRequestVersionRef.current === refreshRequestVersion;
361
- setIsLoading(true);
362
- try {
363
- const directoryPaths = [.../* @__PURE__ */ new Set([root, ...expandedPaths])];
364
- const [refreshedListings] = await Promise.all([Promise.all(directoryPaths.map((path) => loadDirectory(path, { force: true }))), loadInfo()]);
365
- if (!requestIsCurrent()) return;
366
- const refreshedSelectedPath = selectedPathRef.current;
367
- if (refreshedSelectedPath) {
368
- const selectedDirectoryIndex = directoryPaths.indexOf(getWorkspaceParentPath(refreshedSelectedPath));
369
- const selectedDirectoryListing = refreshedListings[selectedDirectoryIndex];
370
- if (selectedDirectoryIndex >= 0 && (selectedDirectoryListing === null || selectedDirectoryListing === void 0)) return;
371
- await loadFile(refreshedSelectedPath, {
372
- force: true,
373
- refreshRequestVersion
383
+ const latestExpandedPaths = latestExpansionRef.current.scopeKey === scopeKey ? latestExpansionRef.current.paths : expandedPaths;
384
+ const refreshedDirectoryPaths = [.../* @__PURE__ */ new Set([root, ...latestExpandedPaths])].sort();
385
+ await queryClient.cancelQueries({ queryKey: workspaceQueryKey(sourceKey, root) });
386
+ const refreshedDirectories = await Promise.all(refreshedDirectoryPaths.map(async (path) => {
387
+ try {
388
+ await queryClient.fetchQuery({
389
+ ...workspaceDirectoryQueryOptions(source, sourceKey, root, path),
390
+ staleTime: 0
374
391
  });
392
+ return true;
393
+ } catch {
394
+ return false;
375
395
  }
376
- } finally {
377
- if (requestIsCurrent() && loadingOperationVersionRef.current === loadingOperationVersion) setIsLoading(false);
378
- }
379
- })();
396
+ }));
397
+ if ("getWorkspaceInfo" in source && typeof source.getWorkspaceInfo === "function") await queryClient.fetchQuery({
398
+ ...workspaceInfoQueryOptions(source, sourceKey, root),
399
+ staleTime: 0
400
+ }).catch(() => void 0);
401
+ if (refreshedDirectories.includes(false)) return;
402
+ await refreshWorkspaceFile(queryClient, {
403
+ source,
404
+ sourceKey,
405
+ root,
406
+ selectedPath,
407
+ resolveFileAsset,
408
+ maxInlineAssetBytes: normalizedMaxInlineAssetBytes
409
+ });
410
+ })().catch(() => {});
380
411
  }, [
381
412
  expandedPaths,
382
- loadDirectory,
383
- loadFile,
384
- loadInfo,
413
+ normalizedMaxInlineAssetBytes,
414
+ queryClient,
415
+ resolveFileAsset,
385
416
  root,
386
- source
417
+ scopeKey,
418
+ selectedPath,
419
+ source,
420
+ sourceKey
387
421
  ]);
388
- const stateIsCurrent = stateGeneration === sourceIdentityRef.current.generation;
389
- const directoryError = [...new Set(Object.values(directoryErrorsByPath))].join("; ") || null;
390
- const visibleSelectedPath = stateIsCurrent ? selectedPath : null;
391
- const selectedFile = visibleSelectedPath ? activeAssetFile?.path === visibleSelectedPath ? activeAssetFile : filesByPath[visibleSelectedPath] ?? null : null;
392
422
  return {
393
423
  root,
394
- entriesByDirectory: stateIsCurrent ? entriesByDirectory : EMPTY_ENTRIES_BY_DIRECTORY,
395
- entryByPath: stateIsCurrent ? entryByPath : EMPTY_ENTRY_BY_PATH,
396
- expandedPaths: stateIsCurrent ? expandedPaths : EMPTY_PATHS,
424
+ entriesByDirectory: Object.keys(entriesByDirectory).length > 0 ? entriesByDirectory : EMPTY_ENTRIES_BY_DIRECTORY,
425
+ entryByPath: entryByPath.size > 0 ? entryByPath : EMPTY_ENTRY_BY_PATH,
426
+ expandedPaths,
397
427
  setExpandedPaths,
398
- selectedPath: visibleSelectedPath,
428
+ selectedPath,
399
429
  selectFile,
400
430
  selectedFile,
401
- isLoading: !stateIsCurrent || isLoading,
402
- loadingPaths: stateIsCurrent ? loadingPaths : EMPTY_PATHS,
403
- error: stateIsCurrent ? directoryError : null,
404
- info: stateIsCurrent ? info : null,
431
+ isLoading,
432
+ loadingPaths: loadingPaths.size > 0 ? loadingPaths : EMPTY_PATHS,
433
+ error,
434
+ info: infoQuery.data ?? null,
405
435
  refresh
406
436
  };
407
437
  }
408
438
  //#endregion
409
- export { useWorkspace };
439
+ export { useWorkspace, workspaceBinaryFileQueryOptions, workspaceDirectoryQueryOptions, workspaceFileAssetQueryOptions, workspaceFileStatQueryOptions, workspaceInfoQueryOptions, workspaceQueryKey, workspaceTextFileQueryOptions };