@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { StigmerContext } from "../../context";
|
|
6
|
+
import { ModelRegistryContext } from "../../models/ModelRegistryContext";
|
|
7
|
+
import { SessionComposer } from "../SessionComposer";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
function createMinimalStigmerMock(): Stigmer {
|
|
14
|
+
return {
|
|
15
|
+
agentExecution: { uploadAttachment: vi.fn() },
|
|
16
|
+
environment: { getPersonal: vi.fn().mockResolvedValue(null) },
|
|
17
|
+
baseUrl: "http://localhost:8080",
|
|
18
|
+
getAuthCredential: vi.fn().mockResolvedValue("test-token"),
|
|
19
|
+
config: {
|
|
20
|
+
baseUrl: "http://localhost:8080",
|
|
21
|
+
getAccessToken: vi.fn().mockResolvedValue(""),
|
|
22
|
+
},
|
|
23
|
+
} as unknown as Stigmer;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createWrapper(client: Stigmer) {
|
|
27
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
28
|
+
return (
|
|
29
|
+
<StigmerContext.Provider value={client}>
|
|
30
|
+
<ModelRegistryContext.Provider
|
|
31
|
+
value={{ models: [], isLoading: false, error: null, refetch: vi.fn() }}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</ModelRegistryContext.Provider>
|
|
35
|
+
</StigmerContext.Provider>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderComposer(
|
|
41
|
+
props?: Partial<React.ComponentProps<typeof SessionComposer>>,
|
|
42
|
+
) {
|
|
43
|
+
const client = createMinimalStigmerMock();
|
|
44
|
+
const onSubmit = vi.fn();
|
|
45
|
+
const result = render(
|
|
46
|
+
<SessionComposer onSubmit={onSubmit} {...props} />,
|
|
47
|
+
{ wrapper: createWrapper(client) },
|
|
48
|
+
);
|
|
49
|
+
return { ...result, onSubmit, client };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getTextarea(): HTMLTextAreaElement {
|
|
53
|
+
return document.querySelector("textarea") as HTMLTextAreaElement;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
afterEach(cleanup);
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Tests
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
describe("SessionComposer — editing mode (edit-and-resubmit)", () => {
|
|
63
|
+
it("renders no editing banner by default (backward compatible)", () => {
|
|
64
|
+
renderComposer();
|
|
65
|
+
|
|
66
|
+
expect(screen.queryByText("Editing message")).toBeNull();
|
|
67
|
+
expect(
|
|
68
|
+
screen.queryByRole("button", { name: "Cancel editing" }),
|
|
69
|
+
).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("renders the editing banner with a cancel affordance while isEditing", () => {
|
|
73
|
+
renderComposer({ isEditing: true, onCancelEdit: vi.fn() });
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText("Editing message")).toBeTruthy();
|
|
76
|
+
expect(
|
|
77
|
+
screen.getByRole("button", { name: "Cancel editing" }),
|
|
78
|
+
).toBeTruthy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("clicking the banner X calls onCancelEdit", () => {
|
|
82
|
+
const onCancelEdit = vi.fn();
|
|
83
|
+
renderComposer({ isEditing: true, onCancelEdit });
|
|
84
|
+
|
|
85
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel editing" }));
|
|
86
|
+
expect(onCancelEdit).toHaveBeenCalledTimes(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("Escape in the textarea calls onCancelEdit while editing", () => {
|
|
90
|
+
const onCancelEdit = vi.fn();
|
|
91
|
+
renderComposer({ isEditing: true, onCancelEdit });
|
|
92
|
+
|
|
93
|
+
fireEvent.keyDown(getTextarea(), { key: "Escape" });
|
|
94
|
+
expect(onCancelEdit).toHaveBeenCalledTimes(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("Escape does nothing when not editing", () => {
|
|
98
|
+
const onCancelEdit = vi.fn();
|
|
99
|
+
renderComposer({ isEditing: false, onCancelEdit });
|
|
100
|
+
|
|
101
|
+
fireEvent.keyDown(getTextarea(), { key: "Escape" });
|
|
102
|
+
expect(onCancelEdit).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("Enter still submits while editing — cancel is Escape-only", async () => {
|
|
106
|
+
const onCancelEdit = vi.fn();
|
|
107
|
+
const { onSubmit } = renderComposer({ isEditing: true, onCancelEdit });
|
|
108
|
+
|
|
109
|
+
const textarea = getTextarea();
|
|
110
|
+
fireEvent.change(textarea, { target: { value: "corrected message" } });
|
|
111
|
+
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
|
|
112
|
+
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
115
|
+
});
|
|
116
|
+
expect(onSubmit.mock.calls[0][0]).toBe("corrected message");
|
|
117
|
+
expect(onCancelEdit).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("renders the banner without a cancel button when onCancelEdit is omitted", () => {
|
|
121
|
+
renderComposer({ isEditing: true });
|
|
122
|
+
|
|
123
|
+
expect(screen.getByText("Editing message")).toBeTruthy();
|
|
124
|
+
expect(
|
|
125
|
+
screen.queryByRole("button", { name: "Cancel editing" }),
|
|
126
|
+
).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@stigmer/theme";
|
|
4
|
+
|
|
5
|
+
/** Props for {@link ArtifactApplyButton}. */
|
|
6
|
+
export interface ArtifactApplyButtonProps {
|
|
7
|
+
/** CTA label (`"Apply to org"` / `"Push Skill to org"`). */
|
|
8
|
+
readonly label: string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the producing execution is terminal. The apply/push action is only
|
|
11
|
+
* enabled once the execution has finished; a mid-run artifact renders the
|
|
12
|
+
* button disabled (its content may still change).
|
|
13
|
+
*/
|
|
14
|
+
readonly isTerminal: boolean;
|
|
15
|
+
/** `true` while the apply/push is in-flight. */
|
|
16
|
+
readonly isApplying: boolean;
|
|
17
|
+
/** Perform the apply/push. */
|
|
18
|
+
readonly onApply: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The primary "Apply to [org]" / "Push Skill to [org]" action for a detected
|
|
23
|
+
* artifact resource, shared by every artifact chrome (the preview modal's
|
|
24
|
+
* action bar and the editor-area `ArtifactDocument` toolbar) so its
|
|
25
|
+
* terminal-gating, in-flight, and disabled states are single-source.
|
|
26
|
+
*
|
|
27
|
+
* All visual properties flow through `--stgm-*` tokens (DD-005).
|
|
28
|
+
*/
|
|
29
|
+
export function ArtifactApplyButton({
|
|
30
|
+
label,
|
|
31
|
+
isTerminal,
|
|
32
|
+
isApplying,
|
|
33
|
+
onApply,
|
|
34
|
+
}: ArtifactApplyButtonProps) {
|
|
35
|
+
const canApply = isTerminal && !isApplying;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={canApply ? onApply : undefined}
|
|
41
|
+
disabled={!canApply}
|
|
42
|
+
aria-busy={isApplying}
|
|
43
|
+
className={cn(
|
|
44
|
+
"rounded-md px-4 py-1.5 text-xs font-medium transition-colors",
|
|
45
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
46
|
+
"disabled:cursor-not-allowed",
|
|
47
|
+
canApply
|
|
48
|
+
? "bg-primary text-primary-foreground hover:bg-primary-hover"
|
|
49
|
+
: "bg-muted text-muted-foreground",
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{isApplying ? (
|
|
53
|
+
<span className="inline-flex items-center gap-1.5">
|
|
54
|
+
<SpinnerIcon />
|
|
55
|
+
Applying{"\u2026"}
|
|
56
|
+
</span>
|
|
57
|
+
) : (
|
|
58
|
+
label
|
|
59
|
+
)}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function SpinnerIcon() {
|
|
65
|
+
return (
|
|
66
|
+
<svg
|
|
67
|
+
width="12"
|
|
68
|
+
height="12"
|
|
69
|
+
viewBox="0 0 12 12"
|
|
70
|
+
fill="none"
|
|
71
|
+
stroke="currentColor"
|
|
72
|
+
strokeWidth="1.5"
|
|
73
|
+
className="shrink-0 animate-spin"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
>
|
|
76
|
+
<path d="M6 1.5A4.5 4.5 0 1 1 1.5 6" strokeLinecap="round" />
|
|
77
|
+
</svg>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
4
|
+
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import { ArtifactContentRenderer } from "./ArtifactContentRenderer.js";
|
|
6
|
+
import type { SkillPackageDetection } from "../library/detect-skill-package.js";
|
|
7
|
+
|
|
8
|
+
/** Props for {@link ArtifactContentBody}. */
|
|
9
|
+
export interface ArtifactContentBodyProps {
|
|
10
|
+
/** The artifact whose body is rendered. */
|
|
11
|
+
readonly artifact: ExecutionArtifact;
|
|
12
|
+
/** Decoded text content, or `null` (directory / binary / loading / error). */
|
|
13
|
+
readonly content: string | null;
|
|
14
|
+
/** Server-detected content type (rendering-strategy fallback). */
|
|
15
|
+
readonly contentType: string | null;
|
|
16
|
+
/** `true` while the content request is in-flight. */
|
|
17
|
+
readonly isLoading: boolean;
|
|
18
|
+
/** Content-fetch error, or `null`. */
|
|
19
|
+
readonly error: Error | null;
|
|
20
|
+
/** Whether the fetched content was truncated by the server's size cap. */
|
|
21
|
+
readonly isTruncated: boolean;
|
|
22
|
+
/** Skill-package detection for directory artifacts. */
|
|
23
|
+
readonly skillDetection: SkillPackageDetection;
|
|
24
|
+
/** Additional CSS classes for the root element. */
|
|
25
|
+
readonly className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The shared, chrome-agnostic body of an artifact view: the file-content
|
|
30
|
+
* states (loading / error / binary / text via {@link ArtifactContentRenderer})
|
|
31
|
+
* for FILE artifacts, or the file listing + skill banner for DIRECTORY
|
|
32
|
+
* artifacts.
|
|
33
|
+
*
|
|
34
|
+
* Extracted so the preview modal and the editor-area `ArtifactDocument` render
|
|
35
|
+
* artifact bodies identically — only the surrounding chrome (modal header +
|
|
36
|
+
* action bar vs. document toolbar) differs. Pair with
|
|
37
|
+
* {@link useArtifactInspection}, which supplies every prop here.
|
|
38
|
+
*
|
|
39
|
+
* All visual properties flow through `--stgm-*` tokens (DD-005).
|
|
40
|
+
*/
|
|
41
|
+
export function ArtifactContentBody({
|
|
42
|
+
artifact,
|
|
43
|
+
content,
|
|
44
|
+
contentType,
|
|
45
|
+
isLoading,
|
|
46
|
+
error,
|
|
47
|
+
isTruncated,
|
|
48
|
+
skillDetection,
|
|
49
|
+
className,
|
|
50
|
+
}: ArtifactContentBodyProps) {
|
|
51
|
+
const isDirectory = artifact.kind === ExecutionArtifactKind.DIRECTORY;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={className}>
|
|
55
|
+
{isDirectory ? (
|
|
56
|
+
<DirectoryContentView artifact={artifact} skillDetection={skillDetection} />
|
|
57
|
+
) : (
|
|
58
|
+
<FileContentStateView
|
|
59
|
+
artifact={artifact}
|
|
60
|
+
content={content}
|
|
61
|
+
contentType={contentType}
|
|
62
|
+
isLoading={isLoading}
|
|
63
|
+
error={error}
|
|
64
|
+
isTruncated={isTruncated}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// File content states (loading / error / binary / text)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const SKELETON_LINE_WIDTHS = [85, 72, 90, 65, 78, 88, 70, 82] as const;
|
|
76
|
+
|
|
77
|
+
function FileContentStateView({
|
|
78
|
+
artifact,
|
|
79
|
+
content,
|
|
80
|
+
contentType,
|
|
81
|
+
isLoading,
|
|
82
|
+
error,
|
|
83
|
+
isTruncated,
|
|
84
|
+
}: {
|
|
85
|
+
readonly artifact: ExecutionArtifact;
|
|
86
|
+
readonly content: string | null;
|
|
87
|
+
readonly contentType: string | null;
|
|
88
|
+
readonly isLoading: boolean;
|
|
89
|
+
readonly error: Error | null;
|
|
90
|
+
readonly isTruncated: boolean;
|
|
91
|
+
}) {
|
|
92
|
+
if (isLoading) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="space-y-2 p-4" aria-busy="true" aria-label="Loading content">
|
|
95
|
+
{SKELETON_LINE_WIDTHS.map((width, i) => (
|
|
96
|
+
<div
|
|
97
|
+
key={i}
|
|
98
|
+
className="h-4 animate-pulse rounded bg-muted"
|
|
99
|
+
style={{ width: `${width}%` }}
|
|
100
|
+
aria-hidden="true"
|
|
101
|
+
/>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (error) {
|
|
108
|
+
return (
|
|
109
|
+
<div className="flex flex-col items-center justify-center gap-2 p-8 text-center">
|
|
110
|
+
<ErrorAlertIcon />
|
|
111
|
+
<p className="text-sm text-destructive">{error.message}</p>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (content === null) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="p-8 text-center text-sm text-muted-foreground">
|
|
119
|
+
Content not available for preview.
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<ArtifactContentRenderer
|
|
126
|
+
content={content}
|
|
127
|
+
fileName={artifact.name}
|
|
128
|
+
contentType={contentType}
|
|
129
|
+
isTruncated={isTruncated}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Directory content (skill banner + file listing)
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
function DirectoryContentView({
|
|
139
|
+
artifact,
|
|
140
|
+
skillDetection,
|
|
141
|
+
}: {
|
|
142
|
+
readonly artifact: ExecutionArtifact;
|
|
143
|
+
readonly skillDetection: SkillPackageDetection;
|
|
144
|
+
}) {
|
|
145
|
+
const entries = artifact.entries;
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="p-4">
|
|
149
|
+
{skillDetection.detected && (
|
|
150
|
+
<div className="mb-4 rounded-md bg-primary-subtle p-3">
|
|
151
|
+
<p className="text-sm font-medium text-foreground">
|
|
152
|
+
{skillDetection.skillName}
|
|
153
|
+
</p>
|
|
154
|
+
{skillDetection.skillDescription && (
|
|
155
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
156
|
+
{skillDetection.skillDescription}
|
|
157
|
+
</p>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{entries.length > 0 ? (
|
|
163
|
+
<div>
|
|
164
|
+
<h3 className="mb-2 text-xs font-medium text-muted-foreground">
|
|
165
|
+
Files ({entries.length})
|
|
166
|
+
</h3>
|
|
167
|
+
<ul className="space-y-0.5" role="list">
|
|
168
|
+
{entries.map((entry) => (
|
|
169
|
+
<li
|
|
170
|
+
key={entry}
|
|
171
|
+
className="flex items-center gap-2 rounded-sm px-2 py-1 font-mono text-xs text-foreground"
|
|
172
|
+
>
|
|
173
|
+
<EntryIcon name={entry} />
|
|
174
|
+
<span className="min-w-0 truncate">{entry}</span>
|
|
175
|
+
</li>
|
|
176
|
+
))}
|
|
177
|
+
</ul>
|
|
178
|
+
</div>
|
|
179
|
+
) : (
|
|
180
|
+
<p className="text-sm text-muted-foreground">
|
|
181
|
+
File listing not available.
|
|
182
|
+
</p>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function EntryIcon({ name }: { readonly name: string }) {
|
|
189
|
+
if (name.endsWith("/")) return <FolderSmallIcon />;
|
|
190
|
+
return <FileSmallIcon />;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Inline SVG icons (SDK independence — no lucide dependency)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
function FileSmallIcon() {
|
|
198
|
+
return (
|
|
199
|
+
<svg
|
|
200
|
+
width="12"
|
|
201
|
+
height="12"
|
|
202
|
+
viewBox="0 0 14 14"
|
|
203
|
+
fill="none"
|
|
204
|
+
stroke="currentColor"
|
|
205
|
+
strokeWidth="1.5"
|
|
206
|
+
strokeLinecap="round"
|
|
207
|
+
strokeLinejoin="round"
|
|
208
|
+
className="shrink-0 text-muted-foreground"
|
|
209
|
+
aria-hidden="true"
|
|
210
|
+
>
|
|
211
|
+
<path d="M8 1H4C3.45 1 3 1.45 3 2V12C3 12.55 3.45 13 4 13H10C10.55 13 11 12.55 11 12V4L8 1Z" />
|
|
212
|
+
<path d="M8 1V4H11" />
|
|
213
|
+
</svg>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function FolderSmallIcon() {
|
|
218
|
+
return (
|
|
219
|
+
<svg
|
|
220
|
+
width="12"
|
|
221
|
+
height="12"
|
|
222
|
+
viewBox="0 0 14 14"
|
|
223
|
+
fill="none"
|
|
224
|
+
stroke="currentColor"
|
|
225
|
+
strokeWidth="1.5"
|
|
226
|
+
strokeLinecap="round"
|
|
227
|
+
strokeLinejoin="round"
|
|
228
|
+
className="shrink-0 text-muted-foreground"
|
|
229
|
+
aria-hidden="true"
|
|
230
|
+
>
|
|
231
|
+
<path d="M13 11C13 11.55 12.55 12 12 12H2C1.45 12 1 11.55 1 11V3C1 2.45 1.45 2 2 2H5L7 4H12C12.55 4 13 4.45 13 5V11Z" />
|
|
232
|
+
</svg>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function ErrorAlertIcon() {
|
|
237
|
+
return (
|
|
238
|
+
<svg
|
|
239
|
+
width="16"
|
|
240
|
+
height="16"
|
|
241
|
+
viewBox="0 0 16 16"
|
|
242
|
+
fill="none"
|
|
243
|
+
stroke="currentColor"
|
|
244
|
+
strokeWidth="1.5"
|
|
245
|
+
strokeLinecap="round"
|
|
246
|
+
strokeLinejoin="round"
|
|
247
|
+
className="text-destructive"
|
|
248
|
+
aria-hidden="true"
|
|
249
|
+
>
|
|
250
|
+
<circle cx="8" cy="8" r="6.5" />
|
|
251
|
+
<path d="M8 5.5V8.5" />
|
|
252
|
+
<circle cx="8" cy="11" r="0.5" fill="currentColor" stroke="none" />
|
|
253
|
+
</svg>
|
|
254
|
+
);
|
|
255
|
+
}
|