@stigmer/react 3.1.2 → 3.1.4
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 +37 -5
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -5
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +9 -9
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +18 -145
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.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/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 +0 -9
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +1 -11
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +3 -4
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +25 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +16 -10
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +31 -7
- 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/PlanStreamingCard.d.ts +7 -2
- package/execution/PlanStreamingCard.d.ts.map +1 -1
- package/execution/PlanStreamingCard.js +11 -6
- package/execution/PlanStreamingCard.js.map +1 -1
- package/execution/index.d.ts +12 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +6 -1
- 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/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +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/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +16 -3
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -2
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/library/detect-plan-artifact.d.ts +27 -9
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +26 -6
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +1 -1
- package/library/index.js.map +1 -1
- package/package.json +5 -4
- package/session/PlanEditor.js +1 -1
- package/session/PlanEditor.js.map +1 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +122 -30
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +14 -2
- package/session/plan-document.d.ts.map +1 -1
- package/session/plan-document.js +14 -3
- package/session/plan-document.js.map +1 -1
- package/session/usePlanDraft.d.ts +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +26 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +51 -6
- 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 +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/src/composer/SessionComposer.tsx +76 -7
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +49 -415
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +0 -62
- package/src/execution/MessageThread.tsx +29 -15
- package/src/execution/PlanArtifactCard.tsx +130 -52
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanStreamingCard.tsx +15 -11
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +1 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
- package/src/execution/__tests__/message-entry.test.tsx +56 -39
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +20 -2
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +17 -3
- package/src/index.ts +17 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/file-tree/FileTreeNode.tsx +13 -2
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
- package/src/library/detect-plan-artifact.ts +32 -10
- package/src/library/index.ts +2 -0
- package/src/session/PlanEditor.tsx +1 -1
- package/src/session/SessionViewer.tsx +227 -51
- package/src/session/__tests__/PlanEditor.test.tsx +4 -4
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +5 -1
- package/src/session/plan-document.ts +14 -3
- package/src/session/usePlanDraft.ts +1 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +80 -10
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/workspace/EditorTabs.tsx +38 -9
- package/src/workspace/ExplorerTree.tsx +10 -4
- package/src/workspace/FileViewer.tsx +27 -6
- package/src/workspace/WorkspaceContentSearch.tsx +44 -20
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileSearch.tsx +51 -28
- package/src/workspace/WorkspaceSurface.tsx +106 -48
- package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
- package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -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/styles.css +1 -1
- package/workspace/EditorTabs.d.ts +21 -3
- package/workspace/EditorTabs.d.ts.map +1 -1
- package/workspace/EditorTabs.js +18 -6
- package/workspace/EditorTabs.js.map +1 -1
- package/workspace/ExplorerTree.js +8 -4
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +19 -8
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +23 -2
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +23 -3
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSurface.d.ts.map +1 -1
- package/workspace/WorkspaceSurface.js +54 -18
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- package/src/execution/ArtifactCard.tsx +0 -320
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
ExecutionPhase,
|
|
10
10
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
11
11
|
import { StigmerContext } from "../../../context";
|
|
12
|
-
import { ArtifactsTab } from "../ArtifactsTab";
|
|
12
|
+
import { ArtifactsTab, type ArtifactsTabProps } from "../ArtifactsTab";
|
|
13
|
+
import type { SessionArtifactEntry } from "../../useSessionArtifacts";
|
|
13
14
|
|
|
14
15
|
function artifact(name: string) {
|
|
15
16
|
return create(ExecutionArtifactSchema, {
|
|
@@ -38,86 +39,149 @@ function createStigmerMock(): Stigmer {
|
|
|
38
39
|
} as unknown as Stigmer;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function renderTab(
|
|
42
|
-
onImplementPlan?: () => void,
|
|
43
|
-
onOpenPlan?: (executionId: string) => void,
|
|
44
|
-
) {
|
|
42
|
+
function renderTab(props: Partial<ArtifactsTabProps> = {}) {
|
|
45
43
|
return render(
|
|
46
44
|
<StigmerContext.Provider value={createStigmerMock()}>
|
|
47
|
-
<ArtifactsTab
|
|
48
|
-
executions={[execution]}
|
|
49
|
-
org="acme"
|
|
50
|
-
onImplementPlan={onImplementPlan}
|
|
51
|
-
onOpenPlan={onOpenPlan}
|
|
52
|
-
/>
|
|
45
|
+
<ArtifactsTab executions={[execution]} org="acme" {...props} />
|
|
53
46
|
</StigmerContext.Provider>,
|
|
54
47
|
);
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fireEvent.click(
|
|
50
|
+
/** Click the open target of a row by its artifact name. */
|
|
51
|
+
function openRow(name: string) {
|
|
52
|
+
fireEvent.click(screen.getByText(name));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Double-click the open target of a row by its artifact name (the pin gesture). */
|
|
56
|
+
function activateRow(name: string) {
|
|
57
|
+
fireEvent.doubleClick(screen.getByText(name));
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
afterEach(cleanup);
|
|
63
61
|
|
|
64
|
-
describe("ArtifactsTab —
|
|
65
|
-
it("
|
|
66
|
-
renderTab(vi.fn());
|
|
62
|
+
describe("ArtifactsTab — dense row list", () => {
|
|
63
|
+
it("renders one row per artifact with a Download control (no old 'Preview' button)", () => {
|
|
64
|
+
renderTab({ onOpenArtifact: vi.fn() });
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(2);
|
|
67
|
+
expect(screen.getByText("plan.md")).toBeTruthy();
|
|
68
|
+
expect(screen.getByText("notes.md")).toBeTruthy();
|
|
69
|
+
expect(screen.queryByText("Preview")).toBeNull();
|
|
70
|
+
expect(screen.getByLabelText("Download plan.md")).toBeTruthy();
|
|
71
|
+
expect(screen.getByLabelText("Download notes.md")).toBeTruthy();
|
|
72
|
+
});
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
it("renders the empty state with no artifacts", () => {
|
|
75
|
+
render(
|
|
76
|
+
<StigmerContext.Provider value={createStigmerMock()}>
|
|
77
|
+
<ArtifactsTab executions={[]} org="acme" />
|
|
78
|
+
</StigmerContext.Provider>,
|
|
79
|
+
);
|
|
80
|
+
expect(screen.getByText(/No artifacts yet/)).toBeTruthy();
|
|
72
81
|
});
|
|
82
|
+
});
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
describe("ArtifactsTab — open routing", () => {
|
|
85
|
+
it("routes a non-plan artifact to onOpenArtifact (no modal) when provided", () => {
|
|
86
|
+
const onOpenArtifact = vi.fn<(entry: SessionArtifactEntry) => void>();
|
|
87
|
+
renderTab({ onOpenArtifact, onOpenPlan: vi.fn() });
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
openRow("notes.md");
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
expect(
|
|
91
|
+
expect(onOpenArtifact).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(onOpenArtifact.mock.calls[0][0].artifact.name).toBe("notes.md");
|
|
93
|
+
expect(document.querySelector("dialog")).toBeNull();
|
|
81
94
|
});
|
|
82
95
|
|
|
83
|
-
it("
|
|
84
|
-
const
|
|
85
|
-
|
|
96
|
+
it("routes plan.md to onOpenPlan (never onOpenArtifact) when both are provided", () => {
|
|
97
|
+
const onOpenArtifact = vi.fn();
|
|
98
|
+
const onOpenPlan = vi.fn();
|
|
99
|
+
renderTab({ onOpenArtifact, onOpenPlan });
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
const dialog = document.querySelector("dialog")!;
|
|
89
|
-
fireEvent.click(within(dialog).getByText("Build from plan"));
|
|
101
|
+
openRow("plan.md");
|
|
90
102
|
|
|
91
|
-
expect(
|
|
103
|
+
expect(onOpenPlan).toHaveBeenCalledWith("aex_1");
|
|
104
|
+
expect(onOpenArtifact).not.toHaveBeenCalled();
|
|
105
|
+
expect(document.querySelector("dialog")).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("falls back to the modal for a non-plan artifact when onOpenArtifact is omitted", () => {
|
|
109
|
+
renderTab({ onOpenPlan: vi.fn() });
|
|
110
|
+
|
|
111
|
+
openRow("notes.md");
|
|
112
|
+
|
|
113
|
+
expect(document.querySelector("dialog")).toBeTruthy();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("falls back to the modal for plan.md when neither onOpenPlan nor onOpenArtifact is provided", () => {
|
|
117
|
+
renderTab();
|
|
118
|
+
|
|
119
|
+
openRow("plan.md");
|
|
120
|
+
|
|
121
|
+
expect(document.querySelector("dialog")).toBeTruthy();
|
|
92
122
|
});
|
|
93
123
|
});
|
|
94
124
|
|
|
95
|
-
describe("ArtifactsTab —
|
|
96
|
-
it("routes a plan
|
|
125
|
+
describe("ArtifactsTab — pin (double-click) routing", () => {
|
|
126
|
+
it("routes a non-plan double-click to onActivateArtifact (pin)", () => {
|
|
127
|
+
const onActivateArtifact = vi.fn<(entry: SessionArtifactEntry) => void>();
|
|
128
|
+
renderTab({ onOpenArtifact: vi.fn(), onActivateArtifact, onOpenPlan: vi.fn() });
|
|
129
|
+
|
|
130
|
+
activateRow("notes.md");
|
|
131
|
+
|
|
132
|
+
expect(onActivateArtifact).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(onActivateArtifact.mock.calls[0][0].artifact.name).toBe("notes.md");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("routes a plan.md double-click to onOpenPlan (its tab is already pinned), never onActivateArtifact", () => {
|
|
137
|
+
const onActivateArtifact = vi.fn();
|
|
97
138
|
const onOpenPlan = vi.fn();
|
|
98
|
-
renderTab(vi.fn(), onOpenPlan);
|
|
139
|
+
renderTab({ onOpenArtifact: vi.fn(), onActivateArtifact, onOpenPlan });
|
|
99
140
|
|
|
100
|
-
|
|
141
|
+
activateRow("plan.md");
|
|
101
142
|
|
|
102
143
|
expect(onOpenPlan).toHaveBeenCalledWith("aex_1");
|
|
103
|
-
expect(
|
|
144
|
+
expect(onActivateArtifact).not.toHaveBeenCalled();
|
|
104
145
|
});
|
|
105
146
|
|
|
106
|
-
it("
|
|
107
|
-
const
|
|
108
|
-
renderTab(
|
|
147
|
+
it("binds no double-click when onActivateArtifact is omitted (panel-less host) — a double-click is harmless", () => {
|
|
148
|
+
const onOpenArtifact = vi.fn();
|
|
149
|
+
renderTab({ onOpenArtifact });
|
|
109
150
|
|
|
110
|
-
|
|
151
|
+
// With no pin handler the row binds no onDoubleClick, so a dblclick fires
|
|
152
|
+
// nothing (in a real browser the leading single-clicks still preview).
|
|
153
|
+
expect(() => activateRow("notes.md")).not.toThrow();
|
|
154
|
+
expect(onOpenArtifact).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
111
157
|
|
|
112
|
-
|
|
113
|
-
|
|
158
|
+
describe("ArtifactsTab — modal fallback plan 'Build' wiring", () => {
|
|
159
|
+
it("shows 'Build' in the modal preview of a plan.md artifact (panel-less host)", () => {
|
|
160
|
+
renderTab({ onImplementPlan: vi.fn() });
|
|
161
|
+
|
|
162
|
+
openRow("plan.md");
|
|
163
|
+
|
|
164
|
+
const dialog = document.querySelector("dialog")!;
|
|
165
|
+
expect(within(dialog).getByText("Build")).toBeTruthy();
|
|
114
166
|
});
|
|
115
167
|
|
|
116
|
-
it("
|
|
117
|
-
renderTab(vi.fn());
|
|
168
|
+
it("does not show 'Build' in the modal preview of a non-plan artifact", () => {
|
|
169
|
+
renderTab({ onImplementPlan: vi.fn() });
|
|
118
170
|
|
|
119
|
-
|
|
171
|
+
openRow("notes.md");
|
|
120
172
|
|
|
121
|
-
|
|
173
|
+
const dialog = document.querySelector("dialog")!;
|
|
174
|
+
expect(within(dialog).queryByText("Build")).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("invokes onImplementPlan when 'Build' is clicked for a plan", () => {
|
|
178
|
+
const onImplementPlan = vi.fn();
|
|
179
|
+
renderTab({ onImplementPlan });
|
|
180
|
+
|
|
181
|
+
openRow("plan.md");
|
|
182
|
+
const dialog = document.querySelector("dialog")!;
|
|
183
|
+
fireEvent.click(within(dialog).getByText("Build"));
|
|
184
|
+
|
|
185
|
+
expect(onImplementPlan).toHaveBeenCalledTimes(1);
|
|
122
186
|
});
|
|
123
187
|
});
|
package/src/session/index.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type {
|
|
|
33
33
|
UseSessionConversationReturn,
|
|
34
34
|
} from "./useSessionConversation.js";
|
|
35
35
|
|
|
36
|
-
export { useSessionArtifacts } from "./useSessionArtifacts.js";
|
|
36
|
+
export { useSessionArtifacts, artifactKey } from "./useSessionArtifacts.js";
|
|
37
37
|
export type {
|
|
38
38
|
SessionArtifactEntry,
|
|
39
39
|
UseSessionArtifactsReturn,
|
|
@@ -127,6 +127,10 @@ export {
|
|
|
127
127
|
PLAN_DOCUMENT_PATH,
|
|
128
128
|
} from "./plan-document.js";
|
|
129
129
|
|
|
130
|
+
// Artifact document — the editor-area rendering of an execution artifact
|
|
131
|
+
// (mounted as an artifact-family virtual document) and the identity of its tabs.
|
|
132
|
+
export { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
|
|
133
|
+
|
|
130
134
|
// Selection types (for platform builders composing their own Inspect facet)
|
|
131
135
|
export type { SelectedThreadItem } from "../internal/store/selection-store.js";
|
|
132
136
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
// their module graph (DD-003 headless-first).
|
|
7
7
|
|
|
8
8
|
import { virtualEntryId } from "../internal/store/index.js";
|
|
9
|
-
import { PLAN_ARTIFACT_NAME } from "../library/detect-plan-artifact.js";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Virtual entry id of the plan document tab. One tab per session — the host
|
|
@@ -15,5 +14,17 @@ import { PLAN_ARTIFACT_NAME } from "../library/detect-plan-artifact.js";
|
|
|
15
14
|
*/
|
|
16
15
|
export const PLAN_DOCUMENT_ENTRY_ID = virtualEntryId("plan");
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The plan tab's path — doubles as its tab label.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately plan-AGNOSTIC ("Plan"), not the artifact's filename. The
|
|
21
|
+
* workspace surface identifies a virtual document's tab by
|
|
22
|
+
* `editorKey(entryId, path)`, so `path` is part of the tab's IDENTITY — and the
|
|
23
|
+
* session keeps exactly ONE plan tab that swaps which plan it shows (latest
|
|
24
|
+
* editable, or a superseded plan read-only). Now that plans carry unique,
|
|
25
|
+
* title-derived filenames (`<slug>_<id>.plan.md`), binding this label to any one
|
|
26
|
+
* plan's name would mint a new tab identity per plan and shatter the
|
|
27
|
+
* single-tab design. A stable, generic label is therefore required, not a
|
|
28
|
+
* stylistic choice — do not "fix" it to track the filename.
|
|
29
|
+
*/
|
|
30
|
+
export const PLAN_DOCUMENT_PATH = "Plan";
|
|
@@ -32,7 +32,7 @@ export interface PlanDraftController {
|
|
|
32
32
|
/**
|
|
33
33
|
* Snapshot reader for submit-time consumers: returns the current draft (or
|
|
34
34
|
* `null`) without the caller depending on draft state. Referentially stable
|
|
35
|
-
* for the lifetime of the component (DD-010) — "Build
|
|
35
|
+
* for the lifetime of the component (DD-010) — the "Build" action reads the
|
|
36
36
|
* draft through this at click time, so per-keystroke draft updates never
|
|
37
37
|
* re-bind the build callback or re-render the conversation column.
|
|
38
38
|
*/
|
|
@@ -41,11 +41,22 @@ export interface UseSessionArtifactsReturn {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
* available (
|
|
46
|
-
* falling back to `name` for older artifacts that predate the field.
|
|
44
|
+
* The stable identity of an artifact within a session. Uses `sandbox_path`
|
|
45
|
+
* when available (the artifact's filesystem identity within the session
|
|
46
|
+
* sandbox), falling back to `name` for older artifacts that predate the field.
|
|
47
|
+
*
|
|
48
|
+
* This is the SINGLE source of truth for artifact identity across three sites
|
|
49
|
+
* that must agree or a tab could fail to resolve its artifact:
|
|
50
|
+
* 1. deduplication here (latest execution wins per key),
|
|
51
|
+
* 2. the virtual-document tab `path` when an artifact opens in the editor area
|
|
52
|
+
* (see `SessionViewer` / `useSessionPanel.openArtifact`), and
|
|
53
|
+
* 3. the `artifactKey → entry` lookup that renders the open tab's document.
|
|
54
|
+
*
|
|
55
|
+
* Its basename doubles as the editor tab's label (paths render as their last
|
|
56
|
+
* `/` segment in `EditorTabs`), so `sandbox_path` gives a natural filename
|
|
57
|
+
* label with cross-directory disambiguation for free.
|
|
47
58
|
*/
|
|
48
|
-
function
|
|
59
|
+
export function artifactKey(artifact: ExecutionArtifact): string {
|
|
49
60
|
return artifact.sandboxPath || artifact.name;
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -102,7 +113,7 @@ export function useSessionArtifacts(
|
|
|
102
113
|
const terminal = isTerminalPhase(phase);
|
|
103
114
|
|
|
104
115
|
for (const artifact of execution.status?.artifacts ?? []) {
|
|
105
|
-
const key =
|
|
116
|
+
const key = artifactKey(artifact);
|
|
106
117
|
entryMap.set(key, {
|
|
107
118
|
artifact,
|
|
108
119
|
executionId,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
5
5
|
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
6
|
-
import type { FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
6
|
+
import type { FileChangeProgress, FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
7
7
|
import { ApprovalAction, ExecutionPhase, FileChangeSetStatus, FileDecisionAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
8
8
|
import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
9
9
|
import type { McpServerUsage as ProtoMcpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
@@ -97,7 +97,7 @@ export interface SendFollowUpOptions {
|
|
|
97
97
|
*/
|
|
98
98
|
readonly interactionMode?: "agent" | "plan";
|
|
99
99
|
/**
|
|
100
|
-
* Marks this execution as a
|
|
100
|
+
* Marks this execution as a Build-from-plan turn.
|
|
101
101
|
*
|
|
102
102
|
* @see {@link CreateAgentExecutionInput.buildFromPlan}
|
|
103
103
|
*/
|
|
@@ -117,6 +117,15 @@ export interface SendFollowUpOptions {
|
|
|
117
117
|
* @see {@link CreateAgentExecutionInput.workspaceFileRefs}
|
|
118
118
|
*/
|
|
119
119
|
readonly workspaceFileRefs?: string[];
|
|
120
|
+
/**
|
|
121
|
+
* ID of the execution this follow-up supersedes (edit-and-resubmit).
|
|
122
|
+
*
|
|
123
|
+
* The conversation read model hides the superseded execution so the
|
|
124
|
+
* edited message replaces the original in place.
|
|
125
|
+
*
|
|
126
|
+
* @see {@link CreateAgentExecutionInput.supersedesExecutionId}
|
|
127
|
+
*/
|
|
128
|
+
readonly supersedesExecutionId?: string;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
/**
|
|
@@ -129,7 +138,14 @@ export interface SendFollowUpOptions {
|
|
|
129
138
|
export interface UseSessionConversationReturn {
|
|
130
139
|
/** The session object, or null while loading. */
|
|
131
140
|
readonly session: Session | null;
|
|
132
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Executions in terminal phases, in chronological order.
|
|
143
|
+
*
|
|
144
|
+
* Excludes turns replaced via edit-and-resubmit (an execution whose id
|
|
145
|
+
* appears as another execution's `supersedesExecutionId`) — the edited
|
|
146
|
+
* message stands in the original's place. Superseded records remain in
|
|
147
|
+
* execution history surfaces; only the conversation view hides them.
|
|
148
|
+
*/
|
|
133
149
|
readonly completedExecutions: readonly AgentExecution[];
|
|
134
150
|
/** Currently streaming execution (stream or fetch fallback), or null. */
|
|
135
151
|
readonly activeStreamExecution: AgentExecution | null;
|
|
@@ -222,6 +238,13 @@ export interface UseSessionConversationReturn {
|
|
|
222
238
|
|
|
223
239
|
/** Captured change sets awaiting file review on the active execution, empty when none. */
|
|
224
240
|
readonly fileChangeSets: readonly FileChangeSet[];
|
|
241
|
+
/**
|
|
242
|
+
* Mid-run live capture (DD-32): the transient, non-authoritative snapshot of the
|
|
243
|
+
* workspace delta accumulating during the active turn ("N files changed so far").
|
|
244
|
+
* Undefined when no turn is currently capturing. Never decidable — the reviewed
|
|
245
|
+
* diff is {@link fileChangeSets}.
|
|
246
|
+
*/
|
|
247
|
+
readonly fileChangeProgress: FileChangeProgress | undefined;
|
|
225
248
|
/** Submit a file-review decision for a change set. The executionId is managed internally. */
|
|
226
249
|
readonly submitFileDecision: (
|
|
227
250
|
changeSetId: string,
|
|
@@ -484,12 +507,37 @@ export function useSessionConversation(
|
|
|
484
507
|
}
|
|
485
508
|
}, [activeExecutionId, stream.phase, refetch]);
|
|
486
509
|
|
|
510
|
+
// Executions replaced via edit-and-resubmit. The successor carries
|
|
511
|
+
// `spec.supersedes_execution_id`; hiding the superseded turn makes the
|
|
512
|
+
// edited message read as a single corrected exchange (in-place replace).
|
|
513
|
+
// The link must also be read off the live stream copy — right after a
|
|
514
|
+
// resubmit, the successor streams before the list refetch delivers it.
|
|
515
|
+
// Display-only: the raw `executions` list (and therefore active-id
|
|
516
|
+
// resolution above) is never filtered.
|
|
517
|
+
//
|
|
518
|
+
// Dep is the scalar link, NOT stream.execution: the stream object changes
|
|
519
|
+
// reference every frame, and rebuilding the Set per frame would hand
|
|
520
|
+
// MessageThread a fresh executions array mid-stream, defeating its
|
|
521
|
+
// memoization (DD-010).
|
|
522
|
+
const streamSupersededId =
|
|
523
|
+
stream.execution?.spec?.supersedesExecutionId || null;
|
|
524
|
+
const supersededIds = useMemo(() => {
|
|
525
|
+
const ids = new Set<string>();
|
|
526
|
+
for (const e of executions) {
|
|
527
|
+
const superseded = e.spec?.supersedesExecutionId;
|
|
528
|
+
if (superseded) ids.add(superseded);
|
|
529
|
+
}
|
|
530
|
+
if (streamSupersededId) ids.add(streamSupersededId);
|
|
531
|
+
return ids;
|
|
532
|
+
}, [executions, streamSupersededId]);
|
|
533
|
+
|
|
487
534
|
const completedExecutions = useMemo(() => {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
(
|
|
491
|
-
|
|
492
|
-
|
|
535
|
+
return executions.filter((e) => {
|
|
536
|
+
const id = e.metadata?.id ?? "";
|
|
537
|
+
if (activeExecutionId && id === activeExecutionId) return false;
|
|
538
|
+
return !supersededIds.has(id);
|
|
539
|
+
});
|
|
540
|
+
}, [executions, activeExecutionId, supersededIds]);
|
|
493
541
|
|
|
494
542
|
const fetchedActiveExecution = useMemo(() => {
|
|
495
543
|
if (!activeExecutionId) return null;
|
|
@@ -554,7 +602,14 @@ export function useSessionConversation(
|
|
|
554
602
|
// Capture for retry and clear any prior failure before the new attempt.
|
|
555
603
|
lastSendRef.current = { message, options };
|
|
556
604
|
setSendError(null);
|
|
557
|
-
|
|
605
|
+
// A Build-from-plan turn shows no optimistic bubble: its message is a
|
|
606
|
+
// machine-written label, not user prose, and the thread hides the turn
|
|
607
|
+
// entirely (the plan card's "Starting build…" state covers the send
|
|
608
|
+
// window). It is still set on FAILURE below, so a failed build send
|
|
609
|
+
// renders the failed-with-retry bubble instead of vanishing.
|
|
610
|
+
if (!options?.buildFromPlan) {
|
|
611
|
+
setPendingUserMessage(message);
|
|
612
|
+
}
|
|
558
613
|
|
|
559
614
|
try {
|
|
560
615
|
const needsSessionUpdate =
|
|
@@ -590,13 +645,17 @@ export function useSessionConversation(
|
|
|
590
645
|
buildFromPlan: options?.buildFromPlan,
|
|
591
646
|
autoApproveAll: options?.autoApproveAll,
|
|
592
647
|
workspaceFileRefs: options?.workspaceFileRefs,
|
|
648
|
+
supersedesExecutionId: options?.supersedesExecutionId,
|
|
593
649
|
});
|
|
594
650
|
setPendingExecutionId(result.executionId);
|
|
595
651
|
refetch();
|
|
596
652
|
} catch (err) {
|
|
597
653
|
// Surface the failure and KEEP the user's message visible (do not clear
|
|
598
654
|
// pendingUserMessage) so the turn renders as failed-with-retry instead
|
|
599
|
-
// of vanishing. Covers both the update() and create() paths.
|
|
655
|
+
// of vanishing. Covers both the update() and create() paths. For a
|
|
656
|
+
// build turn this is the FIRST time the message is set — failure is
|
|
657
|
+
// the one case where its label must become visible.
|
|
658
|
+
setPendingUserMessage(message);
|
|
600
659
|
setSendError(toError(err));
|
|
601
660
|
if (process.env.NODE_ENV !== "production") {
|
|
602
661
|
console.error("[useSessionConversation] sendFollowUp failed:", err);
|
|
@@ -641,6 +700,16 @@ export function useSessionConversation(
|
|
|
641
700
|
[activeStreamExecution],
|
|
642
701
|
);
|
|
643
702
|
|
|
703
|
+
// Mid-run live capture (DD-32): the transient, non-authoritative "N files
|
|
704
|
+
// changed so far" snapshot for the active turn. The server clears it once the
|
|
705
|
+
// turn's change set leaves CAPTURING, so its mere presence means a turn is
|
|
706
|
+
// still accumulating changes. Reference-stable per DD-010 (it rides the
|
|
707
|
+
// structurally-shared live status).
|
|
708
|
+
const fileChangeProgress = useMemo<FileChangeProgress | undefined>(
|
|
709
|
+
() => activeStreamExecution?.status?.fileChangeProgress,
|
|
710
|
+
[activeStreamExecution],
|
|
711
|
+
);
|
|
712
|
+
|
|
644
713
|
const submitFileDecision = useCallback(
|
|
645
714
|
async (
|
|
646
715
|
changeSetId: string,
|
|
@@ -685,6 +754,7 @@ export function useSessionConversation(
|
|
|
685
754
|
clearApprovalError,
|
|
686
755
|
|
|
687
756
|
fileChangeSets,
|
|
757
|
+
fileChangeProgress,
|
|
688
758
|
submitFileDecision,
|
|
689
759
|
submittingFileDecisionKeys: submittingDecisionKeys,
|
|
690
760
|
fileDecisionErrors,
|
|
@@ -410,6 +410,7 @@ export function useSessionPageFlow(
|
|
|
410
410
|
// not from the composer (the pre-arm toggle was removed).
|
|
411
411
|
autoApproveAll: autoApproveAll || undefined,
|
|
412
412
|
workspaceFileRefs: context?.workspaceFileRefs,
|
|
413
|
+
supersedesExecutionId: context?.supersedesExecutionId,
|
|
413
414
|
});
|
|
414
415
|
|
|
415
416
|
sessionVariables.clear();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import type { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
5
6
|
import { isTerminalPhase } from "../execution/execution-phases.js";
|
|
6
7
|
import type { SelectedThreadItem } from "../internal/store/selection-store.js";
|
|
7
8
|
import {
|
|
@@ -10,6 +11,8 @@ import {
|
|
|
10
11
|
type WorkspaceEditorsStore,
|
|
11
12
|
} from "../internal/store/index.js";
|
|
12
13
|
import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
|
|
14
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
|
|
15
|
+
import { artifactKey } from "./useSessionArtifacts.js";
|
|
13
16
|
|
|
14
17
|
/** Options for {@link useSessionPanel}. */
|
|
15
18
|
export interface UseSessionPanelOptions {
|
|
@@ -118,6 +121,25 @@ export interface SessionPanelController {
|
|
|
118
121
|
* both when a plan STARTS STREAMING and when it publishes (see `planKey`).
|
|
119
122
|
*/
|
|
120
123
|
readonly openPlanDocument: () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Open (or focus) an artifact as an editor-pane document tab and expand the
|
|
126
|
+
* panel. Uses the PREVIEW slot — like files and content-search hits, casual
|
|
127
|
+
* artifact browsing reuses one tab (single-click), and double-clicking the
|
|
128
|
+
* tab pins it. Distinct from `openPlanDocument`, which pins its single tab: a
|
|
129
|
+
* plan is the turn's deliverable, artifacts are browsable outputs. The
|
|
130
|
+
* artifact's {@link artifactKey} is the tab identity, shared with the
|
|
131
|
+
* `SurfaceVirtualDocument` the viewer builds for the open tab.
|
|
132
|
+
*/
|
|
133
|
+
readonly openArtifact: (artifact: ExecutionArtifact) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Pin an artifact's tab (clear its preview state) — the double-click half of
|
|
136
|
+
* the open/activate split, the encapsulated sibling of {@link openArtifact}.
|
|
137
|
+
* Delegates to the editors store's generic `pin`, exactly as the file tree
|
|
138
|
+
* pins via `pinEditor`: the leading single-click of the double-click has
|
|
139
|
+
* already opened the preview tab, so this promotes it to a persistent tab. A
|
|
140
|
+
* no-op if the artifact is not open.
|
|
141
|
+
*/
|
|
142
|
+
readonly pinArtifact: (artifact: ExecutionArtifact) => void;
|
|
121
143
|
/**
|
|
122
144
|
* Report the current thread-item selection. Called from an effect inside the
|
|
123
145
|
* panel subtree (the level that subscribes to the selection store), keeping
|
|
@@ -224,6 +246,21 @@ export function useSessionPanel({
|
|
|
224
246
|
[editorsStore],
|
|
225
247
|
);
|
|
226
248
|
|
|
249
|
+
const openArtifact = useCallback(
|
|
250
|
+
(artifact: ExecutionArtifact) => {
|
|
251
|
+
editorsStore.openPreview(ARTIFACT_DOCUMENT_ENTRY_ID, artifactKey(artifact));
|
|
252
|
+
setIsOpen(true);
|
|
253
|
+
},
|
|
254
|
+
[editorsStore],
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const pinArtifact = useCallback(
|
|
258
|
+
(artifact: ExecutionArtifact) => {
|
|
259
|
+
editorsStore.pin(ARTIFACT_DOCUMENT_ENTRY_ID, artifactKey(artifact));
|
|
260
|
+
},
|
|
261
|
+
[editorsStore],
|
|
262
|
+
);
|
|
263
|
+
|
|
227
264
|
const activateEditor = useCallback(
|
|
228
265
|
(entryId: string, path: string) => editorsStore.activate(entryId, path),
|
|
229
266
|
[editorsStore],
|
|
@@ -268,6 +305,8 @@ export function useSessionPanel({
|
|
|
268
305
|
pinEditor,
|
|
269
306
|
closeEditor,
|
|
270
307
|
openPlanDocument,
|
|
308
|
+
openArtifact,
|
|
309
|
+
pinArtifact,
|
|
271
310
|
notifySelection,
|
|
272
311
|
}),
|
|
273
312
|
[
|
|
@@ -282,6 +321,8 @@ export function useSessionPanel({
|
|
|
282
321
|
pinEditor,
|
|
283
322
|
closeEditor,
|
|
284
323
|
openPlanDocument,
|
|
324
|
+
openArtifact,
|
|
325
|
+
pinArtifact,
|
|
285
326
|
notifySelection,
|
|
286
327
|
],
|
|
287
328
|
);
|
|
@@ -6,7 +6,10 @@ import type { SurfaceRailView } from "../workspace/WorkspaceSurface.js";
|
|
|
6
6
|
import type { SelectedThreadItem } from "../internal/store/selection-store.js";
|
|
7
7
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
8
8
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useSessionArtifacts,
|
|
11
|
+
type SessionArtifactEntry,
|
|
12
|
+
} from "./useSessionArtifacts.js";
|
|
10
13
|
import { SetupTab, type SetupTabProps } from "./facets/SetupTab.js";
|
|
11
14
|
import { ChangesTab } from "./facets/ChangesTab.js";
|
|
12
15
|
import { ArtifactsTab } from "./facets/ArtifactsTab.js";
|
|
@@ -33,6 +36,17 @@ export interface UseSessionRailViewsOptions {
|
|
|
33
36
|
* of the preview modal.
|
|
34
37
|
*/
|
|
35
38
|
readonly onOpenPlan?: (executionId: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Open a (non-plan) artifact as an editor-pane document tab. Routed to the
|
|
41
|
+
* Artifacts facet so clicking an artifact opens it in the editor area (VS
|
|
42
|
+
* Code "each file is a tab") instead of the preview modal.
|
|
43
|
+
*/
|
|
44
|
+
readonly onOpenArtifact?: (entry: SessionArtifactEntry) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Pin a (non-plan) artifact's document tab — the double-click half of the
|
|
47
|
+
* Artifacts facet's open/activate split. Routed through to the facet's rows.
|
|
48
|
+
*/
|
|
49
|
+
readonly onActivateArtifact?: (entry: SessionArtifactEntry) => void;
|
|
36
50
|
/**
|
|
37
51
|
* Whether to offer the execution-derived facets (Changes / Artifacts /
|
|
38
52
|
* Usage). The launcher passes `false` — before a session exists there are no
|
|
@@ -60,6 +74,8 @@ export function useSessionRailViews({
|
|
|
60
74
|
onApplied,
|
|
61
75
|
onImplementPlan,
|
|
62
76
|
onOpenPlan,
|
|
77
|
+
onOpenArtifact,
|
|
78
|
+
onActivateArtifact,
|
|
63
79
|
includeExecutionFacets = true,
|
|
64
80
|
}: UseSessionRailViewsOptions): readonly SurfaceRailView[] {
|
|
65
81
|
const { hasWriteBacks, writeBackCount } = useSessionWriteBacks(allExecutions);
|
|
@@ -101,6 +117,8 @@ export function useSessionRailViews({
|
|
|
101
117
|
onApplied={onApplied}
|
|
102
118
|
onImplementPlan={onImplementPlan}
|
|
103
119
|
onOpenPlan={onOpenPlan}
|
|
120
|
+
onOpenArtifact={onOpenArtifact}
|
|
121
|
+
onActivateArtifact={onActivateArtifact}
|
|
104
122
|
/>
|
|
105
123
|
),
|
|
106
124
|
});
|
|
@@ -136,6 +154,8 @@ export function useSessionRailViews({
|
|
|
136
154
|
onApplied,
|
|
137
155
|
onImplementPlan,
|
|
138
156
|
onOpenPlan,
|
|
157
|
+
onOpenArtifact,
|
|
158
|
+
onActivateArtifact,
|
|
139
159
|
selectedItem,
|
|
140
160
|
]);
|
|
141
161
|
}
|