@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.
Files changed (285) hide show
  1. package/composer/SessionComposer.d.ts +37 -5
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +13 -5
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactApplyButton.d.ts +25 -0
  6. package/execution/ArtifactApplyButton.d.ts.map +1 -0
  7. package/execution/ArtifactApplyButton.js +21 -0
  8. package/execution/ArtifactApplyButton.js.map +1 -0
  9. package/execution/ArtifactContentBody.d.ts +36 -0
  10. package/execution/ArtifactContentBody.d.ts.map +1 -0
  11. package/execution/ArtifactContentBody.js +62 -0
  12. package/execution/ArtifactContentBody.js.map +1 -0
  13. package/execution/ArtifactDocument.d.ts +48 -0
  14. package/execution/ArtifactDocument.d.ts.map +1 -0
  15. package/execution/ArtifactDocument.js +89 -0
  16. package/execution/ArtifactDocument.js.map +1 -0
  17. package/execution/ArtifactPreviewModal.d.ts +9 -9
  18. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  19. package/execution/ArtifactPreviewModal.js +18 -145
  20. package/execution/ArtifactPreviewModal.js.map +1 -1
  21. package/execution/ArtifactRow.d.ts +49 -0
  22. package/execution/ArtifactRow.d.ts.map +1 -0
  23. package/execution/ArtifactRow.js +56 -0
  24. package/execution/ArtifactRow.js.map +1 -0
  25. package/execution/ArtifactsWidget.d.ts +6 -9
  26. package/execution/ArtifactsWidget.d.ts.map +1 -1
  27. package/execution/ArtifactsWidget.js +15 -19
  28. package/execution/ArtifactsWidget.js.map +1 -1
  29. package/execution/FileChangeProgressBar.d.ts +47 -0
  30. package/execution/FileChangeProgressBar.d.ts.map +1 -0
  31. package/execution/FileChangeProgressBar.js +49 -0
  32. package/execution/FileChangeProgressBar.js.map +1 -0
  33. package/execution/FileChangesView.d.ts.map +1 -1
  34. package/execution/FileChangesView.js +19 -4
  35. package/execution/FileChangesView.js.map +1 -1
  36. package/execution/FileReviewAtoms.d.ts +27 -0
  37. package/execution/FileReviewAtoms.d.ts.map +1 -0
  38. package/execution/FileReviewAtoms.js +64 -0
  39. package/execution/FileReviewAtoms.js.map +1 -0
  40. package/execution/FileReviewCard.d.ts +2 -2
  41. package/execution/FileReviewCard.d.ts.map +1 -1
  42. package/execution/FileReviewCard.js +32 -62
  43. package/execution/FileReviewCard.js.map +1 -1
  44. package/execution/MessageEntry.d.ts +0 -9
  45. package/execution/MessageEntry.d.ts.map +1 -1
  46. package/execution/MessageEntry.js +1 -11
  47. package/execution/MessageEntry.js.map +1 -1
  48. package/execution/MessageThread.d.ts +3 -4
  49. package/execution/MessageThread.d.ts.map +1 -1
  50. package/execution/MessageThread.js +25 -11
  51. package/execution/MessageThread.js.map +1 -1
  52. package/execution/PlanArtifactCard.d.ts +16 -10
  53. package/execution/PlanArtifactCard.d.ts.map +1 -1
  54. package/execution/PlanArtifactCard.js +31 -7
  55. package/execution/PlanArtifactCard.js.map +1 -1
  56. package/execution/PlanCompletionCard.d.ts +4 -4
  57. package/execution/PlanCompletionCard.d.ts.map +1 -1
  58. package/execution/PlanCompletionCard.js +4 -4
  59. package/execution/PlanCompletionCard.js.map +1 -1
  60. package/execution/PlanStreamingCard.d.ts +7 -2
  61. package/execution/PlanStreamingCard.d.ts.map +1 -1
  62. package/execution/PlanStreamingCard.js +11 -6
  63. package/execution/PlanStreamingCard.js.map +1 -1
  64. package/execution/index.d.ts +12 -2
  65. package/execution/index.d.ts.map +1 -1
  66. package/execution/index.js +6 -1
  67. package/execution/index.js.map +1 -1
  68. package/execution/use-build-from-plan-hotkey.d.ts +1 -1
  69. package/execution/use-build-from-plan-hotkey.js +1 -1
  70. package/execution/useArtifactContent.d.ts +6 -1
  71. package/execution/useArtifactContent.d.ts.map +1 -1
  72. package/execution/useArtifactContent.js +7 -2
  73. package/execution/useArtifactContent.js.map +1 -1
  74. package/execution/useArtifactCopy.d.ts +51 -0
  75. package/execution/useArtifactCopy.d.ts.map +1 -0
  76. package/execution/useArtifactCopy.js +75 -0
  77. package/execution/useArtifactCopy.js.map +1 -0
  78. package/execution/useArtifactDownload.d.ts.map +1 -1
  79. package/execution/useArtifactDownload.js +17 -5
  80. package/execution/useArtifactDownload.js.map +1 -1
  81. package/execution/useArtifactInspection.d.ts +85 -0
  82. package/execution/useArtifactInspection.d.ts.map +1 -0
  83. package/execution/useArtifactInspection.js +143 -0
  84. package/execution/useArtifactInspection.js.map +1 -0
  85. package/execution/useCreateAgentExecution.d.ts +16 -3
  86. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  87. package/execution/useCreateAgentExecution.js +1 -0
  88. package/execution/useCreateAgentExecution.js.map +1 -1
  89. package/index.d.ts +4 -4
  90. package/index.d.ts.map +1 -1
  91. package/index.js +3 -3
  92. package/index.js.map +1 -1
  93. package/internal/MermaidDiagram.d.ts +47 -0
  94. package/internal/MermaidDiagram.d.ts.map +1 -0
  95. package/internal/MermaidDiagram.js +118 -0
  96. package/internal/MermaidDiagram.js.map +1 -0
  97. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  98. package/internal/file-tree/FileTreeNode.js +2 -2
  99. package/internal/file-tree/FileTreeNode.js.map +1 -1
  100. package/internal/markdown-components.d.ts.map +1 -1
  101. package/internal/markdown-components.js +34 -0
  102. package/internal/markdown-components.js.map +1 -1
  103. package/internal/mermaid-loader.d.ts +26 -0
  104. package/internal/mermaid-loader.d.ts.map +1 -0
  105. package/internal/mermaid-loader.js +34 -0
  106. package/internal/mermaid-loader.js.map +1 -0
  107. package/internal/mermaid-theme-css.d.ts +41 -0
  108. package/internal/mermaid-theme-css.d.ts.map +1 -0
  109. package/internal/mermaid-theme-css.js +179 -0
  110. package/internal/mermaid-theme-css.js.map +1 -0
  111. package/internal/slug.d.ts +9 -5
  112. package/internal/slug.d.ts.map +1 -1
  113. package/internal/slug.js +10 -6
  114. package/internal/slug.js.map +1 -1
  115. package/library/detect-plan-artifact.d.ts +27 -9
  116. package/library/detect-plan-artifact.d.ts.map +1 -1
  117. package/library/detect-plan-artifact.js +26 -6
  118. package/library/detect-plan-artifact.js.map +1 -1
  119. package/library/index.d.ts +1 -1
  120. package/library/index.d.ts.map +1 -1
  121. package/library/index.js +1 -1
  122. package/library/index.js.map +1 -1
  123. package/package.json +5 -4
  124. package/session/PlanEditor.js +1 -1
  125. package/session/PlanEditor.js.map +1 -1
  126. package/session/SessionViewer.d.ts.map +1 -1
  127. package/session/SessionViewer.js +122 -30
  128. package/session/SessionViewer.js.map +1 -1
  129. package/session/artifact-document.d.ts +13 -0
  130. package/session/artifact-document.d.ts.map +1 -0
  131. package/session/artifact-document.js +22 -0
  132. package/session/artifact-document.js.map +1 -0
  133. package/session/facets/ArtifactsTab.d.ts +30 -4
  134. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  135. package/session/facets/ArtifactsTab.js +32 -10
  136. package/session/facets/ArtifactsTab.js.map +1 -1
  137. package/session/index.d.ts +2 -1
  138. package/session/index.d.ts.map +1 -1
  139. package/session/index.js +4 -1
  140. package/session/index.js.map +1 -1
  141. package/session/plan-document.d.ts +14 -2
  142. package/session/plan-document.d.ts.map +1 -1
  143. package/session/plan-document.js +14 -3
  144. package/session/plan-document.js.map +1 -1
  145. package/session/usePlanDraft.d.ts +1 -1
  146. package/session/useSessionArtifacts.d.ts +17 -0
  147. package/session/useSessionArtifacts.d.ts.map +1 -1
  148. package/session/useSessionArtifacts.js +16 -5
  149. package/session/useSessionArtifacts.js.map +1 -1
  150. package/session/useSessionConversation.d.ts +26 -3
  151. package/session/useSessionConversation.d.ts.map +1 -1
  152. package/session/useSessionConversation.js +51 -6
  153. package/session/useSessionConversation.js.map +1 -1
  154. package/session/useSessionPageFlow.d.ts.map +1 -1
  155. package/session/useSessionPageFlow.js +1 -0
  156. package/session/useSessionPageFlow.js.map +1 -1
  157. package/session/useSessionPanel.d.ts +20 -0
  158. package/session/useSessionPanel.d.ts.map +1 -1
  159. package/session/useSessionPanel.js +13 -0
  160. package/session/useSessionPanel.js.map +1 -1
  161. package/session/useSessionRailViews.d.ts +13 -1
  162. package/session/useSessionRailViews.d.ts.map +1 -1
  163. package/session/useSessionRailViews.js +5 -3
  164. package/session/useSessionRailViews.js.map +1 -1
  165. package/src/composer/SessionComposer.tsx +76 -7
  166. package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
  167. package/src/execution/ArtifactApplyButton.tsx +79 -0
  168. package/src/execution/ArtifactContentBody.tsx +255 -0
  169. package/src/execution/ArtifactDocument.tsx +344 -0
  170. package/src/execution/ArtifactPreviewModal.tsx +49 -415
  171. package/src/execution/ArtifactRow.tsx +165 -0
  172. package/src/execution/ArtifactsWidget.tsx +24 -33
  173. package/src/execution/FileChangeProgressBar.tsx +121 -0
  174. package/src/execution/FileChangesView.tsx +18 -4
  175. package/src/execution/FileReviewAtoms.tsx +89 -0
  176. package/src/execution/FileReviewCard.tsx +57 -90
  177. package/src/execution/MessageEntry.tsx +0 -62
  178. package/src/execution/MessageThread.tsx +29 -15
  179. package/src/execution/PlanArtifactCard.tsx +130 -52
  180. package/src/execution/PlanCompletionCard.tsx +5 -5
  181. package/src/execution/PlanStreamingCard.tsx +15 -11
  182. package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
  183. package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
  184. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
  185. package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
  186. package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
  187. package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
  188. package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
  189. package/src/execution/__tests__/MessageThread.test.tsx +1 -1
  190. package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
  191. package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
  192. package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
  193. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
  194. package/src/execution/__tests__/message-entry.test.tsx +56 -39
  195. package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
  196. package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
  197. package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
  198. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
  199. package/src/execution/index.ts +20 -2
  200. package/src/execution/use-build-from-plan-hotkey.ts +1 -1
  201. package/src/execution/useArtifactContent.ts +7 -0
  202. package/src/execution/useArtifactCopy.ts +109 -0
  203. package/src/execution/useArtifactDownload.ts +17 -5
  204. package/src/execution/useArtifactInspection.ts +252 -0
  205. package/src/execution/useCreateAgentExecution.ts +17 -3
  206. package/src/index.ts +17 -2
  207. package/src/internal/MermaidDiagram.tsx +164 -0
  208. package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
  209. package/src/internal/__tests__/markdown-components.test.tsx +93 -1
  210. package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
  211. package/src/internal/file-tree/FileTreeNode.tsx +13 -2
  212. package/src/internal/markdown-components.tsx +36 -1
  213. package/src/internal/mermaid-loader.ts +44 -0
  214. package/src/internal/mermaid-theme-css.ts +178 -0
  215. package/src/internal/slug.ts +10 -6
  216. package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
  217. package/src/library/detect-plan-artifact.ts +32 -10
  218. package/src/library/index.ts +2 -0
  219. package/src/session/PlanEditor.tsx +1 -1
  220. package/src/session/SessionViewer.tsx +227 -51
  221. package/src/session/__tests__/PlanEditor.test.tsx +4 -4
  222. package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
  223. package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
  224. package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
  225. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
  226. package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
  227. package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
  228. package/src/session/__tests__/useSessionPanel.test.ts +70 -1
  229. package/src/session/artifact-document.ts +23 -0
  230. package/src/session/facets/ArtifactsTab.tsx +66 -18
  231. package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
  232. package/src/session/index.ts +5 -1
  233. package/src/session/plan-document.ts +14 -3
  234. package/src/session/usePlanDraft.ts +1 -1
  235. package/src/session/useSessionArtifacts.ts +16 -5
  236. package/src/session/useSessionConversation.ts +80 -10
  237. package/src/session/useSessionPageFlow.ts +1 -0
  238. package/src/session/useSessionPanel.ts +41 -0
  239. package/src/session/useSessionRailViews.tsx +21 -1
  240. package/src/workspace/EditorTabs.tsx +38 -9
  241. package/src/workspace/ExplorerTree.tsx +10 -4
  242. package/src/workspace/FileViewer.tsx +27 -6
  243. package/src/workspace/WorkspaceContentSearch.tsx +44 -20
  244. package/src/workspace/WorkspaceEditor.tsx +19 -16
  245. package/src/workspace/WorkspaceFileSearch.tsx +51 -28
  246. package/src/workspace/WorkspaceSurface.tsx +106 -48
  247. package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
  248. package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
  249. package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
  250. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
  251. package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
  252. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
  253. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
  254. package/src/workspace/__tests__/a11y/harness.tsx +140 -0
  255. package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
  256. package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
  257. package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
  258. package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
  259. package/styles.css +1 -1
  260. package/workspace/EditorTabs.d.ts +21 -3
  261. package/workspace/EditorTabs.d.ts.map +1 -1
  262. package/workspace/EditorTabs.js +18 -6
  263. package/workspace/EditorTabs.js.map +1 -1
  264. package/workspace/ExplorerTree.js +8 -4
  265. package/workspace/ExplorerTree.js.map +1 -1
  266. package/workspace/FileViewer.d.ts.map +1 -1
  267. package/workspace/FileViewer.js +19 -8
  268. package/workspace/FileViewer.js.map +1 -1
  269. package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
  270. package/workspace/WorkspaceContentSearch.js +23 -2
  271. package/workspace/WorkspaceContentSearch.js.map +1 -1
  272. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  273. package/workspace/WorkspaceEditor.js +8 -10
  274. package/workspace/WorkspaceEditor.js.map +1 -1
  275. package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
  276. package/workspace/WorkspaceFileSearch.js +23 -3
  277. package/workspace/WorkspaceFileSearch.js.map +1 -1
  278. package/workspace/WorkspaceSurface.d.ts.map +1 -1
  279. package/workspace/WorkspaceSurface.js +54 -18
  280. package/workspace/WorkspaceSurface.js.map +1 -1
  281. package/execution/ArtifactCard.d.ts +0 -81
  282. package/execution/ArtifactCard.d.ts.map +0 -1
  283. package/execution/ArtifactCard.js +0 -130
  284. package/execution/ArtifactCard.js.map +0 -1
  285. 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
+ }