@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
@@ -16,10 +16,11 @@ import {
16
16
  import { toDisplayFileChange } from "@stigmer/sdk";
17
17
  import { cn } from "@stigmer/theme";
18
18
  import { FileChangeDiff } from "./FileChangesView.js";
19
+ import { FileKindBadge, FileLineStats } from "./FileReviewAtoms.js";
20
+ import { FilePathLink } from "./FilePathLink.js";
19
21
  import { DiffSummary } from "../version-history/DiffSummary.js";
20
22
  import { DecisionButton } from "../internal/DecisionButton.js";
21
23
  import { InCardDecisionError } from "../internal/InCardDecisionError.js";
22
- import { splitDisplayPath } from "./file-path-resolver.js";
23
24
  import { fileDecisionKey, type FileDecisionOptions } from "./useFileReview.js";
24
25
  import {
25
26
  changeSetReviewability,
@@ -62,8 +63,8 @@ export interface FileReviewCardProps {
62
63
  *
63
64
  * `MessageThread` passes `false`: the transcript's stamped edit rows already
64
65
  * show every diff in place, so the expanded body collapses to a compact file
65
- * list (kind letter + path + per-file controls) and the card stays purely
66
- * the decision surface. Decision semantics — digests, scopes,
66
+ * list (kind letter + linked path + `+N −M` + per-file controls) and the card
67
+ * stays purely the decision surface. Decision semantics — digests, scopes,
67
68
  * acknowledgments — are identical in both modes; this is presentation only
68
69
  * (opt-in with a backward-compatible default, DD-011).
69
70
  */
@@ -194,11 +195,18 @@ export const FileReviewCard = memo(function FileReviewCard({
194
195
 
195
196
  // The bar starts expanded exactly when the collapsed bar cannot honestly carry
196
197
  // the decision: an incomplete set (binary acknowledgment / blocked files need
197
- // their per-file context) or a review already in progress. A complete set and
198
- // a settled record start collapsed the transcript's stamped rows already
199
- // show what changed, and the expander reveals the per-file detail on demand.
198
+ // their per-file context), a review already in progress, or a multi-file set
199
+ // in compact-list mode its honest decision surface includes the per-file
200
+ // Keep/Discard controls, which must be visible without hunting behind the
201
+ // expander (the docked strip bounds the height, so expanding is cheap there).
202
+ // Diff-rich mode (`showDiffs`) deliberately stays collapsed for a complete
203
+ // multi-file set: its expanded body is every full diff with no height cap
204
+ // (the workflow file-review list), where "Review" is the act of opening it.
205
+ // A settled record always starts collapsed — the transcript's stamped rows
206
+ // already show what changed, and the expander reveals detail on demand.
200
207
  const [expanded, setExpanded] = useState(
201
- () => interactive && (incomplete || decidedCount > 0),
208
+ () =>
209
+ interactive && (incomplete || decidedCount > 0 || (total > 1 && !showDiffs)),
202
210
  );
203
211
  const bodyId = useId();
204
212
 
@@ -251,12 +259,18 @@ export const FileReviewCard = memo(function FileReviewCard({
251
259
  [onSubmit],
252
260
  );
253
261
 
262
+ // The set's aggregate `+N −M`, summed from the capture-time per-file counts
263
+ // (linesAdded/linesRemoved are stamped by the runner with the same diff
264
+ // algorithm the renderers use — see CapturedFileChange). A file with no
265
+ // counts (binary, secret-withheld, or a record predating the fields)
266
+ // contributes zero; when NO file has counts the aggregate is hidden rather
267
+ // than shown as a dishonest "+0 −0".
254
268
  const totals = useMemo(() => {
255
269
  let additions = 0;
256
270
  let deletions = 0;
257
271
  for (const c of changes) {
258
- if (c.kind === FileChangeKind.ADD) additions += 1;
259
- else if (c.kind === FileChangeKind.DELETE) deletions += 1;
272
+ additions += c.linesAdded;
273
+ deletions += c.linesRemoved;
260
274
  }
261
275
  return { additions, deletions };
262
276
  }, [changes]);
@@ -311,8 +325,16 @@ export const FileReviewCard = memo(function FileReviewCard({
311
325
  <span className="shrink-0 font-medium text-foreground">
312
326
  {interactive ? "Review file changes" : "File changes"}
313
327
  </span>
314
- <span className="min-w-0 flex-1 truncate text-muted-foreground">
315
- {summary}
328
+ {/* The flexible middle: the summary (truncating) plus the set's
329
+ aggregate +N −M, visible even while collapsed so the bar carries
330
+ the magnitude of what is being decided, not just the file count.
331
+ Hidden when no file has counts (FileLineStats). */}
332
+ <span className="flex min-w-0 flex-1 items-center gap-2">
333
+ <span className="min-w-0 truncate text-muted-foreground">{summary}</span>
334
+ <FileLineStats
335
+ linesAdded={totals.additions}
336
+ linesRemoved={totals.deletions}
337
+ />
316
338
  </span>
317
339
 
318
340
  {interactive && showBulkControls && (
@@ -390,9 +412,11 @@ export const FileReviewCard = memo(function FileReviewCard({
390
412
  id={bodyId}
391
413
  className="space-y-2 border-t border-border-muted px-3 py-2.5"
392
414
  >
393
- {/* The list body carries no summary header: the collapsed bar's
394
- file count is the summary, and per-file counts would require
395
- fetching offloaded bodies just to decorate a list. */}
415
+ {/* The list body carries no summary header: the bar already shows
416
+ the file count and the aggregate +N −M (per-file counts are
417
+ capture-time facts on the wire linesAdded/linesRemoved so no
418
+ body fetch is ever needed to decorate the list). Diff mode keeps
419
+ its fuller DiffSummary line above the rendered diffs. */}
396
420
  {showDiffs && (
397
421
  <DiffSummary
398
422
  fileCount={total}
@@ -640,15 +664,17 @@ function CapturedChangeDiff({ change }: { change: CapturedFileChange }) {
640
664
  // ---------------------------------------------------------------------------
641
665
 
642
666
  /**
643
- * One file of the change set as a compact, deliberately inert list line: kind
644
- * letter, path, provenance badge, and an optional trailing slot (the verdict
667
+ * One file of the change set as a compact list line: kind letter, linked path,
668
+ * `+N −M` stats, provenance badge, and an optional trailing slot (the verdict
645
669
  * control while reviewing; the committed verdict once settled).
646
670
  *
647
- * The path is plain text — not `FilePathLink` — because the list is an
648
- * inventory of what the decision covers, not a navigation surface: the diffs
649
- * live on the transcript's stamped edit rows. The full path stays available on
650
- * hover via `title`. A rename shows its source path so the move reads in one
651
- * line.
671
+ * The path renders through `FilePathLink` — the platform-wide path idiom — so
672
+ * a reviewer can jump from the list to the file itself (the session viewer
673
+ * routes the click into the panel's diff-first viewer; GitHub/copy is the
674
+ * fallback) without leaving the decision surface. Deciding and navigating
675
+ * coexist: the link stops click propagation, so opening a file never fights
676
+ * the row's Keep/Discard controls. A rename shows its source path so the move
677
+ * reads in one line.
652
678
  */
653
679
  function FileListRow({
654
680
  change,
@@ -658,7 +684,6 @@ function FileListRow({
658
684
  readonly trailing?: React.ReactNode;
659
685
  }) {
660
686
  const path = change.pathAfter || change.pathBefore;
661
- const { dir, base } = splitDisplayPath(path);
662
687
  const renamedFrom =
663
688
  change.kind === FileChangeKind.RENAME && change.pathBefore !== change.pathAfter
664
689
  ? change.pathBefore
@@ -669,80 +694,22 @@ function FileListRow({
669
694
  data-cursor-target="file-review-list-row"
670
695
  >
671
696
  <FileKindBadge kind={change.kind} />
672
- {/* The directory truncates (context); the base never shrinks (identity) —
673
- the same filename-first rule FilePathLink established. */}
674
- <span
675
- className="flex min-w-0 flex-1 items-center font-mono text-xs text-foreground"
676
- title={path}
677
- >
678
- {renamedFrom && (
679
- <span className="min-w-0 truncate text-muted-foreground-faint">
680
- {renamedFrom}&nbsp;→&nbsp;
681
- </span>
682
- )}
683
- {dir && (
684
- <span className="min-w-0 truncate text-muted-foreground-faint">{dir}</span>
685
- )}
686
- <span className="shrink-0">{base}</span>
687
- </span>
697
+ {renamedFrom && (
698
+ <span className="min-w-0 truncate font-mono text-xs text-muted-foreground-faint">
699
+ {renamedFrom}&nbsp;→&nbsp;
700
+ </span>
701
+ )}
702
+ <FilePathLink path={path} dirDisplay="dim" className="min-w-0 flex-1 text-xs" />
703
+ <FileLineStats
704
+ linesAdded={change.linesAdded}
705
+ linesRemoved={change.linesRemoved}
706
+ />
688
707
  <CaptureBadge change={change} />
689
708
  {trailing}
690
709
  </div>
691
710
  );
692
711
  }
693
712
 
694
- /** The kind letter, tone, and a11y name for each wire {@link FileChangeKind}. */
695
- const KIND_BADGE: Partial<
696
- Record<FileChangeKind, { letter: string; colorClass: string; label: string }>
697
- > = {
698
- [FileChangeKind.ADD]: {
699
- letter: "A",
700
- colorClass: "text-diff-added-fg",
701
- label: "added",
702
- },
703
- [FileChangeKind.MODIFY]: {
704
- letter: "M",
705
- colorClass: "text-diff-hunk-header-fg",
706
- label: "modified",
707
- },
708
- [FileChangeKind.DELETE]: {
709
- letter: "D",
710
- colorClass: "text-diff-removed-fg",
711
- label: "deleted",
712
- },
713
- [FileChangeKind.RENAME]: {
714
- letter: "R",
715
- colorClass: "text-diff-hunk-header-fg",
716
- label: "renamed",
717
- },
718
- // A binary change is a modification whose diff is not text; the letter stays
719
- // M and the binary-ness is told by the row's "Keep anyway" + reason note.
720
- [FileChangeKind.BINARY_CHANGE]: {
721
- letter: "M",
722
- colorClass: "text-diff-hunk-header-fg",
723
- label: "modified (binary)",
724
- },
725
- };
726
-
727
- /**
728
- * The single-letter change-kind marker — the same M/A/D visual vocabulary as
729
- * the version-history `DiffFileList`, extended with R for captured renames.
730
- * The letter itself is the non-color channel; the full word is exposed to
731
- * assistive tech. An UNSPECIFIED kind renders nothing rather than guessing.
732
- */
733
- function FileKindBadge({ kind }: { kind: FileChangeKind }) {
734
- const badge = KIND_BADGE[kind];
735
- if (!badge) return null;
736
- return (
737
- <span
738
- className={cn("shrink-0 font-mono text-[10px] font-bold", badge.colorClass)}
739
- aria-label={badge.label}
740
- >
741
- {badge.letter}
742
- </span>
743
- );
744
- }
745
-
746
713
  /**
747
714
  * A change's committed verdict as a static badge: APPROVE reads "Kept" (the
748
715
  * bytes were reconciled into the workspace), REJECT reads "Discarded" (reverted
@@ -48,15 +48,6 @@ export interface MessageEntryProps {
48
48
  * non-human messages.
49
49
  */
50
50
  readonly interactionMode?: InteractionMode;
51
- /**
52
- * Renders a `MESSAGE_HUMAN` entry as a compact "Build from plan" chip
53
- * instead of a prompt bubble. Stamped by the thread builder from
54
- * `execution_config.build_from_plan`: the turn's message is a short label
55
- * (the implement instruction is runner-injected), so a full-width bubble
56
- * would dignify machine plumbing as user prose. Ignored for non-human
57
- * messages.
58
- */
59
- readonly isBuildFromPlan?: boolean;
60
51
  }
61
52
 
62
53
  /**
@@ -89,7 +80,6 @@ export const MessageEntry = memo(function MessageEntry({
89
80
  onEdit,
90
81
  isPlanDocument,
91
82
  interactionMode,
92
- isBuildFromPlan,
93
83
  }: MessageEntryProps) {
94
84
  useRenderTracer("MessageEntry", {
95
85
  messageType: message.type,
@@ -99,11 +89,6 @@ export const MessageEntry = memo(function MessageEntry({
99
89
 
100
90
  switch (message.type) {
101
91
  case MessageType.MESSAGE_HUMAN:
102
- if (isBuildFromPlan) {
103
- return (
104
- <BuildFromPlanChip content={message.content} className={className} />
105
- );
106
- }
107
92
  return (
108
93
  <HumanMessage
109
94
  content={message.content}
@@ -196,53 +181,6 @@ function HumanMessage({
196
181
  * label is not user prose to rephrase; refining the plan happens in the plan
197
182
  * document, and the build is re-triggered from its card.
198
183
  */
199
- function BuildFromPlanChip({
200
- content,
201
- className,
202
- }: {
203
- content: string;
204
- className?: string;
205
- }) {
206
- return (
207
- <div
208
- role="article"
209
- aria-label="Build from plan"
210
- className={cn("flex justify-end", className)}
211
- >
212
- <span
213
- className={cn(
214
- "inline-flex items-center gap-1.5 rounded-full border border-border",
215
- "bg-muted-subtle px-3 py-1.5 text-xs font-medium text-foreground",
216
- )}
217
- >
218
- <BuildFromPlanIcon />
219
- {content}
220
- </span>
221
- </div>
222
- );
223
- }
224
-
225
- /** The plan-card glyph (document lines + implement arrow) at chip scale. */
226
- function BuildFromPlanIcon() {
227
- return (
228
- <svg
229
- width="13"
230
- height="13"
231
- viewBox="0 0 16 16"
232
- fill="none"
233
- stroke="currentColor"
234
- strokeWidth="1.5"
235
- strokeLinecap="round"
236
- strokeLinejoin="round"
237
- className="shrink-0 text-muted-foreground"
238
- aria-hidden="true"
239
- >
240
- <path d="M3 4h10M3 8h7M3 12h8" />
241
- <path d="M12.5 10.5l1.5 1.5-1.5 1.5" />
242
- </svg>
243
- );
244
- }
245
-
246
184
  function EditIcon() {
247
185
  return (
248
186
  <svg
@@ -289,7 +289,7 @@ export function threadContentColumnClass(
289
289
  * part of the public API.
290
290
  */
291
291
  export type ThreadItem =
292
- | { readonly kind: "message"; readonly message: AgentMessage; readonly key: string; readonly isPending?: boolean; readonly isFailed?: boolean; readonly isEditable?: boolean; readonly isPlanDocument?: boolean; readonly interactionMode?: InteractionMode; readonly isBuildFromPlan?: boolean }
292
+ | { readonly kind: "message"; readonly message: AgentMessage; readonly key: string; readonly isPending?: boolean; readonly isFailed?: boolean; readonly isEditable?: boolean; readonly isPlanDocument?: boolean; readonly interactionMode?: InteractionMode }
293
293
  | { readonly kind: "tool-group"; readonly toolCalls: readonly ToolCall[]; readonly subAgentExecutions: readonly SubAgentExecution[]; readonly key: string }
294
294
  | { readonly kind: "sub-agent"; readonly subAgentExecution: SubAgentExecution; readonly key: string }
295
295
  | { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
@@ -317,9 +317,9 @@ export type ThreadItem =
317
317
  readonly planTitle?: string;
318
318
  /**
319
319
  * True for the most recent completed Plan execution in the thread — the
320
- * only plan whose card carries the primary "Build from plan" action.
321
- * Superseded plans keep their review actions (open/download) but never a
322
- * build CTA, so a stale plan can't be implemented by accident.
320
+ * only plan whose card carries the primary "Build" action. Superseded
321
+ * plans keep their review actions (open/download) but never a build
322
+ * CTA, so a stale plan can't be implemented by accident.
323
323
  */
324
324
  readonly isLatestPlan: boolean;
325
325
  }
@@ -537,9 +537,9 @@ export function buildThreadItems(
537
537
  ? allExecutions.length - 1
538
538
  : -1;
539
539
 
540
- // The most recent completed Plan execution owns the primary "Build from
541
- // plan" action; every earlier plan renders as a review-only record. Resolved
542
- // up front so each segment can stamp its own card with the right authority.
540
+ // The most recent completed Plan execution owns the primary "Build" action;
541
+ // every earlier plan renders as a review-only record. Resolved up front so
542
+ // each segment can stamp its own card with the right authority.
543
543
  let latestPlanExecutionId: string | null = null;
544
544
  for (let i = allExecutions.length - 1; i >= 0; i--) {
545
545
  if (isCompletedPlanExecution(allExecutions[i])) {
@@ -648,7 +648,13 @@ export function buildThreadItems(
648
648
  }
649
649
 
650
650
  const specMessage = exec.spec?.message;
651
- if (specMessage && specMessage !== "execute") {
651
+ // A Build-from-plan turn synthesizes NO prompt item: its message is a
652
+ // machine-written label ("Build from plan"), not user prose — the real
653
+ // instruction is runner-injected from the same flag. The plan card
654
+ // directly above the turn is the visible cause; rendering the label as a
655
+ // user bubble would attribute words to the user they never typed.
656
+ const isBuildTurn = exec.spec?.executionConfig?.buildFromPlan === true;
657
+ if (specMessage && specMessage !== "execute" && !isBuildTurn) {
652
658
  const syntheticHumanMsg = create(AgentMessageSchema);
653
659
  syntheticHumanMsg.type = MessageType.MESSAGE_HUMAN;
654
660
  syntheticHumanMsg.content = specMessage;
@@ -671,10 +677,6 @@ export function buildThreadItems(
671
677
  // The turn's mode marks the prompt bubble (a "Plan" pill on Plan
672
678
  // turns) so the transcript reads unambiguously after mode switches.
673
679
  interactionMode: exec.spec?.executionConfig?.interactionMode,
674
- // A Build-from-plan turn's prompt renders as a compact chip, not the
675
- // message text — the text is a short label; the real instruction is
676
- // runner-injected from the same flag.
677
- isBuildFromPlan: exec.spec?.executionConfig?.buildFromPlan || undefined,
678
680
  });
679
681
  }
680
682
 
@@ -896,11 +898,19 @@ export function buildThreadItems(
896
898
  const reason = lastExec?.status?.error;
897
899
  if (reason) {
898
900
  const specMessage = lastExec?.spec?.message;
901
+ // A failed build turn offers no inline Retry: resending its label as an
902
+ // ordinary message would drop the buildFromPlan flag (no runner
903
+ // directive, no plan attachment). The plan card above the error is the
904
+ // fully-wired retry — its Build button re-runs the whole pipeline.
905
+ const isBuildTurn =
906
+ lastExec?.spec?.executionConfig?.buildFromPlan === true;
899
907
  items.push({
900
908
  kind: "execution-error",
901
909
  error: reason,
902
910
  retryMessage:
903
- specMessage && specMessage !== "execute" ? specMessage : undefined,
911
+ specMessage && specMessage !== "execute" && !isBuildTurn
912
+ ? specMessage
913
+ : undefined,
904
914
  key: `execution-error-${lastExec?.metadata?.id ?? lastPhase}`,
905
915
  });
906
916
  }
@@ -936,8 +946,13 @@ export function buildThreadItems(
936
946
  // not thread items at all: they live in the composer-docked FileReviewDock.
937
947
 
938
948
  if (pendingUserMessage) {
949
+ // A FAILED send never created an execution, so an existing execution with
950
+ // identical text (e.g. two build attempts of the same plan — both
951
+ // "Build from plan") must never swallow the failed bubble. Suppression
952
+ // only applies to the succeeded-send window where the stream has
953
+ // delivered the same turn but the pending state hasn't cleared yet.
939
954
  const alreadySynthesized =
940
- lastExec?.spec?.message === pendingUserMessage;
955
+ !pendingMessageFailed && lastExec?.spec?.message === pendingUserMessage;
941
956
  if (!alreadySynthesized) {
942
957
  const syntheticPending = create(AgentMessageSchema);
943
958
  syntheticPending.type = MessageType.MESSAGE_HUMAN;
@@ -1333,7 +1348,6 @@ export function ThreadItemRenderer({
1333
1348
  className={item.isPending ? "opacity-70" : undefined}
1334
1349
  isPlanDocument={item.isPlanDocument}
1335
1350
  interactionMode={item.interactionMode}
1336
- isBuildFromPlan={item.isBuildFromPlan}
1337
1351
  onEdit={
1338
1352
  item.isEditable && onEditMessage
1339
1353
  ? () => onEditMessage(item.message.content)