@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, useEffect, type KeyboardEvent } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
4
5
|
import type { UseWorkspaceEntriesReturn } from "./useWorkspaceEntries.js";
|
|
5
6
|
import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection.js";
|
|
6
7
|
import { GitHubRepoPicker } from "../github/GitHubRepoPicker.js";
|
|
@@ -136,11 +137,11 @@ export function WorkspaceEditor({
|
|
|
136
137
|
|
|
137
138
|
if (activePanel === "github" && enableGitHub) {
|
|
138
139
|
return (
|
|
139
|
-
<div className={
|
|
140
|
+
<div className={cn("space-y-2", className)}>
|
|
140
141
|
<button
|
|
141
142
|
type="button"
|
|
142
143
|
onClick={goBack}
|
|
143
|
-
className="inline-flex items-center gap-1 text-[0.65rem] text-muted-foreground hover:text-foreground
|
|
144
|
+
className="inline-flex items-center gap-1 rounded-sm text-[0.65rem] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
144
145
|
>
|
|
145
146
|
<ChevronLeftIcon />
|
|
146
147
|
Back
|
|
@@ -180,7 +181,7 @@ export function WorkspaceEditor({
|
|
|
180
181
|
// ---------------------------------------------------------------------------
|
|
181
182
|
|
|
182
183
|
return (
|
|
183
|
-
<div className={
|
|
184
|
+
<div className={cn("space-y-2", className)}>
|
|
184
185
|
{/* Entry list */}
|
|
185
186
|
{workspace.entries.length > 0 && (
|
|
186
187
|
<div className="relative">
|
|
@@ -202,10 +203,10 @@ export function WorkspaceEditor({
|
|
|
202
203
|
</span>
|
|
203
204
|
)}
|
|
204
205
|
<span
|
|
205
|
-
className={
|
|
206
|
+
className={cn(
|
|
206
207
|
"min-w-0 flex-1 truncate text-foreground",
|
|
207
|
-
entry.type === "local"
|
|
208
|
-
|
|
208
|
+
entry.type === "local" && "[direction:rtl] text-left",
|
|
209
|
+
)}
|
|
209
210
|
title={entry.name}
|
|
210
211
|
>
|
|
211
212
|
<bdi>{entry.name}</bdi>
|
|
@@ -214,7 +215,7 @@ export function WorkspaceEditor({
|
|
|
214
215
|
type="button"
|
|
215
216
|
onClick={() => workspace.remove(entry.id)}
|
|
216
217
|
disabled={disabled}
|
|
217
|
-
className="shrink-0 text-muted-foreground hover:text-destructive disabled:pointer-events-none"
|
|
218
|
+
className="shrink-0 rounded-sm text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none"
|
|
218
219
|
aria-label={`Remove ${entry.name}`}
|
|
219
220
|
>
|
|
220
221
|
<XIcon />
|
|
@@ -237,7 +238,7 @@ export function WorkspaceEditor({
|
|
|
237
238
|
if (path) workspace.addLocalPath(path);
|
|
238
239
|
}}
|
|
239
240
|
disabled={disabled}
|
|
240
|
-
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover disabled:pointer-events-none disabled:opacity-40"
|
|
241
|
+
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40"
|
|
241
242
|
>
|
|
242
243
|
<FolderIcon />
|
|
243
244
|
<span className="flex-1 text-left">Browse Folder</span>
|
|
@@ -248,7 +249,7 @@ export function WorkspaceEditor({
|
|
|
248
249
|
type="button"
|
|
249
250
|
onClick={() => setActivePanel("github")}
|
|
250
251
|
disabled={disabled}
|
|
251
|
-
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover disabled:pointer-events-none disabled:opacity-40"
|
|
252
|
+
className="flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-xs text-foreground transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40"
|
|
252
253
|
>
|
|
253
254
|
<GitHubIcon />
|
|
254
255
|
<span className="flex-1 text-left">Connect GitHub</span>
|
|
@@ -307,14 +308,14 @@ function GitHubPanel({
|
|
|
307
308
|
<button
|
|
308
309
|
type="button"
|
|
309
310
|
onClick={() => connection.connect(redirectUri, { popup: true })}
|
|
310
|
-
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground hover:text-foreground
|
|
311
|
+
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
311
312
|
>
|
|
312
313
|
Try again
|
|
313
314
|
</button>
|
|
314
315
|
<button
|
|
315
316
|
type="button"
|
|
316
317
|
onClick={() => connection.connect(redirectUri)}
|
|
317
|
-
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background hover:bg-foreground-hover
|
|
318
|
+
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background transition-colors hover:bg-foreground-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
318
319
|
>
|
|
319
320
|
<GitHubIcon />
|
|
320
321
|
<span>Continue with redirect</span>
|
|
@@ -325,7 +326,7 @@ function GitHubPanel({
|
|
|
325
326
|
<button
|
|
326
327
|
type="button"
|
|
327
328
|
onClick={() => connection.connect(redirectUri, { popup: true })}
|
|
328
|
-
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background hover:bg-foreground-hover
|
|
329
|
+
className="inline-flex items-center gap-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background transition-colors hover:bg-foreground-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
329
330
|
>
|
|
330
331
|
<GitHubIcon />
|
|
331
332
|
<span>Connect GitHub</span>
|
|
@@ -353,7 +354,7 @@ function GitHubPanel({
|
|
|
353
354
|
<button
|
|
354
355
|
type="button"
|
|
355
356
|
onClick={connection.disconnect}
|
|
356
|
-
className="text-[0.6rem] text-muted-foreground hover:text-destructive
|
|
357
|
+
className="rounded-sm text-[0.6rem] text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
357
358
|
>
|
|
358
359
|
Disconnect
|
|
359
360
|
</button>
|
|
@@ -392,6 +393,7 @@ function ManualGitPanel({
|
|
|
392
393
|
<div className="space-y-2">
|
|
393
394
|
<input
|
|
394
395
|
type="url"
|
|
396
|
+
aria-label="Git repository URL"
|
|
395
397
|
placeholder="https://github.com/org/repo.git"
|
|
396
398
|
value={url}
|
|
397
399
|
onChange={(e) => onUrlChange(e.target.value)}
|
|
@@ -401,6 +403,7 @@ function ManualGitPanel({
|
|
|
401
403
|
/>
|
|
402
404
|
<input
|
|
403
405
|
type="text"
|
|
406
|
+
aria-label="Branch (optional)"
|
|
404
407
|
placeholder="Branch (optional)"
|
|
405
408
|
value={branch}
|
|
406
409
|
onChange={(e) => onBranchChange(e.target.value)}
|
|
@@ -411,7 +414,7 @@ function ManualGitPanel({
|
|
|
411
414
|
<button
|
|
412
415
|
type="button"
|
|
413
416
|
onClick={onCancel}
|
|
414
|
-
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground hover:text-foreground
|
|
417
|
+
className="rounded-md px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
415
418
|
>
|
|
416
419
|
Cancel
|
|
417
420
|
</button>
|
|
@@ -419,7 +422,7 @@ function ManualGitPanel({
|
|
|
419
422
|
type="button"
|
|
420
423
|
onClick={onAdd}
|
|
421
424
|
disabled={!url.trim()}
|
|
422
|
-
className="rounded-md bg-primary px-2.5 py-1 text-xs text-primary-foreground hover:bg-primary-hover
|
|
425
|
+
className="rounded-md bg-primary px-2.5 py-1 text-xs text-primary-foreground transition-colors hover:bg-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40"
|
|
423
426
|
>
|
|
424
427
|
Add
|
|
425
428
|
</button>
|
|
@@ -453,7 +456,7 @@ function GitHubConnectingState({ onCancel }: { onCancel: () => void }) {
|
|
|
453
456
|
<button
|
|
454
457
|
type="button"
|
|
455
458
|
onClick={onCancel}
|
|
456
|
-
className="text-[0.65rem] text-muted-foreground hover:text-foreground
|
|
459
|
+
className="rounded-sm text-[0.65rem] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
457
460
|
>
|
|
458
461
|
Cancel
|
|
459
462
|
</button>
|
|
@@ -12,6 +12,17 @@ export interface WorkspaceFileEntry {
|
|
|
12
12
|
readonly path: string;
|
|
13
13
|
/** Whether this entry is a directory. */
|
|
14
14
|
readonly isDirectory: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Marks this entry as an advisory notice rather than an openable file — for
|
|
17
|
+
* example a "listing truncated" message when a repository exceeds the
|
|
18
|
+
* backend's entry limit. When `true`, `path` carries the human-readable
|
|
19
|
+
* message. Consumers must not treat a notice as a real file: it is stripped
|
|
20
|
+
* from the openable listing (never a tree leaf, never a search hit) and is
|
|
21
|
+
* surfaced instead as a single `truncated` banner (see `workspaceListingCache`).
|
|
22
|
+
*
|
|
23
|
+
* Optional and additive: listers that never truncate simply omit it.
|
|
24
|
+
*/
|
|
25
|
+
readonly notice?: true;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
/**
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Content-fetch capability — the byte-reading sibling of WorkspaceFileLister.
|
|
2
|
+
// Domain: workspace. Injected by the host (DD-004) so the SDK never learns how
|
|
3
|
+
// bytes are fetched: GitHub Contents/blob on web, Tauri fs on desktop.
|
|
4
|
+
|
|
5
|
+
import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared soft cap for a single file read, enforced identically on web and
|
|
9
|
+
* desktop so both substrates truncate at the same boundary. Files larger than
|
|
10
|
+
* this return their first {@link MAX_WORKSPACE_FILE_READ_BYTES} bytes in `text`
|
|
11
|
+
* with `truncated: true`; `size` still reports the full file size.
|
|
12
|
+
*/
|
|
13
|
+
export const MAX_WORKSPACE_FILE_READ_BYTES = 1_048_576; // 1 MiB
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decoded content (plus metadata) for a single workspace file.
|
|
17
|
+
*
|
|
18
|
+
* `text` is `null` whenever the bytes are not displayable as UTF-8 — either
|
|
19
|
+
* binary (`isBinary`) or an undecodable byte sequence (`encoding: "unknown"`).
|
|
20
|
+
* The renderer uses `isBinary`/`text === null` for its fallback state.
|
|
21
|
+
*/
|
|
22
|
+
export interface WorkspaceFileContent {
|
|
23
|
+
/** Decoded UTF-8 text, or `null` when binary or undecodable. */
|
|
24
|
+
readonly text: string | null;
|
|
25
|
+
/** Whether the bytes were detected as binary (NUL-byte heuristic). */
|
|
26
|
+
readonly isBinary: boolean;
|
|
27
|
+
/** Full file size in bytes — independent of truncation. */
|
|
28
|
+
readonly size: number;
|
|
29
|
+
/** How the backend delivered the bytes. `"none"` is GitHub's >1 MB signal. */
|
|
30
|
+
readonly encoding: "utf-8" | "base64" | "none" | "unknown";
|
|
31
|
+
/** `true` when `text` holds only the first {@link MAX_WORKSPACE_FILE_READ_BYTES} bytes. */
|
|
32
|
+
readonly truncated?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Platform-injected callback that reads the content of a single workspace file.
|
|
37
|
+
*
|
|
38
|
+
* The byte-reading sibling of {@link WorkspaceFileLister}:
|
|
39
|
+
* the SDK calls it when a user opens a file; the platform decides *how* the
|
|
40
|
+
* bytes are fetched (GitHub on web, Tauri on desktop).
|
|
41
|
+
*
|
|
42
|
+
* Two distinct failure axes — do not conflate them:
|
|
43
|
+
* - Returns `null` **only** when the substrate is unsupported for this entry
|
|
44
|
+
* (git-on-desktop, local-on-web, missing token, non-GitHub URL). This is the
|
|
45
|
+
* honest "unavailable here" state, mirroring the lister's null contract.
|
|
46
|
+
* - **Throws** on a genuine failure (404, network error, 5xx, unreadable file,
|
|
47
|
+
* a directory path). Consumers catch this into a distinct error state — a
|
|
48
|
+
* clicked file that 404s is "failed to load," not "unsupported here."
|
|
49
|
+
*
|
|
50
|
+
* `path` is repo-relative for git entries and root-relative for local entries —
|
|
51
|
+
* exactly the strings the matching `WorkspaceFileLister` emits, so a file-tree
|
|
52
|
+
* node path can be handed straight to the reader with no transformation.
|
|
53
|
+
*/
|
|
54
|
+
export type WorkspaceFileReader = (
|
|
55
|
+
entry: WorkspaceEntry,
|
|
56
|
+
path: string,
|
|
57
|
+
) => Promise<WorkspaceFileContent | null>;
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type KeyboardEvent,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { cn } from "@stigmer/theme";
|
|
13
|
+
import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
|
|
14
|
+
import type { WorkspaceFileLister } from "./WorkspaceFileLister.js";
|
|
15
|
+
import type { SelectedWorkspaceFile } from "../internal/store/workspace-file-selection-store.js";
|
|
16
|
+
import {
|
|
17
|
+
useWorkspaceFileSearch,
|
|
18
|
+
type WorkspaceFileSearchGroup,
|
|
19
|
+
} from "./useWorkspaceFileSearch.js";
|
|
20
|
+
import type { WorkspaceFileMatch } from "./matchWorkspaceFiles.js";
|
|
21
|
+
|
|
22
|
+
/** Cap on rendered rows — keeps the DOM bounded on huge repositories. */
|
|
23
|
+
const MAX_RESULTS = 200;
|
|
24
|
+
|
|
25
|
+
/** Props for {@link WorkspaceFileSearch}. */
|
|
26
|
+
export interface WorkspaceFileSearchProps {
|
|
27
|
+
/** All workspace entries to search across. */
|
|
28
|
+
readonly entries: readonly WorkspaceEntry[];
|
|
29
|
+
/** Platform-injected file lister. `undefined` → search is unavailable. */
|
|
30
|
+
readonly lister: WorkspaceFileLister | undefined;
|
|
31
|
+
/** Opens a result in the viewer. */
|
|
32
|
+
readonly onOpenFile: (entryId: string, path: string) => void;
|
|
33
|
+
/** The file currently open in the viewer, highlighted in the results. */
|
|
34
|
+
readonly selectedFile?: SelectedWorkspaceFile | null;
|
|
35
|
+
/** Additional CSS classes for the root element. */
|
|
36
|
+
readonly className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A rendered result, carrying its owning entry and flat index for keyboard nav. */
|
|
40
|
+
interface FlatResult {
|
|
41
|
+
readonly entryId: string;
|
|
42
|
+
readonly entry: WorkspaceEntry;
|
|
43
|
+
readonly match: WorkspaceFileMatch;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Workspace-wide filename/path search surface.
|
|
48
|
+
*
|
|
49
|
+
* Searches the already-loaded listings across every workspace entry (via
|
|
50
|
+
* {@link useWorkspaceFileSearch}) and opens a hit in the viewer through
|
|
51
|
+
* `onOpenFile` — the same seam a file-tree click uses. Filename-first by design;
|
|
52
|
+
* content search is a separate substrate-specific capability (DD-09).
|
|
53
|
+
*
|
|
54
|
+
* Keyboard/a11y follows the platform's combobox+listbox pattern (see
|
|
55
|
+
* `mcp-server/McpServerPicker`): the input is a `role="combobox"` driving
|
|
56
|
+
* `aria-activedescendant`, results are a `role="listbox"` of `role="option"`
|
|
57
|
+
* rows, and ArrowUp/Down move a virtual focus with Enter to open — focus stays
|
|
58
|
+
* in the input. All visual properties flow through `--stgm-*` tokens (DD-005).
|
|
59
|
+
*/
|
|
60
|
+
export function WorkspaceFileSearch({
|
|
61
|
+
entries,
|
|
62
|
+
lister,
|
|
63
|
+
onOpenFile,
|
|
64
|
+
selectedFile,
|
|
65
|
+
className,
|
|
66
|
+
}: WorkspaceFileSearchProps) {
|
|
67
|
+
const [query, setQuery] = useState("");
|
|
68
|
+
const [focusIndex, setFocusIndex] = useState(-1);
|
|
69
|
+
|
|
70
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
71
|
+
const listboxId = useId();
|
|
72
|
+
const optionId = useCallback(
|
|
73
|
+
(index: number) => `${listboxId}-opt-${index}`,
|
|
74
|
+
[listboxId],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const { groups, totalMatches, isLoading, isUnsupported } =
|
|
78
|
+
useWorkspaceFileSearch({ entries, lister, query });
|
|
79
|
+
|
|
80
|
+
// Flat, capped view of every rendered match — the index space the keyboard
|
|
81
|
+
// and `aria-activedescendant` share with the grouped render below.
|
|
82
|
+
const flatResults = useMemo<readonly FlatResult[]>(() => {
|
|
83
|
+
const flat: FlatResult[] = [];
|
|
84
|
+
for (const group of groups) {
|
|
85
|
+
for (const match of group.matches) {
|
|
86
|
+
if (flat.length >= MAX_RESULTS) return flat;
|
|
87
|
+
flat.push({ entryId: group.entry.id, entry: group.entry, match });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return flat;
|
|
91
|
+
}, [groups]);
|
|
92
|
+
|
|
93
|
+
// A new query invalidates the previous virtual focus.
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setFocusIndex(-1);
|
|
96
|
+
}, [query]);
|
|
97
|
+
|
|
98
|
+
// Autofocus on mount — the user switched into Search intending to type.
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
inputRef.current?.focus();
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
// Keep the virtually-focused option in view (no-op where unsupported, e.g. tests).
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (focusIndex < 0) return;
|
|
106
|
+
const el =
|
|
107
|
+
typeof document !== "undefined"
|
|
108
|
+
? document.getElementById(optionId(focusIndex))
|
|
109
|
+
: null;
|
|
110
|
+
el?.scrollIntoView?.({ block: "nearest" });
|
|
111
|
+
}, [focusIndex, optionId]);
|
|
112
|
+
|
|
113
|
+
const handleKeyDown = useCallback(
|
|
114
|
+
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
115
|
+
if (e.key === "ArrowDown") {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
setFocusIndex((prev) =>
|
|
118
|
+
prev < flatResults.length - 1 ? prev + 1 : prev,
|
|
119
|
+
);
|
|
120
|
+
} else if (e.key === "ArrowUp") {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
setFocusIndex((prev) => (prev > 0 ? prev - 1 : -1));
|
|
123
|
+
} else if (e.key === "Enter") {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
const target = flatResults[focusIndex];
|
|
126
|
+
if (target) onOpenFile(target.entryId, target.match.path);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
[flatResults, focusIndex, onOpenFile],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const trimmed = query.trim();
|
|
133
|
+
const showGroupHeaders = groups.length > 1;
|
|
134
|
+
|
|
135
|
+
// A concise, screen-reader-only summary of the current result state. It lives
|
|
136
|
+
// in a persistent polite live region (below) so status changes are announced
|
|
137
|
+
// even as the visible states — which live in separate, unmounting branches —
|
|
138
|
+
// swap. Deliberately terse: never the result list itself, only its shape.
|
|
139
|
+
const statusMessage = isUnsupported
|
|
140
|
+
? ""
|
|
141
|
+
: trimmed.length === 0
|
|
142
|
+
? ""
|
|
143
|
+
: isLoading && totalMatches === 0 && groups.length === 0
|
|
144
|
+
? "Searching files…"
|
|
145
|
+
: groups.length === 0
|
|
146
|
+
? `No files matching ${trimmed}.`
|
|
147
|
+
: totalMatches > flatResults.length
|
|
148
|
+
? `Showing the first ${flatResults.length} of ${totalMatches} matching files.`
|
|
149
|
+
: `${flatResults.length} matching ${flatResults.length === 1 ? "file" : "files"}.`;
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className={cn("flex h-full flex-col", className)}>
|
|
153
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
154
|
+
{statusMessage}
|
|
155
|
+
</div>
|
|
156
|
+
<div className="flex items-center gap-1.5 border-b border-border px-2 py-1.5">
|
|
157
|
+
<SearchIcon />
|
|
158
|
+
<input
|
|
159
|
+
ref={inputRef}
|
|
160
|
+
type="text"
|
|
161
|
+
role="combobox"
|
|
162
|
+
aria-expanded
|
|
163
|
+
aria-controls={listboxId}
|
|
164
|
+
aria-activedescendant={
|
|
165
|
+
focusIndex >= 0 ? optionId(focusIndex) : undefined
|
|
166
|
+
}
|
|
167
|
+
value={query}
|
|
168
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
169
|
+
onKeyDown={handleKeyDown}
|
|
170
|
+
placeholder="Search files by name…"
|
|
171
|
+
disabled={isUnsupported}
|
|
172
|
+
className={cn(
|
|
173
|
+
"min-w-0 flex-1 bg-transparent text-xs text-foreground",
|
|
174
|
+
"placeholder:text-muted-foreground",
|
|
175
|
+
"focus-visible:outline-none disabled:cursor-not-allowed",
|
|
176
|
+
)}
|
|
177
|
+
aria-label="Search workspace files by name"
|
|
178
|
+
/>
|
|
179
|
+
{trimmed.length > 0 && (
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
onClick={() => {
|
|
183
|
+
setQuery("");
|
|
184
|
+
inputRef.current?.focus();
|
|
185
|
+
}}
|
|
186
|
+
className="shrink-0 text-muted-foreground transition-colors hover:text-foreground"
|
|
187
|
+
aria-label="Clear search"
|
|
188
|
+
>
|
|
189
|
+
<ClearIcon />
|
|
190
|
+
</button>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
195
|
+
{isUnsupported ? (
|
|
196
|
+
<MessageState>File search isn’t available here.</MessageState>
|
|
197
|
+
) : trimmed.length === 0 ? (
|
|
198
|
+
<MessageState>
|
|
199
|
+
Search files by name across the whole workspace.
|
|
200
|
+
</MessageState>
|
|
201
|
+
) : isLoading && totalMatches === 0 && groups.length === 0 ? (
|
|
202
|
+
<MessageState>Searching…</MessageState>
|
|
203
|
+
) : groups.length === 0 ? (
|
|
204
|
+
<MessageState>
|
|
205
|
+
No files matching “{trimmed}”.
|
|
206
|
+
</MessageState>
|
|
207
|
+
) : (
|
|
208
|
+
<>
|
|
209
|
+
<ul
|
|
210
|
+
id={listboxId}
|
|
211
|
+
role="listbox"
|
|
212
|
+
aria-label="Search results"
|
|
213
|
+
className="py-0.5"
|
|
214
|
+
>
|
|
215
|
+
{renderRows({
|
|
216
|
+
groups,
|
|
217
|
+
showGroupHeaders,
|
|
218
|
+
selectedFile: selectedFile ?? null,
|
|
219
|
+
focusIndex,
|
|
220
|
+
optionId,
|
|
221
|
+
onOpenFile,
|
|
222
|
+
})}
|
|
223
|
+
</ul>
|
|
224
|
+
{totalMatches > flatResults.length && (
|
|
225
|
+
<p className="border-t border-border px-3 py-1.5 text-[0.65rem] text-muted-foreground">
|
|
226
|
+
Showing the first {flatResults.length} of {totalMatches} matches
|
|
227
|
+
— refine your search to narrow the list.
|
|
228
|
+
</p>
|
|
229
|
+
)}
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Result rows — a single pass over groups that keeps the flat match index
|
|
239
|
+
// aligned with `flatResults` (and thus keyboard focus / aria-activedescendant).
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
function renderRows({
|
|
243
|
+
groups,
|
|
244
|
+
showGroupHeaders,
|
|
245
|
+
selectedFile,
|
|
246
|
+
focusIndex,
|
|
247
|
+
optionId,
|
|
248
|
+
onOpenFile,
|
|
249
|
+
}: {
|
|
250
|
+
readonly groups: readonly WorkspaceFileSearchGroup[];
|
|
251
|
+
readonly showGroupHeaders: boolean;
|
|
252
|
+
readonly selectedFile: SelectedWorkspaceFile | null;
|
|
253
|
+
readonly focusIndex: number;
|
|
254
|
+
readonly optionId: (index: number) => string;
|
|
255
|
+
readonly onOpenFile: (entryId: string, path: string) => void;
|
|
256
|
+
}) {
|
|
257
|
+
const rows: React.ReactNode[] = [];
|
|
258
|
+
let matchIndex = 0;
|
|
259
|
+
|
|
260
|
+
for (const group of groups) {
|
|
261
|
+
if (showGroupHeaders) {
|
|
262
|
+
rows.push(<GroupHeader key={`h:${group.entry.id}`} name={group.entry.name} />);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for (const match of group.matches) {
|
|
266
|
+
if (matchIndex >= MAX_RESULTS) break;
|
|
267
|
+
const index = matchIndex++;
|
|
268
|
+
const isOpen =
|
|
269
|
+
selectedFile?.entryId === group.entry.id &&
|
|
270
|
+
selectedFile.path === match.path;
|
|
271
|
+
rows.push(
|
|
272
|
+
<ResultRow
|
|
273
|
+
key={`${group.entry.id}:${match.path}`}
|
|
274
|
+
id={optionId(index)}
|
|
275
|
+
match={match}
|
|
276
|
+
isFocused={index === focusIndex}
|
|
277
|
+
isOpen={isOpen}
|
|
278
|
+
onOpen={() => onOpenFile(group.entry.id, match.path)}
|
|
279
|
+
/>,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (group.truncated) {
|
|
284
|
+
rows.push(
|
|
285
|
+
<GroupNotice key={`t:${group.entry.id}`}>
|
|
286
|
+
Partial listing — too many files to search in full.
|
|
287
|
+
</GroupNotice>,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (group.error) {
|
|
291
|
+
rows.push(
|
|
292
|
+
<GroupNotice key={`e:${group.entry.id}`} isError>
|
|
293
|
+
Couldn’t list files: {group.error.message}
|
|
294
|
+
</GroupNotice>,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (matchIndex >= MAX_RESULTS) break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return rows;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function ResultRow({
|
|
305
|
+
id,
|
|
306
|
+
match,
|
|
307
|
+
isFocused,
|
|
308
|
+
isOpen,
|
|
309
|
+
onOpen,
|
|
310
|
+
}: {
|
|
311
|
+
readonly id: string;
|
|
312
|
+
readonly match: WorkspaceFileMatch;
|
|
313
|
+
readonly isFocused: boolean;
|
|
314
|
+
readonly isOpen: boolean;
|
|
315
|
+
readonly onOpen: () => void;
|
|
316
|
+
}) {
|
|
317
|
+
const lastSlash = match.path.lastIndexOf("/");
|
|
318
|
+
const basename = match.path.slice(lastSlash + 1);
|
|
319
|
+
const dir = lastSlash >= 0 ? match.path.slice(0, lastSlash) : "";
|
|
320
|
+
|
|
321
|
+
// The `role="option"` IS the interactive leaf of the listbox (keyboard
|
|
322
|
+
// activation flows through the combobox input's Enter handler +
|
|
323
|
+
// aria-activedescendant). It must not nest another interactive control — a
|
|
324
|
+
// button inside would violate WCAG 4.1.2 (axe `nested-interactive`). So the
|
|
325
|
+
// click handler and row styling live on the option itself.
|
|
326
|
+
return (
|
|
327
|
+
<li
|
|
328
|
+
id={id}
|
|
329
|
+
role="option"
|
|
330
|
+
aria-selected={isFocused}
|
|
331
|
+
aria-current={isOpen ? "true" : undefined}
|
|
332
|
+
onClick={onOpen}
|
|
333
|
+
className={cn(
|
|
334
|
+
"flex cursor-pointer items-baseline gap-1.5 px-3 py-1 text-xs transition-colors",
|
|
335
|
+
isFocused && "bg-muted",
|
|
336
|
+
isOpen && "bg-muted font-medium text-foreground",
|
|
337
|
+
!isFocused && !isOpen && "hover:bg-muted",
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
<span className="truncate text-foreground">
|
|
341
|
+
<HighlightedPath
|
|
342
|
+
text={basename}
|
|
343
|
+
offset={lastSlash + 1}
|
|
344
|
+
matchStart={match.matchStart}
|
|
345
|
+
matchEnd={match.matchEnd}
|
|
346
|
+
/>
|
|
347
|
+
</span>
|
|
348
|
+
{dir && (
|
|
349
|
+
<span className="truncate text-[0.65rem] text-muted-foreground">
|
|
350
|
+
<HighlightedPath
|
|
351
|
+
text={dir}
|
|
352
|
+
offset={0}
|
|
353
|
+
matchStart={match.matchStart}
|
|
354
|
+
matchEnd={match.matchEnd}
|
|
355
|
+
/>
|
|
356
|
+
</span>
|
|
357
|
+
)}
|
|
358
|
+
</li>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Renders a slice of the path (`text`, starting at `offset` within the full
|
|
364
|
+
* path), emphasizing the intersection with the match range `[matchStart,
|
|
365
|
+
* matchEnd)`. Non-overlapping slices render plain.
|
|
366
|
+
*/
|
|
367
|
+
function HighlightedPath({
|
|
368
|
+
text,
|
|
369
|
+
offset,
|
|
370
|
+
matchStart,
|
|
371
|
+
matchEnd,
|
|
372
|
+
}: {
|
|
373
|
+
readonly text: string;
|
|
374
|
+
readonly offset: number;
|
|
375
|
+
readonly matchStart: number;
|
|
376
|
+
readonly matchEnd: number;
|
|
377
|
+
}) {
|
|
378
|
+
const localStart = Math.max(0, matchStart - offset);
|
|
379
|
+
const localEnd = Math.min(text.length, matchEnd - offset);
|
|
380
|
+
|
|
381
|
+
if (localStart >= localEnd) return <>{text}</>;
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<>
|
|
385
|
+
{text.slice(0, localStart)}
|
|
386
|
+
<span className="font-semibold text-foreground">
|
|
387
|
+
{text.slice(localStart, localEnd)}
|
|
388
|
+
</span>
|
|
389
|
+
{text.slice(localEnd)}
|
|
390
|
+
</>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function GroupHeader({ name }: { readonly name: string }) {
|
|
395
|
+
return (
|
|
396
|
+
<li
|
|
397
|
+
role="presentation"
|
|
398
|
+
className="truncate px-3 pb-0.5 pt-2 text-[0.6rem] font-medium uppercase tracking-wide text-muted-foreground"
|
|
399
|
+
>
|
|
400
|
+
{name}
|
|
401
|
+
</li>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function GroupNotice({
|
|
406
|
+
children,
|
|
407
|
+
isError,
|
|
408
|
+
}: {
|
|
409
|
+
readonly children: React.ReactNode;
|
|
410
|
+
readonly isError?: boolean;
|
|
411
|
+
}) {
|
|
412
|
+
return (
|
|
413
|
+
<li
|
|
414
|
+
role="presentation"
|
|
415
|
+
className={cn(
|
|
416
|
+
"px-3 py-1 text-[0.65rem]",
|
|
417
|
+
isError ? "text-destructive" : "text-muted-foreground",
|
|
418
|
+
)}
|
|
419
|
+
>
|
|
420
|
+
{children}
|
|
421
|
+
</li>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function MessageState({ children }: { readonly children: React.ReactNode }) {
|
|
426
|
+
return (
|
|
427
|
+
<div className="flex h-full items-center justify-center p-8 text-center text-xs text-muted-foreground">
|
|
428
|
+
{children}
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// Inline SVG icons (SDK independence — no lucide dependency)
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
|
|
437
|
+
function SearchIcon() {
|
|
438
|
+
return (
|
|
439
|
+
<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">
|
|
440
|
+
<circle cx="5.25" cy="5.25" r="3.5" />
|
|
441
|
+
<path d="M7.75 7.75L10.5 10.5" />
|
|
442
|
+
</svg>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function ClearIcon() {
|
|
447
|
+
return (
|
|
448
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
449
|
+
<path d="M3 3L9 9M9 3L3 9" />
|
|
450
|
+
</svg>
|
|
451
|
+
);
|
|
452
|
+
}
|