@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
@@ -17,7 +17,7 @@ import {
17
17
  WorkspaceSurface,
18
18
  type SurfaceVirtualDocument,
19
19
  } from "../workspace/WorkspaceSurface.js";
20
- import type { InteractionModeOption, SessionComposerHandle } from "../composer/index.js";
20
+ import type { InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
21
21
  import type { ApplyResourceResult } from "../library/useApplyResource.js";
22
22
  import { ResizableSplit } from "../internal/ResizableSplit.js";
23
23
  import { SelectionStore } from "../internal/store/selection-store.js";
@@ -25,13 +25,17 @@ import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.j
25
25
  import { ThreadSelectionContext } from "../execution/ThreadSelectionContext.js";
26
26
  import { useSelectedThreadItem } from "../execution/useThreadSelection.js";
27
27
  import { MessageThread } from "../execution/MessageThread.js";
28
+ import { FileChangeProgressBar } from "../execution/FileChangeProgressBar.js";
28
29
  import { FileReviewDock } from "../execution/FileReviewDock.js";
30
+ import {
31
+ FilePathContext,
32
+ type FilePathContextValue,
33
+ } from "../execution/FilePathContext.js";
29
34
  import { ThreadSkeleton } from "../execution/ThreadSkeleton.js";
30
35
  import { SessionComposer } from "../composer/index.js";
31
36
  import { SecretFlowErrorGuide, isSecretFlowError } from "../error/index.js";
32
37
  import { useStigmer } from "../hooks.js";
33
38
  import {
34
- PLAN_ARTIFACT_NAME,
35
39
  findLatestSessionPlan,
36
40
  findPlanArtifact,
37
41
  type SessionPlan,
@@ -43,11 +47,17 @@ import { usePlanDraft, planDraftKey, type PlanDraftController } from "./usePlanD
43
47
  import { PlanEditor } from "./PlanEditor.js";
44
48
  import { PlanStreamingDocument } from "./PlanStreamingDocument.js";
45
49
  import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
50
+ import { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
51
+ import { ArtifactDocument } from "../execution/ArtifactDocument.js";
46
52
  import { useSessionRailViews } from "./useSessionRailViews.js";
47
53
  import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
48
54
  import { SessionPanelChip } from "./SessionPanelChip.js";
49
55
  import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
50
- import { useSessionArtifacts } from "./useSessionArtifacts.js";
56
+ import {
57
+ useSessionArtifacts,
58
+ artifactKey,
59
+ type SessionArtifactEntry,
60
+ } from "./useSessionArtifacts.js";
51
61
  import type { SetupTabProps } from "./facets/SetupTab.js";
52
62
  import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
53
63
  import type { RuntimeEnvProvider } from "./runtime-env.js";
@@ -57,18 +67,22 @@ import type { SessionAudience } from "./audience.js";
57
67
  * Where the approved plan mounts in the implement execution's workspace —
58
68
  * the harnesses' standard attachment inputs directory, where the runner's
59
69
  * implement-plan directive points the agent (shared/implement-plan-prompt.ts).
60
- * Attaching at this exact path is what makes the build implement the document
61
- * the user approved (including in-place edits), not a paraphrase from memory.
70
+ * Attaching under the plan's own filename (e.g. `feature-x_a1b2c3d4.plan.md`) is what
71
+ * makes the build implement the document the user approved (including in-place
72
+ * edits), not a paraphrase from memory; the runner detects it by the plan
73
+ * filename convention (`isPlanArtifactName`), so the exact name is free to vary.
62
74
  */
63
- const APPROVED_PLAN_MOUNT_PATH = `.stigmer/inputs/${PLAN_ARTIFACT_NAME}`;
75
+ function approvedPlanMountPath(planFileName: string): string {
76
+ return `.stigmer/inputs/${planFileName}`;
77
+ }
64
78
 
65
79
  /**
66
80
  * The build turn's user message — a short human-readable label, NOT the
67
81
  * implement instruction. The agent-facing instruction is runner-injected
68
- * (keyed off `executionConfig.buildFromPlan`), so this text exists for
69
- * humans: the thread renders the turn as a compact chip from the same flag,
70
- * and surfaces without the chip treatment (the CLI, older clients) show
71
- * this label as-is.
82
+ * (keyed off `executionConfig.buildFromPlan`). The thread hides the turn
83
+ * entirely from the same flag (the plan card above it is the visible cause);
84
+ * this label exists for surfaces without that treatment (the CLI, execution
85
+ * history, older clients), which show it as-is.
72
86
  */
73
87
  const BUILD_FROM_PLAN_MESSAGE = "Build from plan";
74
88
 
@@ -377,7 +391,7 @@ export function SessionViewer({
377
391
  setPlanAttachFailed(false);
378
392
 
379
393
  // `buildFromPlan` (not message text) is what tells the runner to inject
380
- // the implement directive and the thread to render the compact chip
394
+ // the implement directive and the thread to hide the turn's message
381
395
  // with or without the attachment (the runner picks the directive variant).
382
396
  const submitImplementTurn = (attachments?: AttachmentInput[]) => {
383
397
  composerRef.current?.submit(BUILD_FROM_PLAN_MESSAGE, {
@@ -398,22 +412,25 @@ export function SessionViewer({
398
412
  // execution. The published artifact itself stays immutable; the approved
399
413
  // copy is a new input on the build turn (edit-as-input provenance).
400
414
  setIsBuildingFromPlan(true);
415
+ // The approved copy keeps the published plan's filename, so the mounted
416
+ // input, the download, and the plan card all agree on one name.
417
+ const planFileName = sessionPlan.artifact.name;
401
418
  void (async () => {
402
419
  try {
403
420
  const approvedText =
404
421
  planDraft.readDraft() ?? (await fetchPlanText(stigmer, sessionPlan));
405
422
  const response = await stigmer.agentExecution.uploadAttachment(
406
423
  create(UploadAttachmentRequestSchema, {
407
- filename: PLAN_ARTIFACT_NAME,
424
+ filename: planFileName,
408
425
  content: new TextEncoder().encode(approvedText),
409
426
  contentType: "text/markdown",
410
427
  }),
411
428
  );
412
429
  submitImplementTurn([
413
430
  {
414
- filename: PLAN_ARTIFACT_NAME,
431
+ filename: planFileName,
415
432
  storageKey: response.storageKey,
416
- mountPath: APPROVED_PLAN_MOUNT_PATH,
433
+ mountPath: approvedPlanMountPath(planFileName),
417
434
  contentType: "text/markdown",
418
435
  },
419
436
  ]);
@@ -444,6 +461,28 @@ export function SessionViewer({
444
461
  [sessionPlan, panel.openPlanDocument],
445
462
  );
446
463
 
464
+ // "Open artifact" (an Artifacts-facet row) → an editor-pane document tab.
465
+ // Writing the editors store re-renders only the panel subtree (which
466
+ // subscribes), never this streaming column. Plan artifacts are routed to the
467
+ // plan tab upstream (in ArtifactsTab), so they never reach here.
468
+ const handleOpenArtifact = useCallback(
469
+ (entry: SessionArtifactEntry) => {
470
+ panel.openArtifact(entry.artifact);
471
+ },
472
+ [panel.openArtifact],
473
+ );
474
+
475
+ // Double-click an artifact row → pin its tab (the leading single click already
476
+ // opened the preview via `handleOpenArtifact`). Mirrors the file tree's
477
+ // preview/pin split; plan artifacts are routed to the plan tab upstream (in
478
+ // ArtifactsTab) and never reach here.
479
+ const handleActivateArtifact = useCallback(
480
+ (entry: SessionArtifactEntry) => {
481
+ panel.pinArtifact(entry.artifact);
482
+ },
483
+ [panel.pinArtifact],
484
+ );
485
+
447
486
  // Open a transcript tool-call file path in the panel's read-only editor.
448
487
  // Resolves the (possibly absolute / subdir-prefixed) path to a
449
488
  // repo/root-relative selection the editor can fetch; on a definite hit it
@@ -550,6 +589,8 @@ export function SessionViewer({
550
589
  planDraft={planDraft}
551
590
  openPlanExecutionId={openPlanExecutionId}
552
591
  onOpenPlan={handleOpenPlan}
592
+ onOpenArtifact={handleOpenArtifact}
593
+ onActivateArtifact={handleActivateArtifact}
553
594
  enableLocal={enableLocal}
554
595
  onBrowseLocalFolder={onBrowseLocalFolder}
555
596
  workspaceFileLister={workspaceFileLister}
@@ -641,18 +682,69 @@ const ConversationColumn = memo(function ConversationColumn({
641
682
  void conv.stop();
642
683
  }, [conv.stop]);
643
684
 
644
- // Edit-and-resubmit: stop the in-flight turn and pre-fill the composer with
645
- // the original text. The append-only execution log can't be rewritten, so
646
- // the user reviews the prefilled message and resubmits it as a NEW execution
647
- // through the normal Send pipeline. The cancelled turn stays in history with
648
- // its phase badge an honest record rather than a silent edit.
685
+ // Edit-and-resubmit: stop the in-flight turn, pre-fill the composer with
686
+ // the original text, and remember which execution is being edited. The
687
+ // append-only execution log is never rewritten submitting while editing
688
+ // creates a NEW execution that carries `supersedesExecutionId`, and the
689
+ // conversation read model hides the superseded turn so the edited message
690
+ // replaces the original in place (Cursor-style, stigmer/stigmer#181).
691
+ //
692
+ // The editing state is explicit (banner + cancel in the composer) so the
693
+ // supersede link only attaches when the user actually resubmits the edit.
694
+ // Cancelling drops the link — a subsequent unrelated message appends
695
+ // normally without hiding any history.
696
+ const [editingExecutionId, setEditingExecutionId] = useState<string | null>(
697
+ null,
698
+ );
699
+ const activeExecutionId = conv.activeStreamExecution?.metadata?.id ?? null;
700
+
649
701
  const handleEditMessage = useCallback(
650
702
  (text: string) => {
651
703
  void conv.stop();
704
+ setEditingExecutionId(activeExecutionId);
652
705
  composerRef.current?.setMessage(text);
653
706
  composerRef.current?.focus();
654
707
  },
655
- [conv.stop, composerRef],
708
+ [conv.stop, activeExecutionId, composerRef],
709
+ );
710
+
711
+ const handleCancelEdit = useCallback(() => {
712
+ setEditingExecutionId(null);
713
+ composerRef.current?.setMessage("");
714
+ }, [composerRef]);
715
+
716
+ // Submit wrapper that attaches the supersede link while editing, then
717
+ // exits editing mode. Ordinary (non-editing) submits pass through as-is.
718
+ const handleComposerSubmit = useCallback(
719
+ (
720
+ message: string,
721
+ modelName?: string,
722
+ context?: SessionComposerSubmitContext,
723
+ ) => {
724
+ void flow.handleSubmit(
725
+ message,
726
+ modelName,
727
+ editingExecutionId
728
+ ? { ...context, supersedesExecutionId: editingExecutionId }
729
+ : context,
730
+ );
731
+ setEditingExecutionId(null);
732
+ },
733
+ [flow.handleSubmit, editingExecutionId],
734
+ );
735
+
736
+ // The composer-docked FileReviewDock sits OUTSIDE MessageThread, so the
737
+ // thread's own FilePathContext provider does not reach it. This provider
738
+ // gives the dock's file list the same path resolution and click routing
739
+ // (open in the panel's viewer; GitHub/copy fallback) as the transcript —
740
+ // one click behavior for a path everywhere in the session. Memoized so the
741
+ // dock's memoized cards are not invalidated by unrelated renders (DD-010).
742
+ const dockFilePathCtx = useMemo<FilePathContextValue>(
743
+ () => ({
744
+ workspaceEntries: conv.workspaceEntries ?? [],
745
+ onFilePathClick,
746
+ }),
747
+ [conv.workspaceEntries, onFilePathClick],
656
748
  );
657
749
 
658
750
  return (
@@ -703,19 +795,28 @@ const ConversationColumn = memo(function ConversationColumn({
703
795
  decision the agent is blocked on can never scroll out of view. The
704
796
  thread renders only observational rows (badges) and read-only
705
797
  settled records; this is the one decision surface. */}
706
- <FileReviewDock
707
- changeSets={conv.fileChangeSets}
708
- onSubmit={conv.submitFileDecision}
709
- submittingDecisionKeys={conv.submittingFileDecisionKeys}
710
- decisionErrors={conv.fileDecisionErrors}
711
- />
798
+ <FilePathContext.Provider value={dockFilePathCtx}>
799
+ {/* Mid-run live capture (DD-32): the "N files changed so far" strip for
800
+ a still-running turn. Mutually exclusive with the dock below —
801
+ progress shows while CAPTURING, the dock once AWAITING_REVIEW — so it
802
+ hands off cleanly when review opens. Non-interactive. */}
803
+ <FileChangeProgressBar progress={conv.fileChangeProgress} />
804
+ <FileReviewDock
805
+ changeSets={conv.fileChangeSets}
806
+ onSubmit={conv.submitFileDecision}
807
+ submittingDecisionKeys={conv.submittingFileDecisionKeys}
808
+ decisionErrors={conv.fileDecisionErrors}
809
+ />
810
+ </FilePathContext.Provider>
712
811
  <SessionComposer
713
812
  ref={composerRef}
714
- onSubmit={flow.handleSubmit}
813
+ onSubmit={handleComposerSubmit}
715
814
  isSubmitting={conv.isSending}
716
815
  disabled={!conv.canSendFollowUp}
717
816
  onStop={conv.isStoppable ? handleStop : undefined}
718
817
  isStopping={conv.isStopping}
818
+ isEditing={editingExecutionId != null}
819
+ onCancelEdit={handleCancelEdit}
719
820
  org={org}
720
821
  harness={flow.harness}
721
822
  defaultModelId={modelId}
@@ -783,6 +884,10 @@ interface SessionPanelRegionProps {
783
884
  readonly openPlanExecutionId: string | null;
784
885
  /** Opens a plan in the plan tab (routed to the Artifacts facet's plan.md). */
785
886
  readonly onOpenPlan: (executionId: string) => void;
887
+ /** Opens an artifact as an editor-pane document (routed to the Artifacts facet). */
888
+ readonly onOpenArtifact: (entry: SessionArtifactEntry) => void;
889
+ /** Pins an artifact's document tab — the double-click half of the open/pin split. */
890
+ readonly onActivateArtifact: (entry: SessionArtifactEntry) => void;
786
891
  readonly enableLocal: boolean;
787
892
  readonly onBrowseLocalFolder?: () => Promise<string | null>;
788
893
  readonly workspaceFileLister?: WorkspaceFileLister;
@@ -804,6 +909,8 @@ function SessionPanelRegion({
804
909
  planDraft,
805
910
  openPlanExecutionId,
806
911
  onOpenPlan,
912
+ onOpenArtifact,
913
+ onActivateArtifact,
807
914
  enableLocal,
808
915
  onBrowseLocalFolder,
809
916
  workspaceFileLister,
@@ -815,6 +922,9 @@ function SessionPanelRegion({
815
922
  const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
816
923
  panel.editorsStore,
817
924
  );
925
+ // Session artifacts, deduped by identity — the source both the Artifacts
926
+ // rail-view badge and the open artifact document tabs resolve against.
927
+ const { artifacts: sessionArtifacts } = useSessionArtifacts(flow.allExecutions);
818
928
 
819
929
  // Honor a pending jump-to-line reveal only while it targets the active editor
820
930
  // (an EditorReveal is structurally a RevealTarget). Switching tabs changes
@@ -870,30 +980,28 @@ function SessionPanelRegion({
870
980
  // tab and its turn then ended without publishing, with no earlier plan to
871
981
  // fall back to.
872
982
  const openPlanIsLatest = openPlan?.executionId === sessionPlan?.executionId;
873
- const virtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(() => {
874
- return [
875
- {
876
- entryId: PLAN_DOCUMENT_ENTRY_ID,
877
- path: PLAN_DOCUMENT_PATH,
878
- content: streamingPlan ? (
879
- <PlanStreamingDocument
880
- key={`streaming-${streamingPlan.executionId}`}
881
- displayText={streamingPlan.displayText}
882
- />
883
- ) : openPlan ? (
884
- <PlanEditor
885
- key={planDraftKey(openPlan)}
886
- plan={openPlan}
887
- draft={openPlanIsLatest ? planDraft : undefined}
888
- onBuildFromPlan={openPlanIsLatest ? onImplementPlan : undefined}
889
- buildDisabled={implementPlanDisabled}
890
- readOnly={!openPlanIsLatest}
891
- />
892
- ) : (
893
- <PlanUnavailableNotice />
894
- ),
895
- },
896
- ];
983
+ const planVirtualDocument = useMemo<SurfaceVirtualDocument>(() => {
984
+ return {
985
+ entryId: PLAN_DOCUMENT_ENTRY_ID,
986
+ path: PLAN_DOCUMENT_PATH,
987
+ content: streamingPlan ? (
988
+ <PlanStreamingDocument
989
+ key={`streaming-${streamingPlan.executionId}`}
990
+ displayText={streamingPlan.displayText}
991
+ />
992
+ ) : openPlan ? (
993
+ <PlanEditor
994
+ key={planDraftKey(openPlan)}
995
+ plan={openPlan}
996
+ draft={openPlanIsLatest ? planDraft : undefined}
997
+ onBuildFromPlan={openPlanIsLatest ? onImplementPlan : undefined}
998
+ buildDisabled={implementPlanDisabled}
999
+ readOnly={!openPlanIsLatest}
1000
+ />
1001
+ ) : (
1002
+ <PlanUnavailableNotice />
1003
+ ),
1004
+ };
897
1005
  }, [
898
1006
  streamingPlan,
899
1007
  openPlan,
@@ -903,6 +1011,50 @@ function SessionPanelRegion({
903
1011
  implementPlanDisabled,
904
1012
  ]);
905
1013
 
1014
+ // The open artifact document tabs (the artifact virtual-document FAMILY).
1015
+ // Built in a SEPARATE memo from the plan doc so artifact-tab churn never
1016
+ // recreates the plan doc (and vice versa). Bounded to what is actually open:
1017
+ // one document per editor whose entry id is the artifact family's, resolved
1018
+ // back to its `SessionArtifactEntry` by the same `artifactKey` used to open
1019
+ // it (single source of truth for artifact identity). Only the ACTIVE doc's
1020
+ // `content` is mounted by the surface, so building these elements is cheap;
1021
+ // keying on `contentHash` remounts a tab whose artifact was overwritten by a
1022
+ // later execution. A tab whose artifact no longer exists degrades to an
1023
+ // honest notice rather than vanishing.
1024
+ const artifactByKey = useMemo(
1025
+ () =>
1026
+ new Map(sessionArtifacts.map((entry) => [artifactKey(entry.artifact), entry])),
1027
+ [sessionArtifacts],
1028
+ );
1029
+ const artifactVirtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(() => {
1030
+ return editors
1031
+ .filter((editor) => editor.entryId === ARTIFACT_DOCUMENT_ENTRY_ID)
1032
+ .map((editor) => {
1033
+ const entry = artifactByKey.get(editor.path);
1034
+ return {
1035
+ entryId: ARTIFACT_DOCUMENT_ENTRY_ID,
1036
+ path: editor.path,
1037
+ content: entry ? (
1038
+ <ArtifactDocument
1039
+ key={entry.artifact.contentHash || editor.path}
1040
+ artifact={entry.artifact}
1041
+ executionId={entry.executionId}
1042
+ org={org}
1043
+ isTerminal={entry.isTerminal}
1044
+ onApplied={onApplied}
1045
+ />
1046
+ ) : (
1047
+ <ArtifactUnavailableNotice />
1048
+ ),
1049
+ };
1050
+ });
1051
+ }, [editors, artifactByKey, org, onApplied]);
1052
+
1053
+ const virtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(
1054
+ () => [planVirtualDocument, ...artifactVirtualDocuments],
1055
+ [planVirtualDocument, artifactVirtualDocuments],
1056
+ );
1057
+
906
1058
  const handleRemoveAgent = useCallback(() => {
907
1059
  flow.setAgentRef(null);
908
1060
  flow.setResolution(null);
@@ -969,6 +1121,8 @@ function SessionPanelRegion({
969
1121
  onApplied,
970
1122
  onImplementPlan,
971
1123
  onOpenPlan,
1124
+ onOpenArtifact,
1125
+ onActivateArtifact,
972
1126
  });
973
1127
 
974
1128
  // Explorer-footer folder attach (desktop only — needs the native picker).
@@ -1093,6 +1247,28 @@ function PlanUnavailableNotice() {
1093
1247
  );
1094
1248
  }
1095
1249
 
1250
+ /**
1251
+ * Shown in an artifact document tab whose artifact is no longer among the
1252
+ * session's outputs (an edge only reachable if the executions backing the tab
1253
+ * changed underneath it). The tab stays closable; the content is honest rather
1254
+ * than blank.
1255
+ */
1256
+ function ArtifactUnavailableNotice() {
1257
+ return (
1258
+ <div
1259
+ role="status"
1260
+ className="mx-auto flex w-full max-w-3xl flex-col items-center gap-1 px-4 py-8 text-center"
1261
+ >
1262
+ <p className="text-xs font-medium text-foreground">
1263
+ This artifact is no longer available.
1264
+ </p>
1265
+ <p className="text-xs text-muted-foreground">
1266
+ It may have been replaced by a newer version of this session.
1267
+ </p>
1268
+ </div>
1269
+ );
1270
+ }
1271
+
1096
1272
  /**
1097
1273
  * Non-blocking notice for a build that fell back to message-only implement
1098
1274
  * (the approved plan.md could not be uploaded/attached). The build itself
@@ -81,9 +81,9 @@ describe("PlanEditor", () => {
81
81
  renderEditor({ onBuildFromPlan });
82
82
 
83
83
  await waitFor(() =>
84
- expect(screen.getByText("Build from plan")).toBeTruthy(),
84
+ expect(screen.getByText("Build")).toBeTruthy(),
85
85
  );
86
- fireEvent.click(screen.getByText("Build from plan"));
86
+ fireEvent.click(screen.getByText("Build"));
87
87
  expect(onBuildFromPlan).toHaveBeenCalledTimes(1);
88
88
  });
89
89
 
@@ -99,7 +99,7 @@ describe("PlanEditor", () => {
99
99
  );
100
100
  expect(screen.getByText("Rollout Plan").className).toContain("truncate");
101
101
 
102
- const buildLabel = screen.getByText("Build from plan");
102
+ const buildLabel = screen.getByText("Build");
103
103
  expect(buildLabel.className).toContain("truncate");
104
104
  const toolbar = screen.getByRole("tablist", { name: "Plan view" })
105
105
  .parentElement!;
@@ -188,7 +188,7 @@ describe("PlanEditor — read-only (superseded plan)", () => {
188
188
  );
189
189
  expect(screen.getByText("Superseded by a newer plan")).toBeTruthy();
190
190
  expect(screen.queryByText("Edit")).toBeNull();
191
- expect(screen.queryByText("Build from plan")).toBeNull();
191
+ expect(screen.queryByText("Build")).toBeNull();
192
192
  });
193
193
 
194
194
  it("never applies the current plan's draft to a superseded plan", async () => {
@@ -44,7 +44,7 @@ describe("PlanStreamingDocument — live plan tab view", () => {
44
44
  render(<PlanStreamingDocument displayText={"# Live Plan\n\nBody"} />);
45
45
 
46
46
  expect(screen.queryByText("Edit")).toBeNull();
47
- expect(screen.queryByText("Build from plan")).toBeNull();
47
+ expect(screen.queryByText("Build")).toBeNull();
48
48
  expect(screen.queryByRole("tablist")).toBeNull();
49
49
  });
50
50
  });