@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
@@ -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
- function openPreviewFor(name: string) {
58
- const row = screen.getByText(name).closest("[role='listitem']") as HTMLElement;
59
- fireEvent.click(within(row).getByText("Preview"));
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 — plan 'Build from plan' wiring", () => {
65
- it("shows 'Build from plan' in the preview of a plan.md artifact", () => {
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
- openPreviewFor("plan.md");
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
- const dialog = document.querySelector("dialog")!;
71
- expect(within(dialog).getByText("Build from plan")).toBeTruthy();
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
- it("does not show 'Build from plan' in the preview of a non-plan artifact", () => {
75
- renderTab(vi.fn());
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
- openPreviewFor("notes.md");
89
+ openRow("notes.md");
78
90
 
79
- const dialog = document.querySelector("dialog")!;
80
- expect(within(dialog).queryByText("Build from plan")).toBeNull();
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("invokes onImplementPlan when 'Build from plan' is clicked for a plan", () => {
84
- const onImplementPlan = vi.fn();
85
- renderTab(onImplementPlan);
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
- openPreviewFor("plan.md");
88
- const dialog = document.querySelector("dialog")!;
89
- fireEvent.click(within(dialog).getByText("Build from plan"));
101
+ openRow("plan.md");
90
102
 
91
- expect(onImplementPlan).toHaveBeenCalledTimes(1);
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 — plan routes to the plan document tab", () => {
96
- it("routes a plan.md preview to onOpenPlan (no modal) when provided", () => {
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
- openPreviewFor("plan.md");
141
+ activateRow("plan.md");
101
142
 
102
143
  expect(onOpenPlan).toHaveBeenCalledWith("aex_1");
103
- expect(document.querySelector("dialog")).toBeNull();
144
+ expect(onActivateArtifact).not.toHaveBeenCalled();
104
145
  });
105
146
 
106
- it("keeps the modal for non-plan artifacts even when onOpenPlan is provided", () => {
107
- const onOpenPlan = vi.fn();
108
- renderTab(vi.fn(), onOpenPlan);
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
- openPreviewFor("notes.md");
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
- expect(onOpenPlan).not.toHaveBeenCalled();
113
- expect(document.querySelector("dialog")).toBeTruthy();
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("keeps the modal for plan.md when onOpenPlan is omitted (hosts without the plan tab)", () => {
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
- openPreviewFor("plan.md");
171
+ openRow("notes.md");
120
172
 
121
- expect(document.querySelector("dialog")).toBeTruthy();
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
  });
@@ -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
- /** The plan tab's path — doubles as its tab label, matching the artifact name. */
19
- export const PLAN_DOCUMENT_PATH = PLAN_ARTIFACT_NAME;
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 from plan" reads the
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
- * Returns the dedup key for an artifact. Uses `sandbox_path` when
45
- * available (stable filesystem identity within the session sandbox),
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 dedupKey(artifact: ExecutionArtifact): string {
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 = dedupKey(artifact);
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 "Build from plan" turn.
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
- /** Executions in terminal phases, in chronological order. */
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
- if (!activeExecutionId) return executions;
489
- return executions.filter(
490
- (e) => (e.metadata?.id ?? "") !== activeExecutionId,
491
- );
492
- }, [executions, activeExecutionId]);
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
- setPendingUserMessage(message);
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 { useSessionArtifacts } from "./useSessionArtifacts.js";
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
  }