@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.
- package/composer/SessionComposer.d.ts +24 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +19 -8
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactContentRenderer.d.ts +11 -1
- package/execution/ArtifactContentRenderer.d.ts.map +1 -1
- package/execution/ArtifactContentRenderer.js +39 -15
- package/execution/ArtifactContentRenderer.js.map +1 -1
- package/execution/FilePathContext.d.ts +8 -1
- package/execution/FilePathContext.d.ts.map +1 -1
- package/execution/FilePathContext.js.map +1 -1
- package/execution/FilePathLink.d.ts.map +1 -1
- package/execution/FilePathLink.js +21 -6
- package/execution/FilePathLink.js.map +1 -1
- package/execution/MessageEntry.d.ts +29 -0
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +29 -5
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +76 -13
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +178 -24
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +27 -18
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +13 -23
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanDocumentMessage.d.ts +32 -0
- package/execution/PlanDocumentMessage.d.ts.map +1 -0
- package/execution/PlanDocumentMessage.js +37 -0
- package/execution/PlanDocumentMessage.js.map +1 -0
- package/execution/PlanStreamingCard.d.ts +45 -0
- package/execution/PlanStreamingCard.d.ts.map +1 -0
- package/execution/PlanStreamingCard.js +35 -0
- package/execution/PlanStreamingCard.js.map +1 -0
- package/execution/index.d.ts +5 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +2 -0
- package/execution/index.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +10 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -1
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/github/decodeGitHubContent.d.ts +29 -0
- package/github/decodeGitHubContent.d.ts.map +1 -0
- package/github/decodeGitHubContent.js +72 -0
- package/github/decodeGitHubContent.js.map +1 -0
- package/github/index.d.ts +1 -0
- package/github/index.d.ts.map +1 -1
- package/github/index.js +1 -0
- package/github/index.js.map +1 -1
- package/github/useGitHubFileReader.d.ts +26 -0
- package/github/useGitHubFileReader.d.ts.map +1 -0
- package/github/useGitHubFileReader.js +106 -0
- package/github/useGitHubFileReader.js.map +1 -0
- package/github/useGitHubTreeLister.d.ts.map +1 -1
- package/github/useGitHubTreeLister.js +6 -2
- package/github/useGitHubTreeLister.js.map +1 -1
- package/index.d.ts +9 -9
- package/index.d.ts.map +1 -1
- package/index.js +5 -5
- package/index.js.map +1 -1
- package/internal/ResizableSplit.d.ts +72 -22
- package/internal/ResizableSplit.d.ts.map +1 -1
- package/internal/ResizableSplit.js +96 -38
- package/internal/ResizableSplit.js.map +1 -1
- package/internal/VirtualizedThread.d.ts +5 -3
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +6 -4
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/file-icons/index.d.ts +17 -0
- package/internal/file-icons/index.d.ts.map +1 -0
- package/internal/file-icons/index.js +60 -0
- package/internal/file-icons/index.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts +18 -1
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +11 -2
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts +38 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +86 -11
- package/internal/markdown-components.js.map +1 -1
- package/{workflow → internal}/motion-preference.d.ts +7 -4
- package/internal/motion-preference.d.ts.map +1 -0
- package/{workflow → internal}/motion-preference.js +7 -4
- package/internal/motion-preference.js.map +1 -0
- package/internal/store/index.d.ts +33 -0
- package/internal/store/index.d.ts.map +1 -1
- package/internal/store/index.js +59 -0
- package/internal/store/index.js.map +1 -1
- package/internal/store/workspace-editors-store.d.ts +145 -0
- package/internal/store/workspace-editors-store.d.ts.map +1 -0
- package/internal/store/workspace-editors-store.js +208 -0
- package/internal/store/workspace-editors-store.js.map +1 -0
- package/internal/store/workspace-file-selection-store.d.ts +38 -0
- package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
- package/internal/store/workspace-file-selection-store.js +62 -0
- package/internal/store/workspace-file-selection-store.js.map +1 -0
- package/internal/useRevealLine.d.ts +49 -0
- package/internal/useRevealLine.d.ts.map +1 -0
- package/internal/useRevealLine.js +43 -0
- package/internal/useRevealLine.js.map +1 -0
- package/library/detect-plan-artifact.d.ts +21 -0
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +20 -0
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/detect-streaming-plan.d.ts +45 -0
- package/library/detect-streaming-plan.d.ts.map +1 -0
- package/library/detect-streaming-plan.js +83 -0
- package/library/detect-streaming-plan.js.map +1 -0
- package/library/index.d.ts +4 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +2 -1
- package/library/index.js.map +1 -1
- package/package.json +4 -4
- package/session/NewSessionViewer.d.ts +19 -4
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +52 -25
- package/session/NewSessionViewer.js.map +1 -1
- package/session/PlanEditor.d.ts +55 -0
- package/session/PlanEditor.d.ts.map +1 -0
- package/session/PlanEditor.js +88 -0
- package/session/PlanEditor.js.map +1 -0
- package/session/PlanStreamingDocument.d.ts +30 -0
- package/session/PlanStreamingDocument.d.ts.map +1 -0
- package/session/PlanStreamingDocument.js +37 -0
- package/session/PlanStreamingDocument.js.map +1 -0
- package/session/SessionPanelChip.d.ts +26 -0
- package/session/SessionPanelChip.d.ts.map +1 -0
- package/session/SessionPanelChip.js +40 -0
- package/session/SessionPanelChip.js.map +1 -0
- package/session/SessionViewer.d.ts +34 -7
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +315 -33
- package/session/SessionViewer.js.map +1 -1
- package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
- package/session/facets/ArtifactsTab.d.ts.map +1 -0
- package/session/{inspector → facets}/ArtifactsTab.js +8 -3
- package/session/facets/ArtifactsTab.js.map +1 -0
- package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
- package/session/facets/ChangesTab.d.ts.map +1 -0
- package/session/{inspector → facets}/ChangesTab.js +3 -3
- package/session/facets/ChangesTab.js.map +1 -0
- package/session/facets/InspectTab.d.ts.map +1 -0
- package/session/facets/InspectTab.js.map +1 -0
- package/session/{inspector → facets}/SetupTab.d.ts +11 -2
- package/session/facets/SetupTab.d.ts.map +1 -0
- package/session/{inspector → facets}/SetupTab.js +4 -3
- package/session/facets/SetupTab.js.map +1 -0
- package/session/{inspector → facets}/UsageTab.d.ts +1 -1
- package/session/facets/UsageTab.d.ts.map +1 -0
- package/session/{inspector → facets}/UsageTab.js +1 -1
- package/session/facets/UsageTab.js.map +1 -0
- package/session/{inspector → facets}/index.d.ts +0 -6
- package/session/facets/index.d.ts.map +1 -0
- package/session/{inspector → facets}/index.js +0 -3
- package/session/facets/index.js.map +1 -0
- package/session/index.d.ts +11 -2
- package/session/index.d.ts.map +1 -1
- package/session/index.js +15 -2
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +9 -0
- package/session/plan-document.d.ts.map +1 -0
- package/session/plan-document.js +17 -0
- package/session/plan-document.js.map +1 -0
- package/session/useOpenFileChange.d.ts +20 -0
- package/session/useOpenFileChange.d.ts.map +1 -0
- package/session/useOpenFileChange.js +31 -0
- package/session/useOpenFileChange.js.map +1 -0
- package/session/usePlanDraft.d.ts +52 -0
- package/session/usePlanDraft.d.ts.map +1 -0
- package/session/usePlanDraft.js +63 -0
- package/session/usePlanDraft.js.map +1 -0
- package/session/useSessionConversation.d.ts +6 -0
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +1 -0
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +120 -0
- package/session/useSessionPanel.d.ts.map +1 -0
- package/session/useSessionPanel.js +130 -0
- package/session/useSessionPanel.js.map +1 -0
- package/session/useSessionRailViews.d.ts +45 -0
- package/session/useSessionRailViews.d.ts.map +1 -0
- package/session/useSessionRailViews.js +105 -0
- package/session/useSessionRailViews.js.map +1 -0
- package/src/composer/SessionComposer.tsx +52 -10
- package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
- package/src/execution/ArtifactContentRenderer.tsx +101 -24
- package/src/execution/FilePathContext.tsx +8 -1
- package/src/execution/FilePathLink.tsx +22 -6
- package/src/execution/MessageEntry.tsx +111 -1
- package/src/execution/MessageThread.tsx +291 -36
- package/src/execution/PlanArtifactCard.tsx +63 -31
- package/src/execution/PlanDocumentMessage.tsx +96 -0
- package/src/execution/PlanStreamingCard.tsx +137 -0
- package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
- package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
- package/src/execution/__tests__/MessageThread.test.tsx +101 -0
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +66 -12
- package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +49 -0
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +504 -22
- package/src/execution/__tests__/message-entry.test.tsx +48 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
- package/src/execution/index.ts +7 -1
- package/src/execution/useCreateAgentExecution.ts +16 -1
- package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
- package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
- package/src/github/decodeGitHubContent.ts +83 -0
- package/src/github/index.ts +1 -0
- package/src/github/useGitHubFileReader.ts +152 -0
- package/src/github/useGitHubTreeLister.ts +6 -2
- package/src/index.ts +63 -9
- package/src/internal/ResizableSplit.tsx +184 -51
- package/src/internal/VirtualizedThread.tsx +12 -4
- package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
- package/src/internal/__tests__/markdown-components.test.tsx +68 -0
- package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
- package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
- package/src/internal/file-icons/index.tsx +137 -0
- package/src/internal/file-tree/FileTreeNode.tsx +43 -3
- package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
- package/src/internal/markdown-components.tsx +116 -11
- package/src/{workflow → internal}/motion-preference.ts +7 -4
- package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
- package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
- package/src/internal/store/index.ts +94 -0
- package/src/internal/store/workspace-editors-store.ts +315 -0
- package/src/internal/store/workspace-file-selection-store.ts +88 -0
- package/src/internal/useRevealLine.ts +81 -0
- package/src/library/__tests__/detect-plan-artifact.test.ts +48 -0
- package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
- package/src/library/detect-plan-artifact.ts +35 -0
- package/src/library/detect-streaming-plan.ts +108 -0
- package/src/library/index.ts +5 -0
- package/src/session/NewSessionViewer.tsx +123 -55
- package/src/session/PlanEditor.tsx +361 -0
- package/src/session/PlanStreamingDocument.tsx +97 -0
- package/src/session/SessionPanelChip.tsx +103 -0
- package/src/session/SessionViewer.tsx +617 -90
- package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
- package/src/session/__tests__/PlanEditor.test.tsx +217 -0
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
- package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +9 -4
- package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
- package/src/session/__tests__/audienceWiring.test.tsx +55 -19
- package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
- package/src/session/__tests__/usePlanDraft.test.ts +102 -0
- package/src/session/__tests__/useSessionPanel.test.ts +298 -0
- package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
- package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
- package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
- package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
- package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
- package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +35 -1
- package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
- package/src/session/{inspector → facets}/index.ts +0 -16
- package/src/session/index.ts +30 -13
- package/src/session/plan-document.ts +19 -0
- package/src/session/useOpenFileChange.ts +53 -0
- package/src/session/usePlanDraft.ts +109 -0
- package/src/session/useSessionConversation.ts +7 -0
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +288 -0
- package/src/session/useSessionRailViews.tsx +200 -0
- package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
- package/src/workflow/index.ts +1 -1
- package/src/workflow/useFollowExecution.ts +1 -1
- package/src/workspace/EditorTabs.tsx +145 -0
- package/src/workspace/ExplorerTree.tsx +247 -0
- package/src/workspace/FileViewer.tsx +516 -0
- package/src/workspace/WorkspaceContentSearch.tsx +478 -0
- package/src/workspace/WorkspaceContentSearcher.ts +70 -0
- package/src/workspace/WorkspaceFileLister.ts +11 -0
- package/src/workspace/WorkspaceFileReader.ts +57 -0
- package/src/workspace/WorkspaceFileSearch.tsx +429 -0
- package/src/workspace/WorkspaceSurface.tsx +781 -0
- package/src/workspace/__tests__/EditorTabs.test.tsx +100 -0
- package/src/workspace/__tests__/ExplorerTree.test.tsx +152 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +421 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +196 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +206 -0
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +348 -0
- package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
- package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
- package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
- package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
- package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
- package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
- package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
- package/src/workspace/findChangeForSelection.ts +61 -0
- package/src/workspace/index.ts +56 -2
- package/src/workspace/matchContentPreview.ts +51 -0
- package/src/workspace/matchWorkspaceFiles.ts +86 -0
- package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
- package/src/workspace/useWorkspaceContentSearch.ts +228 -0
- package/src/workspace/useWorkspaceFileContent.ts +129 -0
- package/src/workspace/useWorkspaceFileSearch.ts +184 -0
- package/src/workspace/useWorkspaceFiles.ts +27 -27
- package/src/workspace/workspaceListingCache.ts +93 -0
- package/styles.css +1 -1
- package/workflow/WorkflowExecutionGraph.js +1 -1
- package/workflow/WorkflowExecutionGraph.js.map +1 -1
- package/workflow/index.d.ts +1 -1
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +1 -1
- package/workflow/index.js.map +1 -1
- package/workflow/useFollowExecution.js +1 -1
- package/workflow/useFollowExecution.js.map +1 -1
- package/workspace/EditorTabs.d.ts +29 -0
- package/workspace/EditorTabs.d.ts.map +1 -0
- package/workspace/EditorTabs.js +66 -0
- package/workspace/EditorTabs.js.map +1 -0
- package/workspace/ExplorerTree.d.ts +32 -0
- package/workspace/ExplorerTree.d.ts.map +1 -0
- package/workspace/ExplorerTree.js +45 -0
- package/workspace/ExplorerTree.js.map +1 -0
- package/workspace/FileViewer.d.ts +93 -0
- package/workspace/FileViewer.d.ts.map +1 -0
- package/workspace/FileViewer.js +182 -0
- package/workspace/FileViewer.js.map +1 -0
- package/workspace/WorkspaceContentSearch.d.ts +41 -0
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
- package/workspace/WorkspaceContentSearch.js +195 -0
- package/workspace/WorkspaceContentSearch.js.map +1 -0
- package/workspace/WorkspaceContentSearcher.d.ts +59 -0
- package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
- package/workspace/WorkspaceContentSearcher.js +7 -0
- package/workspace/WorkspaceContentSearcher.js.map +1 -0
- package/workspace/WorkspaceFileLister.d.ts +11 -0
- package/workspace/WorkspaceFileLister.d.ts.map +1 -1
- package/workspace/WorkspaceFileReader.d.ts +48 -0
- package/workspace/WorkspaceFileReader.d.ts.map +1 -0
- package/workspace/WorkspaceFileReader.js +11 -0
- package/workspace/WorkspaceFileReader.js.map +1 -0
- package/workspace/WorkspaceFileSearch.d.ts +32 -0
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
- package/workspace/WorkspaceFileSearch.js +155 -0
- package/workspace/WorkspaceFileSearch.js.map +1 -0
- package/workspace/WorkspaceSurface.d.ts +151 -0
- package/workspace/WorkspaceSurface.d.ts.map +1 -0
- package/workspace/WorkspaceSurface.js +184 -0
- package/workspace/WorkspaceSurface.js.map +1 -0
- package/workspace/findChangeForSelection.d.ts +19 -0
- package/workspace/findChangeForSelection.d.ts.map +1 -0
- package/workspace/findChangeForSelection.js +46 -0
- package/workspace/findChangeForSelection.js.map +1 -0
- package/workspace/index.d.ts +24 -2
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +13 -1
- package/workspace/index.js.map +1 -1
- package/workspace/matchContentPreview.d.ts +21 -0
- package/workspace/matchContentPreview.d.ts.map +1 -0
- package/workspace/matchContentPreview.js +36 -0
- package/workspace/matchContentPreview.js.map +1 -0
- package/workspace/matchWorkspaceFiles.d.ts +30 -0
- package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
- package/workspace/matchWorkspaceFiles.js +58 -0
- package/workspace/matchWorkspaceFiles.js.map +1 -0
- package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
- package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
- package/workspace/resolveWorkspaceFileSelection.js +115 -0
- package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
- package/workspace/useWorkspaceContentSearch.d.ts +69 -0
- package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
- package/workspace/useWorkspaceContentSearch.js +154 -0
- package/workspace/useWorkspaceContentSearch.js.map +1 -0
- package/workspace/useWorkspaceFileContent.d.ts +73 -0
- package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
- package/workspace/useWorkspaceFileContent.js +57 -0
- package/workspace/useWorkspaceFileContent.js.map +1 -0
- package/workspace/useWorkspaceFileSearch.d.ts +61 -0
- package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
- package/workspace/useWorkspaceFileSearch.js +119 -0
- package/workspace/useWorkspaceFileSearch.js.map +1 -0
- package/workspace/useWorkspaceFiles.d.ts +13 -5
- package/workspace/useWorkspaceFiles.d.ts.map +1 -1
- package/workspace/useWorkspaceFiles.js +18 -19
- package/workspace/useWorkspaceFiles.js.map +1 -1
- package/workspace/workspaceListingCache.d.ts +47 -0
- package/workspace/workspaceListingCache.d.ts.map +1 -0
- package/workspace/workspaceListingCache.js +55 -0
- package/workspace/workspaceListingCache.js.map +1 -0
- package/session/inspector/ArtifactsTab.d.ts.map +0 -1
- package/session/inspector/ArtifactsTab.js.map +0 -1
- package/session/inspector/ChangesTab.d.ts.map +0 -1
- package/session/inspector/ChangesTab.js.map +0 -1
- package/session/inspector/InspectTab.d.ts.map +0 -1
- package/session/inspector/InspectTab.js.map +0 -1
- package/session/inspector/SessionInspector.d.ts +0 -64
- package/session/inspector/SessionInspector.d.ts.map +0 -1
- package/session/inspector/SessionInspector.js +0 -53
- package/session/inspector/SessionInspector.js.map +0 -1
- package/session/inspector/SetupTab.d.ts.map +0 -1
- package/session/inspector/SetupTab.js.map +0 -1
- package/session/inspector/UsageTab.d.ts.map +0 -1
- package/session/inspector/UsageTab.js.map +0 -1
- package/session/inspector/WorkspaceTab.d.ts +0 -35
- package/session/inspector/WorkspaceTab.d.ts.map +0 -1
- package/session/inspector/WorkspaceTab.js +0 -75
- package/session/inspector/WorkspaceTab.js.map +0 -1
- package/session/inspector/index.d.ts.map +0 -1
- package/session/inspector/index.js.map +0 -1
- package/session/inspector/useSessionInspector.d.ts +0 -49
- package/session/inspector/useSessionInspector.d.ts.map +0 -1
- package/session/inspector/useSessionInspector.js +0 -122
- package/session/inspector/useSessionInspector.js.map +0 -1
- package/src/session/inspector/SessionInspector.tsx +0 -162
- package/src/session/inspector/WorkspaceTab.tsx +0 -245
- package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
- package/src/session/inspector/useSessionInspector.ts +0 -178
- package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
- package/workflow/motion-preference.d.ts.map +0 -1
- package/workflow/motion-preference.js.map +0 -1
- package/workspace/WorkspaceEntryFiles.d.ts +0 -22
- package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
- package/workspace/WorkspaceEntryFiles.js +0 -58
- package/workspace/WorkspaceEntryFiles.js.map +0 -1
- /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
- /package/session/{inspector → facets}/InspectTab.js +0 -0
- /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
- /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
- /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveWorkspaceFileSelection } from "../resolveWorkspaceFileSelection";
|
|
3
|
+
import type { WorkspaceEntry } from "../useWorkspaceEntries";
|
|
4
|
+
|
|
5
|
+
function git(id: string, name: string): WorkspaceEntry {
|
|
6
|
+
return { id, name, type: "git", gitUrl: `https://github.com/${name}.git`, gitBranch: "main" };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function local(id: string, name: string, localPath: string): WorkspaceEntry {
|
|
10
|
+
return { id, name, type: "local", localPath };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DAYTONA = "/home/daytona/workspace";
|
|
14
|
+
|
|
15
|
+
describe("resolveWorkspaceFileSelection", () => {
|
|
16
|
+
// --- single git entry (cloud): repo cloned at the workspace root ----------
|
|
17
|
+
|
|
18
|
+
it("returns a repo-relative path unchanged for a single git entry (identity)", () => {
|
|
19
|
+
const entries = [git("e1", "org/repo")];
|
|
20
|
+
expect(resolveWorkspaceFileSelection("src/main.go", entries)).toEqual({
|
|
21
|
+
entryId: "e1",
|
|
22
|
+
path: "src/main.go",
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("strips the sandbox workspace root from a cloud-absolute path", () => {
|
|
27
|
+
const entries = [git("e1", "org/repo")];
|
|
28
|
+
expect(
|
|
29
|
+
resolveWorkspaceFileSelection(`${DAYTONA}/src/main.go`, entries, DAYTONA),
|
|
30
|
+
).toEqual({ entryId: "e1", path: "src/main.go" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("normalizes a leading slash", () => {
|
|
34
|
+
const entries = [git("e1", "org/repo")];
|
|
35
|
+
expect(resolveWorkspaceFileSelection("/src/main.go", entries)).toEqual({
|
|
36
|
+
entryId: "e1",
|
|
37
|
+
path: "src/main.go",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// --- multiple git entries: each cloned into <root>/<entry.name>/ ----------
|
|
42
|
+
|
|
43
|
+
it("strips the subdir (entry.name) prefix for a multi-entry git session", () => {
|
|
44
|
+
const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
|
|
45
|
+
expect(resolveWorkspaceFileSelection("org/repoB/src/main.go", entries)).toEqual({
|
|
46
|
+
entryId: "b",
|
|
47
|
+
path: "src/main.go",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("strips both the sandbox root and the subdir for a multi-entry absolute path", () => {
|
|
52
|
+
const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
|
|
53
|
+
expect(
|
|
54
|
+
resolveWorkspaceFileSelection(`${DAYTONA}/org/repoA/x.ts`, entries, DAYTONA),
|
|
55
|
+
).toEqual({ entryId: "a", path: "x.ts" });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns null for a multi-entry path that matches no entry (never mis-attribute)", () => {
|
|
59
|
+
const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
|
|
60
|
+
expect(resolveWorkspaceFileSelection("unrelated/x.ts", entries)).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// --- local entries (desktop): anchor on the absolute localPath -----------
|
|
64
|
+
|
|
65
|
+
it("strips the absolute localPath prefix for a local entry", () => {
|
|
66
|
+
const entries = [local("e1", "app", "/Users/dev/app")];
|
|
67
|
+
expect(
|
|
68
|
+
resolveWorkspaceFileSelection("/Users/dev/app/src/main.go", entries),
|
|
69
|
+
).toEqual({ entryId: "e1", path: "src/main.go" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("accepts an already-root-relative path for a single local entry", () => {
|
|
73
|
+
const entries = [local("e1", "app", "/Users/dev/app")];
|
|
74
|
+
expect(resolveWorkspaceFileSelection("src/main.go", entries)).toEqual({
|
|
75
|
+
entryId: "e1",
|
|
76
|
+
path: "src/main.go",
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("normalizes Windows backslash paths for a local entry", () => {
|
|
81
|
+
const entries = [local("e1", "app", "C:\\Users\\dev\\app")];
|
|
82
|
+
expect(
|
|
83
|
+
resolveWorkspaceFileSelection("C:\\Users\\dev\\app\\src\\main.go", entries),
|
|
84
|
+
).toEqual({ entryId: "e1", path: "src/main.go" });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// --- platform mount, roots, and empties -> null (keep copy fallback) ------
|
|
88
|
+
|
|
89
|
+
it("returns null for a .stigmer/ platform path", () => {
|
|
90
|
+
const entries = [git("e1", "org/repo")];
|
|
91
|
+
expect(resolveWorkspaceFileSelection(".stigmer/skills/x.md", entries)).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns null for an absolute .stigmer/ path under the sandbox root", () => {
|
|
95
|
+
const entries = [git("e1", "org/repo")];
|
|
96
|
+
expect(
|
|
97
|
+
resolveWorkspaceFileSelection(`${DAYTONA}/.stigmer/skills/x`, entries, DAYTONA),
|
|
98
|
+
).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns null for the bare workspace root", () => {
|
|
102
|
+
const entries = [git("e1", "org/repo")];
|
|
103
|
+
expect(resolveWorkspaceFileSelection(DAYTONA, entries, DAYTONA)).toBeNull();
|
|
104
|
+
expect(resolveWorkspaceFileSelection(".", entries)).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns null when the path targets a local entry's own root directory", () => {
|
|
108
|
+
const entries = [local("e1", "app", "/Users/dev/app")];
|
|
109
|
+
expect(resolveWorkspaceFileSelection("/Users/dev/app", entries)).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns null for an empty path", () => {
|
|
113
|
+
expect(resolveWorkspaceFileSelection("", [git("e1", "org/repo")])).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns null when there are no workspace entries", () => {
|
|
117
|
+
expect(resolveWorkspaceFileSelection("src/main.go", [])).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// --- cross-check invariant: transcript-open == tree-open ------------------
|
|
121
|
+
|
|
122
|
+
it("produces the same repo-relative path a file-tree click would (single entry)", () => {
|
|
123
|
+
// The GitHub Trees lister emits repo-root-relative node paths; a tree click
|
|
124
|
+
// opens { entryId, path: "src/app/page.tsx" }. A transcript click on the
|
|
125
|
+
// same file must resolve to an identical selection so both hit one fetch.
|
|
126
|
+
const entries = [git("e1", "org/repo")];
|
|
127
|
+
const treeSelection = { entryId: "e1", path: "src/app/page.tsx" };
|
|
128
|
+
expect(resolveWorkspaceFileSelection("src/app/page.tsx", entries)).toEqual(
|
|
129
|
+
treeSelection,
|
|
130
|
+
);
|
|
131
|
+
expect(
|
|
132
|
+
resolveWorkspaceFileSelection(`${DAYTONA}/src/app/page.tsx`, entries, DAYTONA),
|
|
133
|
+
).toEqual(treeSelection);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { renderHook, waitFor, cleanup } from "@testing-library/react";
|
|
3
|
+
import { useWorkspaceContentSearch } from "../useWorkspaceContentSearch";
|
|
4
|
+
import type { WorkspaceEntry } from "../useWorkspaceEntries";
|
|
5
|
+
import type {
|
|
6
|
+
WorkspaceContentMatch,
|
|
7
|
+
WorkspaceContentSearchResult,
|
|
8
|
+
WorkspaceContentSearcher,
|
|
9
|
+
} from "../WorkspaceContentSearcher";
|
|
10
|
+
|
|
11
|
+
// Real timers with `debounceMs: 0` (mirroring the filename-search hook suite):
|
|
12
|
+
// the debounce collapses to a microtask so `waitFor` drives the async settle
|
|
13
|
+
// deterministically. Fake timers + `advanceTimersByTimeAsync` interact badly
|
|
14
|
+
// with the deferred-promise refetch assertions below (microtask blowup), and
|
|
15
|
+
// the behaviors under test — min-length gating, aggregation, error isolation,
|
|
16
|
+
// refetch-keeps-results, and the stale-generation guard — don't need wall-clock
|
|
17
|
+
// timing to verify.
|
|
18
|
+
|
|
19
|
+
let entryIdCounter = 0;
|
|
20
|
+
|
|
21
|
+
function makeEntry(overrides?: Partial<WorkspaceEntry>): WorkspaceEntry {
|
|
22
|
+
return {
|
|
23
|
+
id: `ws-${++entryIdCounter}`,
|
|
24
|
+
name: "acme/api",
|
|
25
|
+
type: "local",
|
|
26
|
+
localPath: "/repo",
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function match(path: string, line: number, preview: string): WorkspaceContentMatch {
|
|
32
|
+
return { path, line, preview };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function result(
|
|
36
|
+
matches: WorkspaceContentMatch[],
|
|
37
|
+
truncated = false,
|
|
38
|
+
): WorkspaceContentSearchResult {
|
|
39
|
+
return { matches, truncated };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A manually-resolvable searcher result, for observing in-flight states. */
|
|
43
|
+
function deferred() {
|
|
44
|
+
let resolve!: (r: WorkspaceContentSearchResult) => void;
|
|
45
|
+
const promise = new Promise<WorkspaceContentSearchResult>((res) => {
|
|
46
|
+
resolve = res;
|
|
47
|
+
});
|
|
48
|
+
return { promise, resolve };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
afterEach(cleanup);
|
|
52
|
+
|
|
53
|
+
describe("useWorkspaceContentSearch", () => {
|
|
54
|
+
it("is unsupported and never searches when no searcher is injected", () => {
|
|
55
|
+
const entries = [makeEntry()];
|
|
56
|
+
const { result: r } = renderHook(() =>
|
|
57
|
+
useWorkspaceContentSearch({
|
|
58
|
+
entries,
|
|
59
|
+
searcher: undefined,
|
|
60
|
+
query: "foo",
|
|
61
|
+
debounceMs: 0,
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
expect(r.current.isUnsupported).toBe(true);
|
|
65
|
+
expect(r.current.groups).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("does not search below the minimum query length", async () => {
|
|
69
|
+
const entries = [makeEntry()];
|
|
70
|
+
const searcher = vi.fn(async () => result([])) as unknown as WorkspaceContentSearcher;
|
|
71
|
+
const { result: r } = renderHook(() =>
|
|
72
|
+
useWorkspaceContentSearch({ entries, searcher, query: "a", debounceMs: 0 }),
|
|
73
|
+
);
|
|
74
|
+
// Give the debounce a tick to (not) fire.
|
|
75
|
+
await new Promise((res) => setTimeout(res, 20));
|
|
76
|
+
expect(searcher).not.toHaveBeenCalled();
|
|
77
|
+
expect(r.current.groups).toEqual([]);
|
|
78
|
+
expect(r.current.isUnsupported).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("aggregates per-entry line matches", async () => {
|
|
82
|
+
const a = makeEntry();
|
|
83
|
+
const b = makeEntry();
|
|
84
|
+
const searcher: WorkspaceContentSearcher = vi.fn(async (e) =>
|
|
85
|
+
e.id === a.id
|
|
86
|
+
? result([match("a.ts", 1, "foo one"), match("a.ts", 4, "foo two")])
|
|
87
|
+
: result([match("b.ts", 2, "foo three")]),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const { result: r } = renderHook(() =>
|
|
91
|
+
useWorkspaceContentSearch({ entries: [a, b], searcher, query: "foo", debounceMs: 0 }),
|
|
92
|
+
);
|
|
93
|
+
await waitFor(() => expect(r.current.isLoading).toBe(false));
|
|
94
|
+
|
|
95
|
+
expect(r.current.totalMatches).toBe(3);
|
|
96
|
+
expect(
|
|
97
|
+
r.current.groups.map((g) => ({ id: g.entry.id, lines: g.matches.map((m) => m.line) })),
|
|
98
|
+
).toEqual([
|
|
99
|
+
{ id: a.id, lines: [1, 4] },
|
|
100
|
+
{ id: b.id, lines: [2] },
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("isolates a per-entry error while other entries still resolve", async () => {
|
|
105
|
+
const good = makeEntry();
|
|
106
|
+
const bad = makeEntry();
|
|
107
|
+
const searcher: WorkspaceContentSearcher = vi.fn(async (e) => {
|
|
108
|
+
if (e.id === bad.id) throw new Error("disk error");
|
|
109
|
+
return result([match("a.ts", 1, "foo")]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const { result: r } = renderHook(() =>
|
|
113
|
+
useWorkspaceContentSearch({ entries: [good, bad], searcher, query: "foo", debounceMs: 0 }),
|
|
114
|
+
);
|
|
115
|
+
await waitFor(() => expect(r.current.isLoading).toBe(false));
|
|
116
|
+
|
|
117
|
+
expect(r.current.groups.find((g) => g.entry.id === good.id)?.matches).toHaveLength(1);
|
|
118
|
+
expect(r.current.groups.find((g) => g.entry.id === bad.id)?.error?.message).toBe("disk error");
|
|
119
|
+
expect(r.current.error?.message).toBe("disk error");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("reports isUnsupported when every entry returns null", async () => {
|
|
123
|
+
const entries = [makeEntry()];
|
|
124
|
+
const searcher: WorkspaceContentSearcher = vi.fn(async () => null);
|
|
125
|
+
const { result: r } = renderHook(() =>
|
|
126
|
+
useWorkspaceContentSearch({ entries, searcher, query: "foo", debounceMs: 0 }),
|
|
127
|
+
);
|
|
128
|
+
await waitFor(() => expect(r.current.isUnsupported).toBe(true));
|
|
129
|
+
expect(r.current.groups).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("surfaces a per-entry truncation flag", async () => {
|
|
133
|
+
const entries = [makeEntry()];
|
|
134
|
+
const searcher: WorkspaceContentSearcher = vi.fn(async () =>
|
|
135
|
+
result([match("a.ts", 1, "foo")], true),
|
|
136
|
+
);
|
|
137
|
+
const { result: r } = renderHook(() =>
|
|
138
|
+
useWorkspaceContentSearch({ entries, searcher, query: "foo", debounceMs: 0 }),
|
|
139
|
+
);
|
|
140
|
+
await waitFor(() => expect(r.current.isLoading).toBe(false));
|
|
141
|
+
expect(r.current.groups[0].truncated).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("keeps prior results visible while a new query loads (isRefetching, no blank flash)", async () => {
|
|
145
|
+
const entry = makeEntry();
|
|
146
|
+
const second = deferred();
|
|
147
|
+
const searcher = vi
|
|
148
|
+
.fn()
|
|
149
|
+
.mockResolvedValueOnce(result([match("a.ts", 1, "foo")]))
|
|
150
|
+
.mockReturnValueOnce(second.promise) as unknown as WorkspaceContentSearcher;
|
|
151
|
+
|
|
152
|
+
const { result: r, rerender } = renderHook(
|
|
153
|
+
({ q }) => useWorkspaceContentSearch({ entries: [entry], searcher, query: q, debounceMs: 0 }),
|
|
154
|
+
{ initialProps: { q: "foo" } },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await waitFor(() => expect(r.current.groups[0]?.matches).toHaveLength(1));
|
|
158
|
+
expect(r.current.isLoading).toBe(false);
|
|
159
|
+
|
|
160
|
+
// New query → the second (pending) search starts; old results stay visible.
|
|
161
|
+
rerender({ q: "bar" });
|
|
162
|
+
await waitFor(() => expect(r.current.isRefetching).toBe(true));
|
|
163
|
+
expect(r.current.isLoading).toBe(false);
|
|
164
|
+
expect(r.current.groups[0].matches).toHaveLength(1);
|
|
165
|
+
|
|
166
|
+
// Resolve the second search → new results replace the old ones.
|
|
167
|
+
second.resolve(result([match("b.ts", 2, "bar"), match("c.ts", 3, "bar")]));
|
|
168
|
+
await waitFor(() => expect(r.current.isRefetching).toBe(false));
|
|
169
|
+
expect(r.current.totalMatches).toBe(2);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("ignores a stale in-flight search when the query changes again", async () => {
|
|
173
|
+
const entry = makeEntry();
|
|
174
|
+
const first = deferred();
|
|
175
|
+
const searcher = vi
|
|
176
|
+
.fn()
|
|
177
|
+
.mockReturnValueOnce(first.promise)
|
|
178
|
+
.mockResolvedValueOnce(result([match("new.ts", 1, "bar")])) as unknown as WorkspaceContentSearcher;
|
|
179
|
+
|
|
180
|
+
const { result: r, rerender } = renderHook(
|
|
181
|
+
({ q }) => useWorkspaceContentSearch({ entries: [entry], searcher, query: q, debounceMs: 0 }),
|
|
182
|
+
{ initialProps: { q: "foo" } },
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Supersede the first (still pending) query; the second resolves.
|
|
186
|
+
rerender({ q: "bar" });
|
|
187
|
+
await waitFor(() =>
|
|
188
|
+
expect(r.current.groups.map((g) => g.matches.map((m) => m.path))).toEqual([["new.ts"]]),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// The stale first now resolves — it must not clobber the fresher result.
|
|
192
|
+
first.resolve(result([match("stale.ts", 9, "foo")]));
|
|
193
|
+
await new Promise((res) => setTimeout(res, 20));
|
|
194
|
+
expect(r.current.groups.map((g) => g.matches.map((m) => m.path))).toEqual([["new.ts"]]);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { renderHook, act, waitFor } from "@testing-library/react";
|
|
3
|
+
import { useWorkspaceFileContent } from "../useWorkspaceFileContent";
|
|
4
|
+
import type { WorkspaceEntry } from "../useWorkspaceEntries";
|
|
5
|
+
import type {
|
|
6
|
+
WorkspaceFileContent,
|
|
7
|
+
WorkspaceFileReader,
|
|
8
|
+
} from "../WorkspaceFileReader";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Fixtures
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function gitEntry(id = "e1"): WorkspaceEntry {
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
name: "acme/repo",
|
|
18
|
+
type: "git",
|
|
19
|
+
gitUrl: "https://github.com/acme/repo",
|
|
20
|
+
gitBranch: "main",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function textContent(text: string): WorkspaceFileContent {
|
|
25
|
+
return { text, isBinary: false, size: text.length, encoding: "utf-8" };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// useWorkspaceFileContent
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
describe("useWorkspaceFileContent", () => {
|
|
33
|
+
it("resolves file content on success and is not unsupported", async () => {
|
|
34
|
+
const reader: WorkspaceFileReader = vi.fn(async () => textContent("hello"));
|
|
35
|
+
const { result } = renderHook(() =>
|
|
36
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "a.ts", reader }),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result.current.isLoading).toBe(true);
|
|
40
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
41
|
+
|
|
42
|
+
expect(result.current.content?.text).toBe("hello");
|
|
43
|
+
expect(result.current.isUnsupported).toBe(false);
|
|
44
|
+
expect(result.current.error).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("reports isUnsupported when the reader resolves null", async () => {
|
|
48
|
+
const reader: WorkspaceFileReader = vi.fn(async () => null);
|
|
49
|
+
const { result } = renderHook(() =>
|
|
50
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "a.ts", reader }),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
54
|
+
|
|
55
|
+
expect(result.current.content).toBeNull();
|
|
56
|
+
expect(result.current.isUnsupported).toBe(true);
|
|
57
|
+
expect(result.current.error).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("surfaces a reader throw as error, not unsupported", async () => {
|
|
61
|
+
const reader: WorkspaceFileReader = vi.fn(async () => {
|
|
62
|
+
throw new Error("boom");
|
|
63
|
+
});
|
|
64
|
+
const { result } = renderHook(() =>
|
|
65
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "a.ts", reader }),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await waitFor(() => expect(result.current.error).not.toBeNull());
|
|
69
|
+
|
|
70
|
+
expect(result.current.error?.message).toBe("boom");
|
|
71
|
+
expect(result.current.isUnsupported).toBe(false);
|
|
72
|
+
expect(result.current.content).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("stays idle and unsupported when no reader is injected", () => {
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "a.ts", reader: undefined }),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(result.current.isLoading).toBe(false);
|
|
81
|
+
expect(result.current.isUnsupported).toBe(true);
|
|
82
|
+
expect(result.current.content).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("does not treat a binary file (present object, text null) as unsupported", async () => {
|
|
86
|
+
const binary: WorkspaceFileContent = {
|
|
87
|
+
text: null,
|
|
88
|
+
isBinary: true,
|
|
89
|
+
size: 10,
|
|
90
|
+
encoding: "unknown",
|
|
91
|
+
};
|
|
92
|
+
const reader: WorkspaceFileReader = vi.fn(async () => binary);
|
|
93
|
+
const { result } = renderHook(() =>
|
|
94
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "img.png", reader }),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
98
|
+
|
|
99
|
+
expect(result.current.content).toEqual(binary);
|
|
100
|
+
expect(result.current.isUnsupported).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("stays idle and never calls the reader when no file is selected", () => {
|
|
104
|
+
const reader = vi.fn(async () => textContent("x")) as WorkspaceFileReader;
|
|
105
|
+
const { result } = renderHook(() =>
|
|
106
|
+
useWorkspaceFileContent({ entry: null, path: null, reader }),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(result.current.isLoading).toBe(false);
|
|
110
|
+
expect(result.current.isUnsupported).toBe(false);
|
|
111
|
+
expect(reader).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("re-reads on refetch()", async () => {
|
|
115
|
+
let n = 0;
|
|
116
|
+
const reader: WorkspaceFileReader = vi.fn(async () => textContent(`v${++n}`));
|
|
117
|
+
const { result } = renderHook(() =>
|
|
118
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: "a.ts", reader }),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await waitFor(() => expect(result.current.content?.text).toBe("v1"));
|
|
122
|
+
|
|
123
|
+
act(() => {
|
|
124
|
+
result.current.refetch();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await waitFor(() => expect(result.current.content?.text).toBe("v2"));
|
|
128
|
+
expect(reader).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("re-reads when the path changes", async () => {
|
|
132
|
+
const reader: WorkspaceFileReader = vi.fn(async (_e, p) =>
|
|
133
|
+
textContent(`content:${p}`),
|
|
134
|
+
);
|
|
135
|
+
const { result, rerender } = renderHook(
|
|
136
|
+
(props: { path: string }) =>
|
|
137
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: props.path, reader }),
|
|
138
|
+
{ initialProps: { path: "a.ts" } },
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
await waitFor(() => expect(result.current.content?.text).toBe("content:a.ts"));
|
|
142
|
+
|
|
143
|
+
rerender({ path: "b.ts" });
|
|
144
|
+
|
|
145
|
+
await waitFor(() => expect(result.current.content?.text).toBe("content:b.ts"));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("ignores a stale slow read when the path changes mid-flight", async () => {
|
|
149
|
+
const deferred = new Map<string, (v: WorkspaceFileContent) => void>();
|
|
150
|
+
const reader: WorkspaceFileReader = (_e, p) =>
|
|
151
|
+
new Promise((resolve) => deferred.set(p, resolve));
|
|
152
|
+
|
|
153
|
+
const { result, rerender } = renderHook(
|
|
154
|
+
(props: { path: string }) =>
|
|
155
|
+
useWorkspaceFileContent({ entry: gitEntry(), path: props.path, reader }),
|
|
156
|
+
{ initialProps: { path: "slow.ts" } },
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Switch to a second file before the first resolves.
|
|
160
|
+
rerender({ path: "fast.ts" });
|
|
161
|
+
|
|
162
|
+
// Resolve the current (fast) read first.
|
|
163
|
+
await act(async () => {
|
|
164
|
+
deferred.get("fast.ts")!(textContent("FAST"));
|
|
165
|
+
});
|
|
166
|
+
await waitFor(() => expect(result.current.content?.text).toBe("FAST"));
|
|
167
|
+
|
|
168
|
+
// The stale (slow) read resolves later — it must NOT overwrite FAST.
|
|
169
|
+
await act(async () => {
|
|
170
|
+
deferred.get("slow.ts")!(textContent("SLOW"));
|
|
171
|
+
});
|
|
172
|
+
expect(result.current.content?.text).toBe("FAST");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("goes idle when the entry is removed (entry -> null)", async () => {
|
|
176
|
+
const reader: WorkspaceFileReader = vi.fn(async () => textContent("x"));
|
|
177
|
+
const { result, rerender } = renderHook(
|
|
178
|
+
(props: { entry: WorkspaceEntry | null }) =>
|
|
179
|
+
useWorkspaceFileContent({ entry: props.entry, path: "a.ts", reader }),
|
|
180
|
+
{ initialProps: { entry: gitEntry() as WorkspaceEntry | null } },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await waitFor(() => expect(result.current.content?.text).toBe("x"));
|
|
184
|
+
|
|
185
|
+
rerender({ entry: null });
|
|
186
|
+
|
|
187
|
+
await waitFor(() => expect(result.current.content).toBeNull());
|
|
188
|
+
expect(result.current.isLoading).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { renderHook, waitFor, cleanup } from "@testing-library/react";
|
|
3
|
+
import { useWorkspaceFileSearch } from "../useWorkspaceFileSearch";
|
|
4
|
+
import { __clearWorkspaceListingCache } from "../workspaceListingCache";
|
|
5
|
+
import type { WorkspaceEntry } from "../useWorkspaceEntries";
|
|
6
|
+
import type { WorkspaceFileEntry, WorkspaceFileLister } from "../WorkspaceFileLister";
|
|
7
|
+
|
|
8
|
+
let entryIdCounter = 0;
|
|
9
|
+
|
|
10
|
+
function makeEntry(overrides?: Partial<WorkspaceEntry>): WorkspaceEntry {
|
|
11
|
+
return {
|
|
12
|
+
id: `ws-${++entryIdCounter}`,
|
|
13
|
+
name: "acme/api",
|
|
14
|
+
type: "git",
|
|
15
|
+
gitUrl: "https://github.com/acme/api",
|
|
16
|
+
gitBranch: "main",
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const FILES_A: WorkspaceFileEntry[] = [
|
|
22
|
+
{ path: "src/index.ts", isDirectory: false },
|
|
23
|
+
{ path: "src/button.tsx", isDirectory: false },
|
|
24
|
+
{ path: "README.md", isDirectory: false },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const FILES_B: WorkspaceFileEntry[] = [
|
|
28
|
+
{ path: "lib/button.ts", isDirectory: false },
|
|
29
|
+
{ path: "lib/util.ts", isDirectory: false },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
describe("useWorkspaceFileSearch", () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
__clearWorkspaceListingCache();
|
|
35
|
+
});
|
|
36
|
+
afterEach(cleanup);
|
|
37
|
+
|
|
38
|
+
it("is unsupported and never fetches when no lister is injected", () => {
|
|
39
|
+
const lister = vi.fn();
|
|
40
|
+
const entries = [makeEntry()];
|
|
41
|
+
const { result } = renderHook(() =>
|
|
42
|
+
useWorkspaceFileSearch({ entries, lister: undefined, query: "x" }),
|
|
43
|
+
);
|
|
44
|
+
expect(result.current.isUnsupported).toBe(true);
|
|
45
|
+
expect(result.current.groups).toEqual([]);
|
|
46
|
+
expect(lister).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("aggregates ranked matches across multiple entries", async () => {
|
|
50
|
+
const entryA = makeEntry();
|
|
51
|
+
const entryB = makeEntry();
|
|
52
|
+
const lister: WorkspaceFileLister = vi.fn(async (e) =>
|
|
53
|
+
e.id === entryA.id ? FILES_A : FILES_B,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const { result } = renderHook(() =>
|
|
57
|
+
useWorkspaceFileSearch({ entries: [entryA, entryB], lister, query: "button" }),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
61
|
+
|
|
62
|
+
expect(result.current.totalMatches).toBe(2);
|
|
63
|
+
const groupPaths = result.current.groups.map((g) => ({
|
|
64
|
+
id: g.entry.id,
|
|
65
|
+
paths: g.matches.map((m) => m.path),
|
|
66
|
+
}));
|
|
67
|
+
expect(groupPaths).toEqual([
|
|
68
|
+
{ id: entryA.id, paths: ["src/button.tsx"] },
|
|
69
|
+
{ id: entryB.id, paths: ["lib/button.ts"] },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("reuses the shared cache — no refetch when warm", async () => {
|
|
74
|
+
const entry = makeEntry();
|
|
75
|
+
const lister: WorkspaceFileLister = vi.fn(async () => FILES_A);
|
|
76
|
+
|
|
77
|
+
const first = renderHook(
|
|
78
|
+
({ q }) => useWorkspaceFileSearch({ entries: [entry], lister, query: q }),
|
|
79
|
+
{ initialProps: { q: "button" } },
|
|
80
|
+
);
|
|
81
|
+
await waitFor(() => expect(first.result.current.isLoading).toBe(false));
|
|
82
|
+
expect(lister).toHaveBeenCalledTimes(1);
|
|
83
|
+
first.unmount();
|
|
84
|
+
|
|
85
|
+
// Remount (re-enter Search) with the same entry → served from cache.
|
|
86
|
+
const second = renderHook(() =>
|
|
87
|
+
useWorkspaceFileSearch({ entries: [entry], lister, query: "index" }),
|
|
88
|
+
);
|
|
89
|
+
await waitFor(() => expect(second.result.current.isLoading).toBe(false));
|
|
90
|
+
expect(lister).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(second.result.current.groups[0].matches.map((m) => m.path)).toEqual([
|
|
92
|
+
"src/index.ts",
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("isolates a per-entry error while other entries still resolve", async () => {
|
|
97
|
+
const good = makeEntry();
|
|
98
|
+
const bad = makeEntry();
|
|
99
|
+
const lister: WorkspaceFileLister = vi.fn(async (e) => {
|
|
100
|
+
if (e.id === bad.id) throw new Error("network down");
|
|
101
|
+
return FILES_A;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const { result } = renderHook(() =>
|
|
105
|
+
useWorkspaceFileSearch({ entries: [good, bad], lister, query: "button" }),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
109
|
+
|
|
110
|
+
const goodGroup = result.current.groups.find((g) => g.entry.id === good.id);
|
|
111
|
+
const badGroup = result.current.groups.find((g) => g.entry.id === bad.id);
|
|
112
|
+
expect(goodGroup?.matches.map((m) => m.path)).toEqual(["src/button.tsx"]);
|
|
113
|
+
expect(badGroup?.error?.message).toBe("network down");
|
|
114
|
+
expect(result.current.error?.message).toBe("network down");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("surfaces per-group truncated from a notice entry", async () => {
|
|
118
|
+
const entry = makeEntry();
|
|
119
|
+
const lister: WorkspaceFileLister = vi.fn(async () => [
|
|
120
|
+
{ path: "src/button.tsx", isDirectory: false },
|
|
121
|
+
{ path: "... truncated", isDirectory: false, notice: true as const },
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const { result } = renderHook(() =>
|
|
125
|
+
useWorkspaceFileSearch({ entries: [entry], lister, query: "button" }),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
129
|
+
expect(result.current.groups[0].truncated).toBe(true);
|
|
130
|
+
expect(result.current.groups[0].matches.map((m) => m.path)).toEqual([
|
|
131
|
+
"src/button.tsx",
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("reports isUnsupported when every entry's substrate returns null", async () => {
|
|
136
|
+
const entry = makeEntry();
|
|
137
|
+
const lister: WorkspaceFileLister = vi.fn(async () => null);
|
|
138
|
+
|
|
139
|
+
const { result } = renderHook(() =>
|
|
140
|
+
useWorkspaceFileSearch({ entries: [entry], lister, query: "x" }),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
144
|
+
expect(result.current.isUnsupported).toBe(true);
|
|
145
|
+
expect(result.current.groups).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("keeps a stable return reference across rerenders with an unchanged query", async () => {
|
|
149
|
+
const entry = makeEntry();
|
|
150
|
+
const entries = [entry];
|
|
151
|
+
const lister: WorkspaceFileLister = vi.fn(async () => FILES_A);
|
|
152
|
+
|
|
153
|
+
const { result, rerender } = renderHook(() =>
|
|
154
|
+
useWorkspaceFileSearch({ entries, lister, query: "button" }),
|
|
155
|
+
);
|
|
156
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
157
|
+
|
|
158
|
+
const before = result.current;
|
|
159
|
+
rerender();
|
|
160
|
+
expect(result.current).toBe(before);
|
|
161
|
+
});
|
|
162
|
+
});
|