@stigmer/react 3.1.1 → 3.1.3
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 +25 -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/ArtifactPreviewModal.d.ts +5 -5
- package/execution/ArtifactPreviewModal.js +4 -4
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/FileChangeProgressBar.d.ts +47 -0
- package/execution/FileChangeProgressBar.d.ts.map +1 -0
- package/execution/FileChangeProgressBar.js +49 -0
- package/execution/FileChangeProgressBar.js.map +1 -0
- package/execution/FileChangesView.d.ts.map +1 -1
- package/execution/FileChangesView.js +19 -4
- package/execution/FileChangesView.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/FileReviewAtoms.d.ts +27 -0
- package/execution/FileReviewAtoms.d.ts.map +1 -0
- package/execution/FileReviewAtoms.js +64 -0
- package/execution/FileReviewAtoms.js.map +1 -0
- package/execution/FileReviewCard.d.ts +2 -2
- package/execution/FileReviewCard.d.ts.map +1 -1
- package/execution/FileReviewCard.js +32 -62
- package/execution/FileReviewCard.js.map +1 -1
- package/execution/MessageEntry.d.ts +20 -0
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +19 -5
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +75 -13
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +195 -27
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +36 -21
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +39 -25
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +4 -4
- package/execution/PlanCompletionCard.d.ts.map +1 -1
- package/execution/PlanCompletionCard.js +4 -4
- package/execution/PlanCompletionCard.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 +50 -0
- package/execution/PlanStreamingCard.d.ts.map +1 -0
- package/execution/PlanStreamingCard.js +40 -0
- package/execution/PlanStreamingCard.js.map +1 -0
- package/execution/index.d.ts +9 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +4 -0
- package/execution/index.js.map +1 -1
- package/execution/use-build-from-plan-hotkey.d.ts +1 -1
- package/execution/use-build-from-plan-hotkey.js +1 -1
- package/execution/useArtifactCopy.d.ts +51 -0
- package/execution/useArtifactCopy.d.ts.map +1 -0
- package/execution/useArtifactCopy.js +75 -0
- package/execution/useArtifactCopy.js.map +1 -0
- package/execution/useArtifactDownload.d.ts.map +1 -1
- package/execution/useArtifactDownload.js +17 -5
- package/execution/useArtifactDownload.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +12 -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 +43 -4
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +45 -5
- 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 +334 -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 +21 -0
- package/session/plan-document.d.ts.map +1 -0
- package/session/plan-document.js +28 -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 +14 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +20 -2
- 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 +53 -10
- package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
- package/src/execution/ArtifactContentRenderer.tsx +101 -24
- package/src/execution/ArtifactPreviewModal.tsx +9 -9
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FilePathContext.tsx +8 -1
- package/src/execution/FilePathLink.tsx +22 -6
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +49 -1
- package/src/execution/MessageThread.tsx +308 -39
- package/src/execution/PlanArtifactCard.tsx +174 -64
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanDocumentMessage.tsx +96 -0
- package/src/execution/PlanStreamingCard.tsx +141 -0
- package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +102 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +114 -26
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +56 -0
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +600 -22
- package/src/execution/__tests__/message-entry.test.tsx +17 -0
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
- package/src/execution/index.ts +13 -1
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useCreateAgentExecution.ts +18 -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 +69 -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 +56 -5
- 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 +69 -0
- package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
- package/src/library/detect-plan-artifact.ts +62 -5
- package/src/library/detect-streaming-plan.ts +108 -0
- package/src/library/index.ts +7 -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 +655 -96
- 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 +66 -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__/useSessionConversation.test.tsx +44 -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 +42 -8
- 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 +30 -0
- package/src/session/useOpenFileChange.ts +53 -0
- package/src/session/usePlanDraft.ts +109 -0
- package/src/session/useSessionConversation.ts +38 -3
- 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 +174 -0
- package/src/workspace/ExplorerTree.tsx +253 -0
- package/src/workspace/FileViewer.tsx +537 -0
- package/src/workspace/WorkspaceContentSearch.tsx +502 -0
- package/src/workspace/WorkspaceContentSearcher.ts +70 -0
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileLister.ts +11 -0
- package/src/workspace/WorkspaceFileReader.ts +57 -0
- package/src/workspace/WorkspaceFileSearch.tsx +452 -0
- package/src/workspace/WorkspaceSurface.tsx +839 -0
- package/src/workspace/__tests__/EditorTabs.test.tsx +102 -0
- package/src/workspace/__tests__/ExplorerTree.test.tsx +204 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +439 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +229 -0
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +227 -0
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +418 -0
- package/src/workspace/__tests__/a11y/harness.tsx +140 -0
- package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
- package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
- package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
- package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -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 +47 -0
- package/workspace/EditorTabs.d.ts.map +1 -0
- package/workspace/EditorTabs.js +78 -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 +49 -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 +193 -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 +216 -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/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- 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 +175 -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 +220 -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,502 @@
|
|
|
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
|
+
// Screen-reader-only summary in a persistent polite live region: status swaps
|
|
165
|
+
// (searching → results/none) happen across separate unmounting branches, so a
|
|
166
|
+
// stable region is what actually gets announced. Terse by design.
|
|
167
|
+
const statusMessage = isUnsupported
|
|
168
|
+
? ""
|
|
169
|
+
: trimmed.length === 0
|
|
170
|
+
? ""
|
|
171
|
+
: belowMinLength
|
|
172
|
+
? `Type at least ${MIN_QUERY_LENGTH} characters to search.`
|
|
173
|
+
: isLoading
|
|
174
|
+
? "Searching file contents…"
|
|
175
|
+
: groups.length === 0
|
|
176
|
+
? `No files containing ${trimmed}.`
|
|
177
|
+
: totalMatches > flatResults.length
|
|
178
|
+
? `Showing the first ${flatResults.length} of ${totalMatches} matches.`
|
|
179
|
+
: `${totalMatches} ${totalMatches === 1 ? "match" : "matches"}.`;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className={cn("flex h-full flex-col", className)}>
|
|
183
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
184
|
+
{statusMessage}
|
|
185
|
+
</div>
|
|
186
|
+
<div className="flex items-center gap-1.5 border-b border-border px-2 py-1.5">
|
|
187
|
+
<SearchIcon />
|
|
188
|
+
<input
|
|
189
|
+
ref={inputRef}
|
|
190
|
+
type="text"
|
|
191
|
+
role="combobox"
|
|
192
|
+
aria-expanded
|
|
193
|
+
aria-controls={listboxId}
|
|
194
|
+
aria-activedescendant={focusIndex >= 0 ? optionId(focusIndex) : undefined}
|
|
195
|
+
value={query}
|
|
196
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
197
|
+
onKeyDown={handleKeyDown}
|
|
198
|
+
placeholder="Search text in files…"
|
|
199
|
+
disabled={isUnsupported}
|
|
200
|
+
className={cn(
|
|
201
|
+
"min-w-0 flex-1 bg-transparent text-xs text-foreground",
|
|
202
|
+
"placeholder:text-muted-foreground",
|
|
203
|
+
"focus-visible:outline-none disabled:cursor-not-allowed",
|
|
204
|
+
)}
|
|
205
|
+
aria-label="Search text in workspace files"
|
|
206
|
+
/>
|
|
207
|
+
{isRefetching && <Spinner />}
|
|
208
|
+
{trimmed.length > 0 && (
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
onClick={() => {
|
|
212
|
+
setQuery("");
|
|
213
|
+
inputRef.current?.focus();
|
|
214
|
+
}}
|
|
215
|
+
className="shrink-0 text-muted-foreground transition-colors hover:text-foreground"
|
|
216
|
+
aria-label="Clear search"
|
|
217
|
+
>
|
|
218
|
+
<ClearIcon />
|
|
219
|
+
</button>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
224
|
+
{isUnsupported ? (
|
|
225
|
+
<MessageState>Text search isn’t available here.</MessageState>
|
|
226
|
+
) : trimmed.length === 0 ? (
|
|
227
|
+
<MessageState>Search file contents across the whole workspace.</MessageState>
|
|
228
|
+
) : belowMinLength ? (
|
|
229
|
+
<MessageState>
|
|
230
|
+
Type at least {MIN_QUERY_LENGTH} characters to search.
|
|
231
|
+
</MessageState>
|
|
232
|
+
) : isLoading ? (
|
|
233
|
+
<MessageState>Searching…</MessageState>
|
|
234
|
+
) : groups.length === 0 ? (
|
|
235
|
+
<MessageState>No files containing “{trimmed}”.</MessageState>
|
|
236
|
+
) : (
|
|
237
|
+
<>
|
|
238
|
+
<ul id={listboxId} role="listbox" aria-label="Search results" className="py-0.5">
|
|
239
|
+
{renderRows({
|
|
240
|
+
groups,
|
|
241
|
+
query: trimmed,
|
|
242
|
+
showEntryHeaders,
|
|
243
|
+
selectedFile: selectedFile ?? null,
|
|
244
|
+
focusIndex,
|
|
245
|
+
optionId,
|
|
246
|
+
onOpenFile,
|
|
247
|
+
})}
|
|
248
|
+
</ul>
|
|
249
|
+
{totalMatches > flatResults.length && (
|
|
250
|
+
<p className="border-t border-border px-3 py-1.5 text-[0.65rem] text-muted-foreground">
|
|
251
|
+
Showing the first {flatResults.length} of {totalMatches} matches — refine
|
|
252
|
+
your search to narrow the list.
|
|
253
|
+
</p>
|
|
254
|
+
)}
|
|
255
|
+
</>
|
|
256
|
+
)}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Result rows — a single pass that keeps the flat line-hit index aligned with
|
|
264
|
+
// `flatResults` (and thus keyboard focus / aria-activedescendant), rendering a
|
|
265
|
+
// VS Code-style entry → file → line hierarchy. Backend order (path, then line)
|
|
266
|
+
// makes each file's hits contiguous, so file headers fall out of a path change.
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
function renderRows({
|
|
270
|
+
groups,
|
|
271
|
+
query,
|
|
272
|
+
showEntryHeaders,
|
|
273
|
+
selectedFile,
|
|
274
|
+
focusIndex,
|
|
275
|
+
optionId,
|
|
276
|
+
onOpenFile,
|
|
277
|
+
}: {
|
|
278
|
+
readonly groups: readonly WorkspaceContentSearchGroup[];
|
|
279
|
+
readonly query: string;
|
|
280
|
+
readonly showEntryHeaders: boolean;
|
|
281
|
+
readonly selectedFile: SelectedWorkspaceFile | null;
|
|
282
|
+
readonly focusIndex: number;
|
|
283
|
+
readonly optionId: (index: number) => string;
|
|
284
|
+
readonly onOpenFile: (
|
|
285
|
+
entryId: string,
|
|
286
|
+
path: string,
|
|
287
|
+
options?: OpenFileOptions,
|
|
288
|
+
) => void;
|
|
289
|
+
}) {
|
|
290
|
+
const rows: ReactNode[] = [];
|
|
291
|
+
let matchIndex = 0;
|
|
292
|
+
|
|
293
|
+
for (const group of groups) {
|
|
294
|
+
if (showEntryHeaders) {
|
|
295
|
+
rows.push(<EntryHeader key={`entry:${group.entry.id}`} name={group.entry.name} />);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let currentPath: string | null = null;
|
|
299
|
+
for (const match of group.matches) {
|
|
300
|
+
if (matchIndex >= MAX_RESULTS) break;
|
|
301
|
+
|
|
302
|
+
if (match.path !== currentPath) {
|
|
303
|
+
currentPath = match.path;
|
|
304
|
+
rows.push(<FileHeader key={`file:${group.entry.id}:${match.path}`} path={match.path} />);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const index = matchIndex++;
|
|
308
|
+
const isOpen =
|
|
309
|
+
selectedFile?.entryId === group.entry.id && selectedFile.path === match.path;
|
|
310
|
+
rows.push(
|
|
311
|
+
<LineRow
|
|
312
|
+
key={`line:${group.entry.id}:${match.path}:${match.line}`}
|
|
313
|
+
id={optionId(index)}
|
|
314
|
+
match={match}
|
|
315
|
+
query={query}
|
|
316
|
+
isFocused={index === focusIndex}
|
|
317
|
+
isOpen={isOpen}
|
|
318
|
+
onOpen={() => onOpenFile(group.entry.id, match.path, { line: match.line })}
|
|
319
|
+
/>,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (group.truncated) {
|
|
324
|
+
rows.push(
|
|
325
|
+
<GroupNotice key={`t:${group.entry.id}`}>
|
|
326
|
+
Partial results — more matches exist than shown for this workspace.
|
|
327
|
+
</GroupNotice>,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (group.error) {
|
|
331
|
+
rows.push(
|
|
332
|
+
<GroupNotice key={`e:${group.entry.id}`} isError>
|
|
333
|
+
Couldn’t search: {group.error.message}
|
|
334
|
+
</GroupNotice>,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (matchIndex >= MAX_RESULTS) break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return rows;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function LineRow({
|
|
345
|
+
id,
|
|
346
|
+
match,
|
|
347
|
+
query,
|
|
348
|
+
isFocused,
|
|
349
|
+
isOpen,
|
|
350
|
+
onOpen,
|
|
351
|
+
}: {
|
|
352
|
+
readonly id: string;
|
|
353
|
+
readonly match: WorkspaceContentMatch;
|
|
354
|
+
readonly query: string;
|
|
355
|
+
readonly isFocused: boolean;
|
|
356
|
+
readonly isOpen: boolean;
|
|
357
|
+
readonly onOpen: () => void;
|
|
358
|
+
}) {
|
|
359
|
+
// The `role="option"` IS the interactive leaf of the listbox (keyboard
|
|
360
|
+
// activation flows through the combobox input's Enter handler +
|
|
361
|
+
// aria-activedescendant). It must not nest another interactive control — a
|
|
362
|
+
// button inside would violate WCAG 4.1.2 (axe `nested-interactive`). So the
|
|
363
|
+
// click handler and row styling live on the option itself.
|
|
364
|
+
return (
|
|
365
|
+
<li
|
|
366
|
+
id={id}
|
|
367
|
+
role="option"
|
|
368
|
+
aria-selected={isFocused}
|
|
369
|
+
aria-current={isOpen ? "true" : undefined}
|
|
370
|
+
onClick={onOpen}
|
|
371
|
+
className={cn(
|
|
372
|
+
"flex cursor-pointer items-baseline gap-2 py-0.5 pl-6 pr-3 text-xs transition-colors",
|
|
373
|
+
isFocused && "bg-muted",
|
|
374
|
+
isOpen && "bg-muted",
|
|
375
|
+
!isFocused && !isOpen && "hover:bg-muted",
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
<span className="w-8 shrink-0 text-right font-mono text-[0.65rem] tabular-nums text-muted-foreground-subtle">
|
|
379
|
+
{match.line}
|
|
380
|
+
</span>
|
|
381
|
+
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
382
|
+
<HighlightedPreview text={match.preview} query={query} />
|
|
383
|
+
</span>
|
|
384
|
+
</li>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Renders a preview line, emphasizing every case-insensitive occurrence of the
|
|
390
|
+
* query. Offsets are re-derived from the text (the backend carries no column),
|
|
391
|
+
* so highlighting is consistent with what the user sees.
|
|
392
|
+
*/
|
|
393
|
+
function HighlightedPreview({
|
|
394
|
+
text,
|
|
395
|
+
query,
|
|
396
|
+
}: {
|
|
397
|
+
readonly text: string;
|
|
398
|
+
readonly query: string;
|
|
399
|
+
}) {
|
|
400
|
+
const ranges = findHighlightRanges(text, query);
|
|
401
|
+
if (ranges.length === 0) return <>{text}</>;
|
|
402
|
+
|
|
403
|
+
const parts: ReactNode[] = [];
|
|
404
|
+
let cursor = 0;
|
|
405
|
+
ranges.forEach((range, i) => {
|
|
406
|
+
if (range.start > cursor) parts.push(text.slice(cursor, range.start));
|
|
407
|
+
parts.push(
|
|
408
|
+
<span key={i} className="font-semibold text-foreground">
|
|
409
|
+
{text.slice(range.start, range.end)}
|
|
410
|
+
</span>,
|
|
411
|
+
);
|
|
412
|
+
cursor = range.end;
|
|
413
|
+
});
|
|
414
|
+
if (cursor < text.length) parts.push(text.slice(cursor));
|
|
415
|
+
|
|
416
|
+
return <>{parts}</>;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function EntryHeader({ name }: { readonly name: string }) {
|
|
420
|
+
return (
|
|
421
|
+
<li
|
|
422
|
+
role="presentation"
|
|
423
|
+
className="truncate px-3 pb-0.5 pt-2 text-[0.6rem] font-medium uppercase tracking-wide text-muted-foreground"
|
|
424
|
+
>
|
|
425
|
+
{name}
|
|
426
|
+
</li>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function FileHeader({ path }: { readonly path: string }) {
|
|
431
|
+
const lastSlash = path.lastIndexOf("/");
|
|
432
|
+
const basename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
|
|
433
|
+
const dir = lastSlash >= 0 ? path.slice(0, lastSlash) : "";
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<li
|
|
437
|
+
role="presentation"
|
|
438
|
+
className="flex items-baseline gap-1.5 px-3 pb-0.5 pt-1.5 text-xs"
|
|
439
|
+
>
|
|
440
|
+
<span className="shrink-0 font-medium text-foreground">{basename}</span>
|
|
441
|
+
{dir && <span className="truncate text-[0.65rem] text-muted-foreground">{dir}</span>}
|
|
442
|
+
</li>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function GroupNotice({
|
|
447
|
+
children,
|
|
448
|
+
isError,
|
|
449
|
+
}: {
|
|
450
|
+
readonly children: ReactNode;
|
|
451
|
+
readonly isError?: boolean;
|
|
452
|
+
}) {
|
|
453
|
+
return (
|
|
454
|
+
<li
|
|
455
|
+
role="presentation"
|
|
456
|
+
className={cn(
|
|
457
|
+
"px-3 py-1 text-[0.65rem]",
|
|
458
|
+
isError ? "text-destructive" : "text-muted-foreground",
|
|
459
|
+
)}
|
|
460
|
+
>
|
|
461
|
+
{children}
|
|
462
|
+
</li>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function MessageState({ children }: { readonly children: ReactNode }) {
|
|
467
|
+
return (
|
|
468
|
+
<div className="flex h-full items-center justify-center p-8 text-center text-xs text-muted-foreground">
|
|
469
|
+
{children}
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
// Inline SVG icons (SDK independence — no lucide dependency)
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
function SearchIcon() {
|
|
479
|
+
return (
|
|
480
|
+
<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">
|
|
481
|
+
<circle cx="5.25" cy="5.25" r="3.5" />
|
|
482
|
+
<path d="M7.75 7.75L10.5 10.5" />
|
|
483
|
+
</svg>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function ClearIcon() {
|
|
488
|
+
return (
|
|
489
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
490
|
+
<path d="M3 3L9 9M9 3L3 9" />
|
|
491
|
+
</svg>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function Spinner() {
|
|
496
|
+
return (
|
|
497
|
+
<span
|
|
498
|
+
aria-hidden="true"
|
|
499
|
+
className="h-3 w-3 shrink-0 animate-spin rounded-full border border-muted-foreground border-t-transparent"
|
|
500
|
+
/>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
@@ -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>;
|