@stigmer/react 3.1.1 → 3.1.2

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 (426) hide show
  1. package/composer/SessionComposer.d.ts +24 -1
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +19 -8
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactContentRenderer.d.ts +11 -1
  6. package/execution/ArtifactContentRenderer.d.ts.map +1 -1
  7. package/execution/ArtifactContentRenderer.js +39 -15
  8. package/execution/ArtifactContentRenderer.js.map +1 -1
  9. package/execution/FilePathContext.d.ts +8 -1
  10. package/execution/FilePathContext.d.ts.map +1 -1
  11. package/execution/FilePathContext.js.map +1 -1
  12. package/execution/FilePathLink.d.ts.map +1 -1
  13. package/execution/FilePathLink.js +21 -6
  14. package/execution/FilePathLink.js.map +1 -1
  15. package/execution/MessageEntry.d.ts +29 -0
  16. package/execution/MessageEntry.d.ts.map +1 -1
  17. package/execution/MessageEntry.js +29 -5
  18. package/execution/MessageEntry.js.map +1 -1
  19. package/execution/MessageThread.d.ts +76 -13
  20. package/execution/MessageThread.d.ts.map +1 -1
  21. package/execution/MessageThread.js +178 -24
  22. package/execution/MessageThread.js.map +1 -1
  23. package/execution/PlanArtifactCard.d.ts +27 -18
  24. package/execution/PlanArtifactCard.d.ts.map +1 -1
  25. package/execution/PlanArtifactCard.js +13 -23
  26. package/execution/PlanArtifactCard.js.map +1 -1
  27. package/execution/PlanDocumentMessage.d.ts +32 -0
  28. package/execution/PlanDocumentMessage.d.ts.map +1 -0
  29. package/execution/PlanDocumentMessage.js +37 -0
  30. package/execution/PlanDocumentMessage.js.map +1 -0
  31. package/execution/PlanStreamingCard.d.ts +45 -0
  32. package/execution/PlanStreamingCard.d.ts.map +1 -0
  33. package/execution/PlanStreamingCard.js +35 -0
  34. package/execution/PlanStreamingCard.js.map +1 -0
  35. package/execution/index.d.ts +5 -1
  36. package/execution/index.d.ts.map +1 -1
  37. package/execution/index.js +2 -0
  38. package/execution/index.js.map +1 -1
  39. package/execution/useCreateAgentExecution.d.ts +10 -0
  40. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  41. package/execution/useCreateAgentExecution.js +5 -1
  42. package/execution/useCreateAgentExecution.js.map +1 -1
  43. package/github/decodeGitHubContent.d.ts +29 -0
  44. package/github/decodeGitHubContent.d.ts.map +1 -0
  45. package/github/decodeGitHubContent.js +72 -0
  46. package/github/decodeGitHubContent.js.map +1 -0
  47. package/github/index.d.ts +1 -0
  48. package/github/index.d.ts.map +1 -1
  49. package/github/index.js +1 -0
  50. package/github/index.js.map +1 -1
  51. package/github/useGitHubFileReader.d.ts +26 -0
  52. package/github/useGitHubFileReader.d.ts.map +1 -0
  53. package/github/useGitHubFileReader.js +106 -0
  54. package/github/useGitHubFileReader.js.map +1 -0
  55. package/github/useGitHubTreeLister.d.ts.map +1 -1
  56. package/github/useGitHubTreeLister.js +6 -2
  57. package/github/useGitHubTreeLister.js.map +1 -1
  58. package/index.d.ts +9 -9
  59. package/index.d.ts.map +1 -1
  60. package/index.js +5 -5
  61. package/index.js.map +1 -1
  62. package/internal/ResizableSplit.d.ts +72 -22
  63. package/internal/ResizableSplit.d.ts.map +1 -1
  64. package/internal/ResizableSplit.js +96 -38
  65. package/internal/ResizableSplit.js.map +1 -1
  66. package/internal/VirtualizedThread.d.ts +5 -3
  67. package/internal/VirtualizedThread.d.ts.map +1 -1
  68. package/internal/VirtualizedThread.js +6 -4
  69. package/internal/VirtualizedThread.js.map +1 -1
  70. package/internal/file-icons/index.d.ts +17 -0
  71. package/internal/file-icons/index.d.ts.map +1 -0
  72. package/internal/file-icons/index.js +60 -0
  73. package/internal/file-icons/index.js.map +1 -0
  74. package/internal/file-tree/FileTreeNode.d.ts +18 -1
  75. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  76. package/internal/file-tree/FileTreeNode.js +11 -2
  77. package/internal/file-tree/FileTreeNode.js.map +1 -1
  78. package/internal/markdown-components.d.ts +38 -1
  79. package/internal/markdown-components.d.ts.map +1 -1
  80. package/internal/markdown-components.js +86 -11
  81. package/internal/markdown-components.js.map +1 -1
  82. package/{workflow → internal}/motion-preference.d.ts +7 -4
  83. package/internal/motion-preference.d.ts.map +1 -0
  84. package/{workflow → internal}/motion-preference.js +7 -4
  85. package/internal/motion-preference.js.map +1 -0
  86. package/internal/store/index.d.ts +33 -0
  87. package/internal/store/index.d.ts.map +1 -1
  88. package/internal/store/index.js +59 -0
  89. package/internal/store/index.js.map +1 -1
  90. package/internal/store/workspace-editors-store.d.ts +145 -0
  91. package/internal/store/workspace-editors-store.d.ts.map +1 -0
  92. package/internal/store/workspace-editors-store.js +208 -0
  93. package/internal/store/workspace-editors-store.js.map +1 -0
  94. package/internal/store/workspace-file-selection-store.d.ts +38 -0
  95. package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
  96. package/internal/store/workspace-file-selection-store.js +62 -0
  97. package/internal/store/workspace-file-selection-store.js.map +1 -0
  98. package/internal/useRevealLine.d.ts +49 -0
  99. package/internal/useRevealLine.d.ts.map +1 -0
  100. package/internal/useRevealLine.js +43 -0
  101. package/internal/useRevealLine.js.map +1 -0
  102. package/library/detect-plan-artifact.d.ts +21 -0
  103. package/library/detect-plan-artifact.d.ts.map +1 -1
  104. package/library/detect-plan-artifact.js +20 -0
  105. package/library/detect-plan-artifact.js.map +1 -1
  106. package/library/detect-streaming-plan.d.ts +45 -0
  107. package/library/detect-streaming-plan.d.ts.map +1 -0
  108. package/library/detect-streaming-plan.js +83 -0
  109. package/library/detect-streaming-plan.js.map +1 -0
  110. package/library/index.d.ts +4 -1
  111. package/library/index.d.ts.map +1 -1
  112. package/library/index.js +2 -1
  113. package/library/index.js.map +1 -1
  114. package/package.json +4 -4
  115. package/session/NewSessionViewer.d.ts +19 -4
  116. package/session/NewSessionViewer.d.ts.map +1 -1
  117. package/session/NewSessionViewer.js +52 -25
  118. package/session/NewSessionViewer.js.map +1 -1
  119. package/session/PlanEditor.d.ts +55 -0
  120. package/session/PlanEditor.d.ts.map +1 -0
  121. package/session/PlanEditor.js +88 -0
  122. package/session/PlanEditor.js.map +1 -0
  123. package/session/PlanStreamingDocument.d.ts +30 -0
  124. package/session/PlanStreamingDocument.d.ts.map +1 -0
  125. package/session/PlanStreamingDocument.js +37 -0
  126. package/session/PlanStreamingDocument.js.map +1 -0
  127. package/session/SessionPanelChip.d.ts +26 -0
  128. package/session/SessionPanelChip.d.ts.map +1 -0
  129. package/session/SessionPanelChip.js +40 -0
  130. package/session/SessionPanelChip.js.map +1 -0
  131. package/session/SessionViewer.d.ts +34 -7
  132. package/session/SessionViewer.d.ts.map +1 -1
  133. package/session/SessionViewer.js +315 -33
  134. package/session/SessionViewer.js.map +1 -1
  135. package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
  136. package/session/facets/ArtifactsTab.d.ts.map +1 -0
  137. package/session/{inspector → facets}/ArtifactsTab.js +8 -3
  138. package/session/facets/ArtifactsTab.js.map +1 -0
  139. package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
  140. package/session/facets/ChangesTab.d.ts.map +1 -0
  141. package/session/{inspector → facets}/ChangesTab.js +3 -3
  142. package/session/facets/ChangesTab.js.map +1 -0
  143. package/session/facets/InspectTab.d.ts.map +1 -0
  144. package/session/facets/InspectTab.js.map +1 -0
  145. package/session/{inspector → facets}/SetupTab.d.ts +11 -2
  146. package/session/facets/SetupTab.d.ts.map +1 -0
  147. package/session/{inspector → facets}/SetupTab.js +4 -3
  148. package/session/facets/SetupTab.js.map +1 -0
  149. package/session/{inspector → facets}/UsageTab.d.ts +1 -1
  150. package/session/facets/UsageTab.d.ts.map +1 -0
  151. package/session/{inspector → facets}/UsageTab.js +1 -1
  152. package/session/facets/UsageTab.js.map +1 -0
  153. package/session/{inspector → facets}/index.d.ts +0 -6
  154. package/session/facets/index.d.ts.map +1 -0
  155. package/session/{inspector → facets}/index.js +0 -3
  156. package/session/facets/index.js.map +1 -0
  157. package/session/index.d.ts +11 -2
  158. package/session/index.d.ts.map +1 -1
  159. package/session/index.js +15 -2
  160. package/session/index.js.map +1 -1
  161. package/session/plan-document.d.ts +9 -0
  162. package/session/plan-document.d.ts.map +1 -0
  163. package/session/plan-document.js +17 -0
  164. package/session/plan-document.js.map +1 -0
  165. package/session/useOpenFileChange.d.ts +20 -0
  166. package/session/useOpenFileChange.d.ts.map +1 -0
  167. package/session/useOpenFileChange.js +31 -0
  168. package/session/useOpenFileChange.js.map +1 -0
  169. package/session/usePlanDraft.d.ts +52 -0
  170. package/session/usePlanDraft.d.ts.map +1 -0
  171. package/session/usePlanDraft.js +63 -0
  172. package/session/usePlanDraft.js.map +1 -0
  173. package/session/useSessionConversation.d.ts +6 -0
  174. package/session/useSessionConversation.d.ts.map +1 -1
  175. package/session/useSessionConversation.js +1 -0
  176. package/session/useSessionConversation.js.map +1 -1
  177. package/session/useSessionPageFlow.d.ts.map +1 -1
  178. package/session/useSessionPageFlow.js +1 -0
  179. package/session/useSessionPageFlow.js.map +1 -1
  180. package/session/useSessionPanel.d.ts +120 -0
  181. package/session/useSessionPanel.d.ts.map +1 -0
  182. package/session/useSessionPanel.js +130 -0
  183. package/session/useSessionPanel.js.map +1 -0
  184. package/session/useSessionRailViews.d.ts +45 -0
  185. package/session/useSessionRailViews.d.ts.map +1 -0
  186. package/session/useSessionRailViews.js +105 -0
  187. package/session/useSessionRailViews.js.map +1 -0
  188. package/src/composer/SessionComposer.tsx +52 -10
  189. package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
  190. package/src/execution/ArtifactContentRenderer.tsx +101 -24
  191. package/src/execution/FilePathContext.tsx +8 -1
  192. package/src/execution/FilePathLink.tsx +22 -6
  193. package/src/execution/MessageEntry.tsx +111 -1
  194. package/src/execution/MessageThread.tsx +291 -36
  195. package/src/execution/PlanArtifactCard.tsx +63 -31
  196. package/src/execution/PlanDocumentMessage.tsx +96 -0
  197. package/src/execution/PlanStreamingCard.tsx +137 -0
  198. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
  199. package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
  200. package/src/execution/__tests__/MessageThread.test.tsx +101 -0
  201. package/src/execution/__tests__/PlanArtifactCard.test.tsx +66 -12
  202. package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
  203. package/src/execution/__tests__/PlanStreamingCard.test.tsx +49 -0
  204. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +504 -22
  205. package/src/execution/__tests__/message-entry.test.tsx +48 -0
  206. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
  207. package/src/execution/index.ts +7 -1
  208. package/src/execution/useCreateAgentExecution.ts +16 -1
  209. package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
  210. package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
  211. package/src/github/decodeGitHubContent.ts +83 -0
  212. package/src/github/index.ts +1 -0
  213. package/src/github/useGitHubFileReader.ts +152 -0
  214. package/src/github/useGitHubTreeLister.ts +6 -2
  215. package/src/index.ts +63 -9
  216. package/src/internal/ResizableSplit.tsx +184 -51
  217. package/src/internal/VirtualizedThread.tsx +12 -4
  218. package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
  219. package/src/internal/__tests__/markdown-components.test.tsx +68 -0
  220. package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
  221. package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
  222. package/src/internal/file-icons/index.tsx +137 -0
  223. package/src/internal/file-tree/FileTreeNode.tsx +43 -3
  224. package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
  225. package/src/internal/markdown-components.tsx +116 -11
  226. package/src/{workflow → internal}/motion-preference.ts +7 -4
  227. package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
  228. package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
  229. package/src/internal/store/index.ts +94 -0
  230. package/src/internal/store/workspace-editors-store.ts +315 -0
  231. package/src/internal/store/workspace-file-selection-store.ts +88 -0
  232. package/src/internal/useRevealLine.ts +81 -0
  233. package/src/library/__tests__/detect-plan-artifact.test.ts +48 -0
  234. package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
  235. package/src/library/detect-plan-artifact.ts +35 -0
  236. package/src/library/detect-streaming-plan.ts +108 -0
  237. package/src/library/index.ts +5 -0
  238. package/src/session/NewSessionViewer.tsx +123 -55
  239. package/src/session/PlanEditor.tsx +361 -0
  240. package/src/session/PlanStreamingDocument.tsx +97 -0
  241. package/src/session/SessionPanelChip.tsx +103 -0
  242. package/src/session/SessionViewer.tsx +617 -90
  243. package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
  244. package/src/session/__tests__/PlanEditor.test.tsx +217 -0
  245. package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
  246. package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
  247. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +9 -4
  248. package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
  249. package/src/session/__tests__/audienceWiring.test.tsx +55 -19
  250. package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
  251. package/src/session/__tests__/usePlanDraft.test.ts +102 -0
  252. package/src/session/__tests__/useSessionPanel.test.ts +298 -0
  253. package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
  254. package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
  255. package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
  256. package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
  257. package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
  258. package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +35 -1
  259. package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
  260. package/src/session/{inspector → facets}/index.ts +0 -16
  261. package/src/session/index.ts +30 -13
  262. package/src/session/plan-document.ts +19 -0
  263. package/src/session/useOpenFileChange.ts +53 -0
  264. package/src/session/usePlanDraft.ts +109 -0
  265. package/src/session/useSessionConversation.ts +7 -0
  266. package/src/session/useSessionPageFlow.ts +1 -0
  267. package/src/session/useSessionPanel.ts +288 -0
  268. package/src/session/useSessionRailViews.tsx +200 -0
  269. package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
  270. package/src/workflow/index.ts +1 -1
  271. package/src/workflow/useFollowExecution.ts +1 -1
  272. package/src/workspace/EditorTabs.tsx +145 -0
  273. package/src/workspace/ExplorerTree.tsx +247 -0
  274. package/src/workspace/FileViewer.tsx +516 -0
  275. package/src/workspace/WorkspaceContentSearch.tsx +478 -0
  276. package/src/workspace/WorkspaceContentSearcher.ts +70 -0
  277. package/src/workspace/WorkspaceFileLister.ts +11 -0
  278. package/src/workspace/WorkspaceFileReader.ts +57 -0
  279. package/src/workspace/WorkspaceFileSearch.tsx +429 -0
  280. package/src/workspace/WorkspaceSurface.tsx +781 -0
  281. package/src/workspace/__tests__/EditorTabs.test.tsx +100 -0
  282. package/src/workspace/__tests__/ExplorerTree.test.tsx +152 -0
  283. package/src/workspace/__tests__/FileViewer.test.tsx +421 -0
  284. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +196 -0
  285. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +206 -0
  286. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +348 -0
  287. package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
  288. package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
  289. package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
  290. package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
  291. package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
  292. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
  293. package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
  294. package/src/workspace/findChangeForSelection.ts +61 -0
  295. package/src/workspace/index.ts +56 -2
  296. package/src/workspace/matchContentPreview.ts +51 -0
  297. package/src/workspace/matchWorkspaceFiles.ts +86 -0
  298. package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
  299. package/src/workspace/useWorkspaceContentSearch.ts +228 -0
  300. package/src/workspace/useWorkspaceFileContent.ts +129 -0
  301. package/src/workspace/useWorkspaceFileSearch.ts +184 -0
  302. package/src/workspace/useWorkspaceFiles.ts +27 -27
  303. package/src/workspace/workspaceListingCache.ts +93 -0
  304. package/styles.css +1 -1
  305. package/workflow/WorkflowExecutionGraph.js +1 -1
  306. package/workflow/WorkflowExecutionGraph.js.map +1 -1
  307. package/workflow/index.d.ts +1 -1
  308. package/workflow/index.d.ts.map +1 -1
  309. package/workflow/index.js +1 -1
  310. package/workflow/index.js.map +1 -1
  311. package/workflow/useFollowExecution.js +1 -1
  312. package/workflow/useFollowExecution.js.map +1 -1
  313. package/workspace/EditorTabs.d.ts +29 -0
  314. package/workspace/EditorTabs.d.ts.map +1 -0
  315. package/workspace/EditorTabs.js +66 -0
  316. package/workspace/EditorTabs.js.map +1 -0
  317. package/workspace/ExplorerTree.d.ts +32 -0
  318. package/workspace/ExplorerTree.d.ts.map +1 -0
  319. package/workspace/ExplorerTree.js +45 -0
  320. package/workspace/ExplorerTree.js.map +1 -0
  321. package/workspace/FileViewer.d.ts +93 -0
  322. package/workspace/FileViewer.d.ts.map +1 -0
  323. package/workspace/FileViewer.js +182 -0
  324. package/workspace/FileViewer.js.map +1 -0
  325. package/workspace/WorkspaceContentSearch.d.ts +41 -0
  326. package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
  327. package/workspace/WorkspaceContentSearch.js +195 -0
  328. package/workspace/WorkspaceContentSearch.js.map +1 -0
  329. package/workspace/WorkspaceContentSearcher.d.ts +59 -0
  330. package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
  331. package/workspace/WorkspaceContentSearcher.js +7 -0
  332. package/workspace/WorkspaceContentSearcher.js.map +1 -0
  333. package/workspace/WorkspaceFileLister.d.ts +11 -0
  334. package/workspace/WorkspaceFileLister.d.ts.map +1 -1
  335. package/workspace/WorkspaceFileReader.d.ts +48 -0
  336. package/workspace/WorkspaceFileReader.d.ts.map +1 -0
  337. package/workspace/WorkspaceFileReader.js +11 -0
  338. package/workspace/WorkspaceFileReader.js.map +1 -0
  339. package/workspace/WorkspaceFileSearch.d.ts +32 -0
  340. package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
  341. package/workspace/WorkspaceFileSearch.js +155 -0
  342. package/workspace/WorkspaceFileSearch.js.map +1 -0
  343. package/workspace/WorkspaceSurface.d.ts +151 -0
  344. package/workspace/WorkspaceSurface.d.ts.map +1 -0
  345. package/workspace/WorkspaceSurface.js +184 -0
  346. package/workspace/WorkspaceSurface.js.map +1 -0
  347. package/workspace/findChangeForSelection.d.ts +19 -0
  348. package/workspace/findChangeForSelection.d.ts.map +1 -0
  349. package/workspace/findChangeForSelection.js +46 -0
  350. package/workspace/findChangeForSelection.js.map +1 -0
  351. package/workspace/index.d.ts +24 -2
  352. package/workspace/index.d.ts.map +1 -1
  353. package/workspace/index.js +13 -1
  354. package/workspace/index.js.map +1 -1
  355. package/workspace/matchContentPreview.d.ts +21 -0
  356. package/workspace/matchContentPreview.d.ts.map +1 -0
  357. package/workspace/matchContentPreview.js +36 -0
  358. package/workspace/matchContentPreview.js.map +1 -0
  359. package/workspace/matchWorkspaceFiles.d.ts +30 -0
  360. package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
  361. package/workspace/matchWorkspaceFiles.js +58 -0
  362. package/workspace/matchWorkspaceFiles.js.map +1 -0
  363. package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
  364. package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
  365. package/workspace/resolveWorkspaceFileSelection.js +115 -0
  366. package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
  367. package/workspace/useWorkspaceContentSearch.d.ts +69 -0
  368. package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
  369. package/workspace/useWorkspaceContentSearch.js +154 -0
  370. package/workspace/useWorkspaceContentSearch.js.map +1 -0
  371. package/workspace/useWorkspaceFileContent.d.ts +73 -0
  372. package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
  373. package/workspace/useWorkspaceFileContent.js +57 -0
  374. package/workspace/useWorkspaceFileContent.js.map +1 -0
  375. package/workspace/useWorkspaceFileSearch.d.ts +61 -0
  376. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
  377. package/workspace/useWorkspaceFileSearch.js +119 -0
  378. package/workspace/useWorkspaceFileSearch.js.map +1 -0
  379. package/workspace/useWorkspaceFiles.d.ts +13 -5
  380. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  381. package/workspace/useWorkspaceFiles.js +18 -19
  382. package/workspace/useWorkspaceFiles.js.map +1 -1
  383. package/workspace/workspaceListingCache.d.ts +47 -0
  384. package/workspace/workspaceListingCache.d.ts.map +1 -0
  385. package/workspace/workspaceListingCache.js +55 -0
  386. package/workspace/workspaceListingCache.js.map +1 -0
  387. package/session/inspector/ArtifactsTab.d.ts.map +0 -1
  388. package/session/inspector/ArtifactsTab.js.map +0 -1
  389. package/session/inspector/ChangesTab.d.ts.map +0 -1
  390. package/session/inspector/ChangesTab.js.map +0 -1
  391. package/session/inspector/InspectTab.d.ts.map +0 -1
  392. package/session/inspector/InspectTab.js.map +0 -1
  393. package/session/inspector/SessionInspector.d.ts +0 -64
  394. package/session/inspector/SessionInspector.d.ts.map +0 -1
  395. package/session/inspector/SessionInspector.js +0 -53
  396. package/session/inspector/SessionInspector.js.map +0 -1
  397. package/session/inspector/SetupTab.d.ts.map +0 -1
  398. package/session/inspector/SetupTab.js.map +0 -1
  399. package/session/inspector/UsageTab.d.ts.map +0 -1
  400. package/session/inspector/UsageTab.js.map +0 -1
  401. package/session/inspector/WorkspaceTab.d.ts +0 -35
  402. package/session/inspector/WorkspaceTab.d.ts.map +0 -1
  403. package/session/inspector/WorkspaceTab.js +0 -75
  404. package/session/inspector/WorkspaceTab.js.map +0 -1
  405. package/session/inspector/index.d.ts.map +0 -1
  406. package/session/inspector/index.js.map +0 -1
  407. package/session/inspector/useSessionInspector.d.ts +0 -49
  408. package/session/inspector/useSessionInspector.d.ts.map +0 -1
  409. package/session/inspector/useSessionInspector.js +0 -122
  410. package/session/inspector/useSessionInspector.js.map +0 -1
  411. package/src/session/inspector/SessionInspector.tsx +0 -162
  412. package/src/session/inspector/WorkspaceTab.tsx +0 -245
  413. package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
  414. package/src/session/inspector/useSessionInspector.ts +0 -178
  415. package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
  416. package/workflow/motion-preference.d.ts.map +0 -1
  417. package/workflow/motion-preference.js.map +0 -1
  418. package/workspace/WorkspaceEntryFiles.d.ts +0 -22
  419. package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
  420. package/workspace/WorkspaceEntryFiles.js +0 -58
  421. package/workspace/WorkspaceEntryFiles.js.map +0 -1
  422. /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
  423. /package/session/{inspector → facets}/InspectTab.js +0 -0
  424. /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
  425. /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
  426. /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
@@ -0,0 +1,478 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type KeyboardEvent,
11
+ type ReactNode,
12
+ } from "react";
13
+ import { cn } from "@stigmer/theme";
14
+ import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
15
+ import type {
16
+ WorkspaceContentMatch,
17
+ WorkspaceContentSearcher,
18
+ } from "./WorkspaceContentSearcher.js";
19
+ import type { OpenFileOptions } from "../internal/store/index.js";
20
+ import type { SelectedWorkspaceFile } from "../internal/store/workspace-file-selection-store.js";
21
+ import {
22
+ useWorkspaceContentSearch,
23
+ type WorkspaceContentSearchGroup,
24
+ } from "./useWorkspaceContentSearch.js";
25
+ import { findHighlightRanges } from "./matchContentPreview.js";
26
+
27
+ /** Cap on rendered line rows — keeps the DOM bounded on huge result sets. */
28
+ const MAX_RESULTS = 200;
29
+
30
+ /**
31
+ * Minimum query length before searching. Kept in sync with the hook by being
32
+ * passed in explicitly, and reused for the sub-min-length hint copy.
33
+ */
34
+ const MIN_QUERY_LENGTH = 2;
35
+
36
+ /** Props for {@link WorkspaceContentSearch}. */
37
+ export interface WorkspaceContentSearchProps {
38
+ /** All workspace entries to search across. */
39
+ readonly entries: readonly WorkspaceEntry[];
40
+ /** Platform-injected content searcher. `undefined` → content search is unavailable. */
41
+ readonly searcher: WorkspaceContentSearcher | undefined;
42
+ /**
43
+ * Opens a result's file in the viewer, jumping to the matched line. The
44
+ * `options.line` carries the hit's 1-based line so the viewer scrolls to and
45
+ * highlights it (DR-1/DR-2).
46
+ */
47
+ readonly onOpenFile: (
48
+ entryId: string,
49
+ path: string,
50
+ options?: OpenFileOptions,
51
+ ) => void;
52
+ /** The file currently open in the viewer, highlighted in the results. */
53
+ readonly selectedFile?: SelectedWorkspaceFile | null;
54
+ /** Additional CSS classes for the root element. */
55
+ readonly className?: string;
56
+ }
57
+
58
+ /** A rendered line hit, carrying its owning entry and flat index for keyboard nav. */
59
+ interface FlatResult {
60
+ readonly entryId: string;
61
+ readonly match: WorkspaceContentMatch;
62
+ }
63
+
64
+ /**
65
+ * Workspace-wide content (text) search surface.
66
+ *
67
+ * Greps file contents across every workspace entry (via
68
+ * {@link useWorkspaceContentSearch}) and opens a hit's file in the viewer
69
+ * through `onOpenFile` — the same seam a file-tree click and filename search
70
+ * use. Results are a VS Code-style `file → line` layout: each matched file is a
71
+ * header, each hit a line row with a line-number gutter and a highlighted
72
+ * preview. Filename search lives in the sibling {@link WorkspaceFileSearch}; a
73
+ * `Name | Text` toggle in the surface switches between them (DD-09).
74
+ *
75
+ * Keyboard/a11y mirrors the filename sibling (and `McpServerPicker`): the input
76
+ * is a `role="combobox"` driving `aria-activedescendant`; results are a
77
+ * `role="listbox"` of `role="option"` line rows; ArrowUp/Down move a virtual
78
+ * focus over the flat line-hit index (across files and entries) with Enter to
79
+ * open — focus stays in the input. All visual properties flow through `--stgm-*`
80
+ * tokens (DD-005).
81
+ */
82
+ export function WorkspaceContentSearch({
83
+ entries,
84
+ searcher,
85
+ onOpenFile,
86
+ selectedFile,
87
+ className,
88
+ }: WorkspaceContentSearchProps) {
89
+ const [query, setQuery] = useState("");
90
+ const [focusIndex, setFocusIndex] = useState(-1);
91
+
92
+ const inputRef = useRef<HTMLInputElement>(null);
93
+ const listboxId = useId();
94
+ const optionId = useCallback(
95
+ (index: number) => `${listboxId}-opt-${index}`,
96
+ [listboxId],
97
+ );
98
+
99
+ const { groups, totalMatches, isLoading, isRefetching, isUnsupported } =
100
+ useWorkspaceContentSearch({
101
+ entries,
102
+ searcher,
103
+ query,
104
+ minLength: MIN_QUERY_LENGTH,
105
+ });
106
+
107
+ // Flat, capped view of every rendered line hit — the index space the keyboard
108
+ // and `aria-activedescendant` share with the grouped render below.
109
+ const flatResults = useMemo<readonly FlatResult[]>(() => {
110
+ const flat: FlatResult[] = [];
111
+ for (const group of groups) {
112
+ for (const match of group.matches) {
113
+ if (flat.length >= MAX_RESULTS) return flat;
114
+ flat.push({ entryId: group.entry.id, match });
115
+ }
116
+ }
117
+ return flat;
118
+ }, [groups]);
119
+
120
+ // A new query invalidates the previous virtual focus.
121
+ useEffect(() => {
122
+ setFocusIndex(-1);
123
+ }, [query]);
124
+
125
+ // Autofocus on mount — the user switched into Text search intending to type.
126
+ useEffect(() => {
127
+ inputRef.current?.focus();
128
+ }, []);
129
+
130
+ // Keep the virtually-focused option in view (no-op where unsupported, e.g. tests).
131
+ useEffect(() => {
132
+ if (focusIndex < 0) return;
133
+ const el =
134
+ typeof document !== "undefined"
135
+ ? document.getElementById(optionId(focusIndex))
136
+ : null;
137
+ el?.scrollIntoView?.({ block: "nearest" });
138
+ }, [focusIndex, optionId]);
139
+
140
+ const handleKeyDown = useCallback(
141
+ (e: KeyboardEvent<HTMLInputElement>) => {
142
+ if (e.key === "ArrowDown") {
143
+ e.preventDefault();
144
+ setFocusIndex((prev) => (prev < flatResults.length - 1 ? prev + 1 : prev));
145
+ } else if (e.key === "ArrowUp") {
146
+ e.preventDefault();
147
+ setFocusIndex((prev) => (prev > 0 ? prev - 1 : -1));
148
+ } else if (e.key === "Enter") {
149
+ e.preventDefault();
150
+ const target = flatResults[focusIndex];
151
+ if (target)
152
+ onOpenFile(target.entryId, target.match.path, {
153
+ line: target.match.line,
154
+ });
155
+ }
156
+ },
157
+ [flatResults, focusIndex, onOpenFile],
158
+ );
159
+
160
+ const trimmed = query.trim();
161
+ const belowMinLength = trimmed.length > 0 && trimmed.length < MIN_QUERY_LENGTH;
162
+ const showEntryHeaders = groups.length > 1;
163
+
164
+ return (
165
+ <div className={cn("flex h-full flex-col", className)}>
166
+ <div className="flex items-center gap-1.5 border-b border-border px-2 py-1.5">
167
+ <SearchIcon />
168
+ <input
169
+ ref={inputRef}
170
+ type="text"
171
+ role="combobox"
172
+ aria-expanded
173
+ aria-controls={listboxId}
174
+ aria-activedescendant={focusIndex >= 0 ? optionId(focusIndex) : undefined}
175
+ value={query}
176
+ onChange={(e) => setQuery(e.target.value)}
177
+ onKeyDown={handleKeyDown}
178
+ placeholder="Search text in files…"
179
+ disabled={isUnsupported}
180
+ className={cn(
181
+ "min-w-0 flex-1 bg-transparent text-xs text-foreground",
182
+ "placeholder:text-muted-foreground",
183
+ "focus-visible:outline-none disabled:cursor-not-allowed",
184
+ )}
185
+ aria-label="Search text in workspace files"
186
+ />
187
+ {isRefetching && <Spinner />}
188
+ {trimmed.length > 0 && (
189
+ <button
190
+ type="button"
191
+ onClick={() => {
192
+ setQuery("");
193
+ inputRef.current?.focus();
194
+ }}
195
+ className="shrink-0 text-muted-foreground transition-colors hover:text-foreground"
196
+ aria-label="Clear search"
197
+ >
198
+ <ClearIcon />
199
+ </button>
200
+ )}
201
+ </div>
202
+
203
+ <div className="min-h-0 flex-1 overflow-y-auto">
204
+ {isUnsupported ? (
205
+ <MessageState>Text search isn&rsquo;t available here.</MessageState>
206
+ ) : trimmed.length === 0 ? (
207
+ <MessageState>Search file contents across the whole workspace.</MessageState>
208
+ ) : belowMinLength ? (
209
+ <MessageState>
210
+ Type at least {MIN_QUERY_LENGTH} characters to search.
211
+ </MessageState>
212
+ ) : isLoading ? (
213
+ <MessageState>Searching…</MessageState>
214
+ ) : groups.length === 0 ? (
215
+ <MessageState>No files containing &ldquo;{trimmed}&rdquo;.</MessageState>
216
+ ) : (
217
+ <>
218
+ <ul id={listboxId} role="listbox" aria-label="Search results" className="py-0.5">
219
+ {renderRows({
220
+ groups,
221
+ query: trimmed,
222
+ showEntryHeaders,
223
+ selectedFile: selectedFile ?? null,
224
+ focusIndex,
225
+ optionId,
226
+ onOpenFile,
227
+ })}
228
+ </ul>
229
+ {totalMatches > flatResults.length && (
230
+ <p className="border-t border-border px-3 py-1.5 text-[0.65rem] text-muted-foreground">
231
+ Showing the first {flatResults.length} of {totalMatches} matches — refine
232
+ your search to narrow the list.
233
+ </p>
234
+ )}
235
+ </>
236
+ )}
237
+ </div>
238
+ </div>
239
+ );
240
+ }
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Result rows — a single pass that keeps the flat line-hit index aligned with
244
+ // `flatResults` (and thus keyboard focus / aria-activedescendant), rendering a
245
+ // VS Code-style entry → file → line hierarchy. Backend order (path, then line)
246
+ // makes each file's hits contiguous, so file headers fall out of a path change.
247
+ // ---------------------------------------------------------------------------
248
+
249
+ function renderRows({
250
+ groups,
251
+ query,
252
+ showEntryHeaders,
253
+ selectedFile,
254
+ focusIndex,
255
+ optionId,
256
+ onOpenFile,
257
+ }: {
258
+ readonly groups: readonly WorkspaceContentSearchGroup[];
259
+ readonly query: string;
260
+ readonly showEntryHeaders: boolean;
261
+ readonly selectedFile: SelectedWorkspaceFile | null;
262
+ readonly focusIndex: number;
263
+ readonly optionId: (index: number) => string;
264
+ readonly onOpenFile: (
265
+ entryId: string,
266
+ path: string,
267
+ options?: OpenFileOptions,
268
+ ) => void;
269
+ }) {
270
+ const rows: ReactNode[] = [];
271
+ let matchIndex = 0;
272
+
273
+ for (const group of groups) {
274
+ if (showEntryHeaders) {
275
+ rows.push(<EntryHeader key={`entry:${group.entry.id}`} name={group.entry.name} />);
276
+ }
277
+
278
+ let currentPath: string | null = null;
279
+ for (const match of group.matches) {
280
+ if (matchIndex >= MAX_RESULTS) break;
281
+
282
+ if (match.path !== currentPath) {
283
+ currentPath = match.path;
284
+ rows.push(<FileHeader key={`file:${group.entry.id}:${match.path}`} path={match.path} />);
285
+ }
286
+
287
+ const index = matchIndex++;
288
+ const isOpen =
289
+ selectedFile?.entryId === group.entry.id && selectedFile.path === match.path;
290
+ rows.push(
291
+ <LineRow
292
+ key={`line:${group.entry.id}:${match.path}:${match.line}`}
293
+ id={optionId(index)}
294
+ match={match}
295
+ query={query}
296
+ isFocused={index === focusIndex}
297
+ isOpen={isOpen}
298
+ onOpen={() => onOpenFile(group.entry.id, match.path, { line: match.line })}
299
+ />,
300
+ );
301
+ }
302
+
303
+ if (group.truncated) {
304
+ rows.push(
305
+ <GroupNotice key={`t:${group.entry.id}`}>
306
+ Partial results — more matches exist than shown for this workspace.
307
+ </GroupNotice>,
308
+ );
309
+ }
310
+ if (group.error) {
311
+ rows.push(
312
+ <GroupNotice key={`e:${group.entry.id}`} isError>
313
+ Couldn&rsquo;t search: {group.error.message}
314
+ </GroupNotice>,
315
+ );
316
+ }
317
+
318
+ if (matchIndex >= MAX_RESULTS) break;
319
+ }
320
+
321
+ return rows;
322
+ }
323
+
324
+ function LineRow({
325
+ id,
326
+ match,
327
+ query,
328
+ isFocused,
329
+ isOpen,
330
+ onOpen,
331
+ }: {
332
+ readonly id: string;
333
+ readonly match: WorkspaceContentMatch;
334
+ readonly query: string;
335
+ readonly isFocused: boolean;
336
+ readonly isOpen: boolean;
337
+ readonly onOpen: () => void;
338
+ }) {
339
+ return (
340
+ <li id={id} role="option" aria-selected={isFocused}>
341
+ <button
342
+ type="button"
343
+ onClick={onOpen}
344
+ tabIndex={-1}
345
+ aria-current={isOpen ? "true" : undefined}
346
+ className={cn(
347
+ "flex w-full items-baseline gap-2 py-0.5 pl-6 pr-3 text-left text-xs transition-colors",
348
+ isFocused && "bg-muted",
349
+ isOpen && "bg-muted",
350
+ !isFocused && !isOpen && "hover:bg-muted",
351
+ )}
352
+ >
353
+ <span className="w-8 shrink-0 text-right font-mono text-[0.65rem] tabular-nums text-muted-foreground-subtle">
354
+ {match.line}
355
+ </span>
356
+ <span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
357
+ <HighlightedPreview text={match.preview} query={query} />
358
+ </span>
359
+ </button>
360
+ </li>
361
+ );
362
+ }
363
+
364
+ /**
365
+ * Renders a preview line, emphasizing every case-insensitive occurrence of the
366
+ * query. Offsets are re-derived from the text (the backend carries no column),
367
+ * so highlighting is consistent with what the user sees.
368
+ */
369
+ function HighlightedPreview({
370
+ text,
371
+ query,
372
+ }: {
373
+ readonly text: string;
374
+ readonly query: string;
375
+ }) {
376
+ const ranges = findHighlightRanges(text, query);
377
+ if (ranges.length === 0) return <>{text}</>;
378
+
379
+ const parts: ReactNode[] = [];
380
+ let cursor = 0;
381
+ ranges.forEach((range, i) => {
382
+ if (range.start > cursor) parts.push(text.slice(cursor, range.start));
383
+ parts.push(
384
+ <span key={i} className="font-semibold text-foreground">
385
+ {text.slice(range.start, range.end)}
386
+ </span>,
387
+ );
388
+ cursor = range.end;
389
+ });
390
+ if (cursor < text.length) parts.push(text.slice(cursor));
391
+
392
+ return <>{parts}</>;
393
+ }
394
+
395
+ function EntryHeader({ name }: { readonly name: string }) {
396
+ return (
397
+ <li
398
+ role="presentation"
399
+ className="truncate px-3 pb-0.5 pt-2 text-[0.6rem] font-medium uppercase tracking-wide text-muted-foreground"
400
+ >
401
+ {name}
402
+ </li>
403
+ );
404
+ }
405
+
406
+ function FileHeader({ path }: { readonly path: string }) {
407
+ const lastSlash = path.lastIndexOf("/");
408
+ const basename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
409
+ const dir = lastSlash >= 0 ? path.slice(0, lastSlash) : "";
410
+
411
+ return (
412
+ <li
413
+ role="presentation"
414
+ className="flex items-baseline gap-1.5 px-3 pb-0.5 pt-1.5 text-xs"
415
+ >
416
+ <span className="shrink-0 font-medium text-foreground">{basename}</span>
417
+ {dir && <span className="truncate text-[0.65rem] text-muted-foreground">{dir}</span>}
418
+ </li>
419
+ );
420
+ }
421
+
422
+ function GroupNotice({
423
+ children,
424
+ isError,
425
+ }: {
426
+ readonly children: ReactNode;
427
+ readonly isError?: boolean;
428
+ }) {
429
+ return (
430
+ <li
431
+ role="presentation"
432
+ className={cn(
433
+ "px-3 py-1 text-[0.65rem]",
434
+ isError ? "text-destructive" : "text-muted-foreground",
435
+ )}
436
+ >
437
+ {children}
438
+ </li>
439
+ );
440
+ }
441
+
442
+ function MessageState({ children }: { readonly children: ReactNode }) {
443
+ return (
444
+ <div className="flex h-full items-center justify-center p-8 text-center text-xs text-muted-foreground">
445
+ {children}
446
+ </div>
447
+ );
448
+ }
449
+
450
+ // ---------------------------------------------------------------------------
451
+ // Inline SVG icons (SDK independence — no lucide dependency)
452
+ // ---------------------------------------------------------------------------
453
+
454
+ function SearchIcon() {
455
+ return (
456
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="shrink-0 text-muted-foreground" aria-hidden="true">
457
+ <circle cx="5.25" cy="5.25" r="3.5" />
458
+ <path d="M7.75 7.75L10.5 10.5" />
459
+ </svg>
460
+ );
461
+ }
462
+
463
+ function ClearIcon() {
464
+ return (
465
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
466
+ <path d="M3 3L9 9M9 3L3 9" />
467
+ </svg>
468
+ );
469
+ }
470
+
471
+ function Spinner() {
472
+ return (
473
+ <span
474
+ aria-hidden="true"
475
+ className="h-3 w-3 shrink-0 animate-spin rounded-full border border-muted-foreground border-t-transparent"
476
+ />
477
+ );
478
+ }
@@ -0,0 +1,70 @@
1
+ // Content-search capability — the text-grep sibling of WorkspaceFileLister
2
+ // (which does filename/path search over a cached listing). Injected by the host
3
+ // (DD-004) so the SDK never learns how the grep runs: a native ripgrep-backed
4
+ // walker on desktop; `null` (unsupported) on web/git until a branch-accurate
5
+ // backend search exists (DD-09).
6
+
7
+ import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
8
+
9
+ /**
10
+ * A single content-search hit: a line in a workspace file whose text contains
11
+ * the query.
12
+ *
13
+ * Deliberately line-level and column-free. The consumer recomputes the
14
+ * highlight range from `preview` + query (a case-insensitive substring), so a
15
+ * byte/column offset would be an unused field until jump-to-line ships — at
16
+ * which point `line` is the anchor. `preview` is guaranteed to contain the
17
+ * match even for long lines (the backend windows it around the first hit).
18
+ */
19
+ export interface WorkspaceContentMatch {
20
+ /** Repo-relative (git) or root-relative (local) path of the matched file. */
21
+ readonly path: string;
22
+ /** 1-based line number of the match within the file. */
23
+ readonly line: number;
24
+ /** The matched line's text, windowed so the match stays visible. */
25
+ readonly preview: string;
26
+ }
27
+
28
+ /**
29
+ * The result of searching one workspace entry: its hits plus whether a cap
30
+ * stopped the search early.
31
+ *
32
+ * A result object (rather than a bare `WorkspaceContentMatch[]`) so the
33
+ * per-root `truncated` signal survives to the UI — mirroring how
34
+ * {@link WorkspaceFileReader} returns a
35
+ * `WorkspaceFileContent` object with its own `truncated` flag rather than raw
36
+ * text. `truncated` means "there are more matches than shown for this entry".
37
+ */
38
+ export interface WorkspaceContentSearchResult {
39
+ /** Ranked hits (backend order: path, then line). */
40
+ readonly matches: readonly WorkspaceContentMatch[];
41
+ /** `true` when a per-file or workspace cap stopped the search early. */
42
+ readonly truncated: boolean;
43
+ }
44
+
45
+ /**
46
+ * Platform-injected callback that searches file *contents* within one workspace
47
+ * entry for a case-insensitive substring.
48
+ *
49
+ * The text-grep sibling of {@link WorkspaceFileLister}:
50
+ * the SDK calls it (per entry, debounced) while the user types in the Search
51
+ * pane's Text mode; the platform decides *how* the grep runs.
52
+ *
53
+ * Two distinct outcomes — do not conflate them:
54
+ * - Returns `null` **only** when content search is unsupported for this entry
55
+ * (a git entry on desktop, any entry on web until a backend search exists).
56
+ * This is the honest "unavailable here" state, mirroring the lister's null
57
+ * contract — the whole workspace being `null` disables Text search.
58
+ * - **Throws** on a genuine failure (unreadable root, backend error). Consumers
59
+ * isolate this per entry so one failing root never blanks the rest.
60
+ *
61
+ * Implementations:
62
+ * - **Desktop:** Tauri `search_workspace_content` (ripgrep `ignore` walker +
63
+ * `grep-searcher`, respecting `.gitignore`, skipping binary/oversized files).
64
+ * - **Web:** `undefined`/`null` — GitHub code search is default-branch-only and
65
+ * would disagree with the branch the viewer reads (DD-09).
66
+ */
67
+ export type WorkspaceContentSearcher = (
68
+ entry: WorkspaceEntry,
69
+ query: string,
70
+ ) => Promise<WorkspaceContentSearchResult | null>;
@@ -12,6 +12,17 @@ export interface WorkspaceFileEntry {
12
12
  readonly path: string;
13
13
  /** Whether this entry is a directory. */
14
14
  readonly isDirectory: boolean;
15
+ /**
16
+ * Marks this entry as an advisory notice rather than an openable file — for
17
+ * example a "listing truncated" message when a repository exceeds the
18
+ * backend's entry limit. When `true`, `path` carries the human-readable
19
+ * message. Consumers must not treat a notice as a real file: it is stripped
20
+ * from the openable listing (never a tree leaf, never a search hit) and is
21
+ * surfaced instead as a single `truncated` banner (see `workspaceListingCache`).
22
+ *
23
+ * Optional and additive: listers that never truncate simply omit it.
24
+ */
25
+ readonly notice?: true;
15
26
  }
16
27
 
17
28
  /**
@@ -0,0 +1,57 @@
1
+ // Content-fetch capability — the byte-reading sibling of WorkspaceFileLister.
2
+ // Domain: workspace. Injected by the host (DD-004) so the SDK never learns how
3
+ // bytes are fetched: GitHub Contents/blob on web, Tauri fs on desktop.
4
+
5
+ import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
6
+
7
+ /**
8
+ * Shared soft cap for a single file read, enforced identically on web and
9
+ * desktop so both substrates truncate at the same boundary. Files larger than
10
+ * this return their first {@link MAX_WORKSPACE_FILE_READ_BYTES} bytes in `text`
11
+ * with `truncated: true`; `size` still reports the full file size.
12
+ */
13
+ export const MAX_WORKSPACE_FILE_READ_BYTES = 1_048_576; // 1 MiB
14
+
15
+ /**
16
+ * Decoded content (plus metadata) for a single workspace file.
17
+ *
18
+ * `text` is `null` whenever the bytes are not displayable as UTF-8 — either
19
+ * binary (`isBinary`) or an undecodable byte sequence (`encoding: "unknown"`).
20
+ * The renderer uses `isBinary`/`text === null` for its fallback state.
21
+ */
22
+ export interface WorkspaceFileContent {
23
+ /** Decoded UTF-8 text, or `null` when binary or undecodable. */
24
+ readonly text: string | null;
25
+ /** Whether the bytes were detected as binary (NUL-byte heuristic). */
26
+ readonly isBinary: boolean;
27
+ /** Full file size in bytes — independent of truncation. */
28
+ readonly size: number;
29
+ /** How the backend delivered the bytes. `"none"` is GitHub's >1 MB signal. */
30
+ readonly encoding: "utf-8" | "base64" | "none" | "unknown";
31
+ /** `true` when `text` holds only the first {@link MAX_WORKSPACE_FILE_READ_BYTES} bytes. */
32
+ readonly truncated?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Platform-injected callback that reads the content of a single workspace file.
37
+ *
38
+ * The byte-reading sibling of {@link WorkspaceFileLister}:
39
+ * the SDK calls it when a user opens a file; the platform decides *how* the
40
+ * bytes are fetched (GitHub on web, Tauri on desktop).
41
+ *
42
+ * Two distinct failure axes — do not conflate them:
43
+ * - Returns `null` **only** when the substrate is unsupported for this entry
44
+ * (git-on-desktop, local-on-web, missing token, non-GitHub URL). This is the
45
+ * honest "unavailable here" state, mirroring the lister's null contract.
46
+ * - **Throws** on a genuine failure (404, network error, 5xx, unreadable file,
47
+ * a directory path). Consumers catch this into a distinct error state — a
48
+ * clicked file that 404s is "failed to load," not "unsupported here."
49
+ *
50
+ * `path` is repo-relative for git entries and root-relative for local entries —
51
+ * exactly the strings the matching `WorkspaceFileLister` emits, so a file-tree
52
+ * node path can be handed straight to the reader with no transformation.
53
+ */
54
+ export type WorkspaceFileReader = (
55
+ entry: WorkspaceEntry,
56
+ path: string,
57
+ ) => Promise<WorkspaceFileContent | null>;