@stigmer/react 3.1.3 → 3.1.5

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 (248) hide show
  1. package/composer/SessionComposer.d.ts +31 -0
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +12 -4
  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 +4 -4
  18. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  19. package/execution/ArtifactPreviewModal.js +15 -142
  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/FileReviewContext.d.ts +15 -1
  30. package/execution/FileReviewContext.d.ts.map +1 -1
  31. package/execution/FileReviewContext.js +20 -1
  32. package/execution/FileReviewContext.js.map +1 -1
  33. package/execution/MessageThread.d.ts +65 -2
  34. package/execution/MessageThread.d.ts.map +1 -1
  35. package/execution/MessageThread.js +42 -23
  36. package/execution/MessageThread.js.map +1 -1
  37. package/execution/TodoCard.d.ts +8 -0
  38. package/execution/TodoCard.d.ts.map +1 -1
  39. package/execution/TodoCard.js +6 -4
  40. package/execution/TodoCard.js.map +1 -1
  41. package/execution/TodoList.d.ts +20 -1
  42. package/execution/TodoList.d.ts.map +1 -1
  43. package/execution/TodoList.js +9 -6
  44. package/execution/TodoList.js.map +1 -1
  45. package/execution/ToolCallDetail.d.ts +3 -2
  46. package/execution/ToolCallDetail.d.ts.map +1 -1
  47. package/execution/ToolCallDetail.js +49 -16
  48. package/execution/ToolCallDetail.js.map +1 -1
  49. package/execution/file-review-status.d.ts +15 -0
  50. package/execution/file-review-status.d.ts.map +1 -1
  51. package/execution/file-review-status.js +22 -0
  52. package/execution/file-review-status.js.map +1 -1
  53. package/execution/index.d.ts +13 -7
  54. package/execution/index.d.ts.map +1 -1
  55. package/execution/index.js +7 -4
  56. package/execution/index.js.map +1 -1
  57. package/execution/useArtifactContent.d.ts +6 -1
  58. package/execution/useArtifactContent.d.ts.map +1 -1
  59. package/execution/useArtifactContent.js +7 -2
  60. package/execution/useArtifactContent.js.map +1 -1
  61. package/execution/useArtifactInspection.d.ts +85 -0
  62. package/execution/useArtifactInspection.d.ts.map +1 -0
  63. package/execution/useArtifactInspection.js +143 -0
  64. package/execution/useArtifactInspection.js.map +1 -0
  65. package/execution/useCreateAgentExecution.d.ts +11 -0
  66. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  67. package/execution/useCreateAgentExecution.js +1 -0
  68. package/execution/useCreateAgentExecution.js.map +1 -1
  69. package/github/useGitHubFileReader.d.ts +4 -2
  70. package/github/useGitHubFileReader.d.ts.map +1 -1
  71. package/github/useGitHubFileReader.js +16 -3
  72. package/github/useGitHubFileReader.js.map +1 -1
  73. package/github/useGitHubTreeLister.d.ts +2 -1
  74. package/github/useGitHubTreeLister.d.ts.map +1 -1
  75. package/github/useGitHubTreeLister.js +7 -3
  76. package/github/useGitHubTreeLister.js.map +1 -1
  77. package/index.d.ts +4 -4
  78. package/index.d.ts.map +1 -1
  79. package/index.js +3 -3
  80. package/index.js.map +1 -1
  81. package/internal/MermaidDiagram.d.ts +47 -0
  82. package/internal/MermaidDiagram.d.ts.map +1 -0
  83. package/internal/MermaidDiagram.js +118 -0
  84. package/internal/MermaidDiagram.js.map +1 -0
  85. package/internal/VirtualizedThread.d.ts +3 -2
  86. package/internal/VirtualizedThread.d.ts.map +1 -1
  87. package/internal/VirtualizedThread.js +3 -2
  88. package/internal/VirtualizedThread.js.map +1 -1
  89. package/internal/markdown-components.d.ts.map +1 -1
  90. package/internal/markdown-components.js +34 -0
  91. package/internal/markdown-components.js.map +1 -1
  92. package/internal/mermaid-loader.d.ts +26 -0
  93. package/internal/mermaid-loader.d.ts.map +1 -0
  94. package/internal/mermaid-loader.js +34 -0
  95. package/internal/mermaid-loader.js.map +1 -0
  96. package/internal/mermaid-theme-css.d.ts +41 -0
  97. package/internal/mermaid-theme-css.d.ts.map +1 -0
  98. package/internal/mermaid-theme-css.js +179 -0
  99. package/internal/mermaid-theme-css.js.map +1 -0
  100. package/internal/slug.d.ts +9 -5
  101. package/internal/slug.d.ts.map +1 -1
  102. package/internal/slug.js +10 -6
  103. package/internal/slug.js.map +1 -1
  104. package/package.json +5 -4
  105. package/session/SessionViewer.d.ts +11 -1
  106. package/session/SessionViewer.d.ts.map +1 -1
  107. package/session/SessionViewer.js +101 -21
  108. package/session/SessionViewer.js.map +1 -1
  109. package/session/artifact-document.d.ts +13 -0
  110. package/session/artifact-document.d.ts.map +1 -0
  111. package/session/artifact-document.js +22 -0
  112. package/session/artifact-document.js.map +1 -0
  113. package/session/facets/ArtifactsTab.d.ts +30 -4
  114. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  115. package/session/facets/ArtifactsTab.js +32 -10
  116. package/session/facets/ArtifactsTab.js.map +1 -1
  117. package/session/index.d.ts +3 -1
  118. package/session/index.d.ts.map +1 -1
  119. package/session/index.js +5 -1
  120. package/session/index.js.map +1 -1
  121. package/session/useSessionArtifacts.d.ts +17 -0
  122. package/session/useSessionArtifacts.d.ts.map +1 -1
  123. package/session/useSessionArtifacts.js +16 -5
  124. package/session/useSessionArtifacts.js.map +1 -1
  125. package/session/useSessionConversation.d.ts +17 -1
  126. package/session/useSessionConversation.d.ts.map +1 -1
  127. package/session/useSessionConversation.js +32 -4
  128. package/session/useSessionConversation.js.map +1 -1
  129. package/session/useSessionPageFlow.d.ts.map +1 -1
  130. package/session/useSessionPageFlow.js +1 -0
  131. package/session/useSessionPageFlow.js.map +1 -1
  132. package/session/useSessionPanel.d.ts +20 -0
  133. package/session/useSessionPanel.d.ts.map +1 -1
  134. package/session/useSessionPanel.js +13 -0
  135. package/session/useSessionPanel.js.map +1 -1
  136. package/session/useSessionRailViews.d.ts +13 -1
  137. package/session/useSessionRailViews.d.ts.map +1 -1
  138. package/session/useSessionRailViews.js +5 -3
  139. package/session/useSessionRailViews.js.map +1 -1
  140. package/session/useWorkspaceReadRefs.d.ts +45 -0
  141. package/session/useWorkspaceReadRefs.d.ts.map +1 -0
  142. package/session/useWorkspaceReadRefs.js +104 -0
  143. package/session/useWorkspaceReadRefs.js.map +1 -0
  144. package/src/composer/SessionComposer.tsx +69 -1
  145. package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
  146. package/src/execution/ArtifactApplyButton.tsx +79 -0
  147. package/src/execution/ArtifactContentBody.tsx +255 -0
  148. package/src/execution/ArtifactDocument.tsx +344 -0
  149. package/src/execution/ArtifactPreviewModal.tsx +39 -405
  150. package/src/execution/ArtifactRow.tsx +165 -0
  151. package/src/execution/ArtifactsWidget.tsx +24 -33
  152. package/src/execution/FileReviewContext.ts +27 -1
  153. package/src/execution/MessageThread.tsx +110 -22
  154. package/src/execution/TodoCard.tsx +20 -4
  155. package/src/execution/TodoList.tsx +26 -4
  156. package/src/execution/ToolCallDetail.tsx +69 -16
  157. package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
  158. package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
  159. package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
  160. package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
  161. package/src/execution/__tests__/ToolCallDetail.test.tsx +188 -2
  162. package/src/execution/__tests__/file-review-status.test.ts +47 -0
  163. package/src/execution/__tests__/message-entry.test.tsx +50 -2
  164. package/src/execution/__tests__/message-thread-slots.test.tsx +463 -0
  165. package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
  166. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
  167. package/src/execution/file-review-status.ts +24 -0
  168. package/src/execution/index.ts +27 -5
  169. package/src/execution/useArtifactContent.ts +7 -0
  170. package/src/execution/useArtifactInspection.ts +252 -0
  171. package/src/execution/useCreateAgentExecution.ts +12 -0
  172. package/src/github/__tests__/useGitHubFileReader.test.ts +35 -2
  173. package/src/github/__tests__/useGitHubTreeLister.test.ts +14 -0
  174. package/src/github/useGitHubFileReader.ts +19 -6
  175. package/src/github/useGitHubTreeLister.ts +7 -3
  176. package/src/index.ts +15 -2
  177. package/src/internal/MermaidDiagram.tsx +164 -0
  178. package/src/internal/VirtualizedThread.tsx +5 -1
  179. package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
  180. package/src/internal/__tests__/markdown-components.test.tsx +93 -1
  181. package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
  182. package/src/internal/markdown-components.tsx +36 -1
  183. package/src/internal/mermaid-loader.ts +44 -0
  184. package/src/internal/mermaid-theme-css.ts +178 -0
  185. package/src/internal/slug.ts +10 -6
  186. package/src/session/SessionViewer.tsx +204 -35
  187. package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
  188. package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
  189. package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
  190. package/src/session/__tests__/useSessionConversation.test.tsx +172 -1
  191. package/src/session/__tests__/useSessionPanel.test.ts +70 -1
  192. package/src/session/__tests__/useWorkspaceReadRefs.test.ts +245 -0
  193. package/src/session/artifact-document.ts +23 -0
  194. package/src/session/facets/ArtifactsTab.tsx +66 -18
  195. package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
  196. package/src/session/index.ts +7 -1
  197. package/src/session/useSessionArtifacts.ts +16 -5
  198. package/src/session/useSessionConversation.ts +48 -6
  199. package/src/session/useSessionPageFlow.ts +1 -0
  200. package/src/session/useSessionPanel.ts +41 -0
  201. package/src/session/useSessionRailViews.tsx +21 -1
  202. package/src/session/useWorkspaceReadRefs.ts +118 -0
  203. package/src/workspace/FileViewer.tsx +144 -38
  204. package/src/workspace/WorkspaceFileReader.ts +21 -0
  205. package/src/workspace/__tests__/FileViewer.test.tsx +138 -3
  206. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +20 -0
  207. package/src/workspace/__tests__/workspaceListingCache.test.ts +114 -0
  208. package/src/workspace/index.ts +4 -1
  209. package/src/workspace/useWorkspaceEntries.ts +11 -0
  210. package/src/workspace/useWorkspaceFileContent.ts +4 -1
  211. package/src/workspace/useWorkspaceFileSearch.ts +8 -3
  212. package/src/workspace/useWorkspaceFiles.ts +3 -2
  213. package/src/workspace/workspaceListingCache.ts +21 -7
  214. package/styles.css +1 -1
  215. package/workspace/FileViewer.d.ts +1 -1
  216. package/workspace/FileViewer.d.ts.map +1 -1
  217. package/workspace/FileViewer.js +55 -5
  218. package/workspace/FileViewer.js.map +1 -1
  219. package/workspace/WorkspaceFileReader.d.ts +17 -0
  220. package/workspace/WorkspaceFileReader.d.ts.map +1 -1
  221. package/workspace/WorkspaceFileReader.js +18 -0
  222. package/workspace/WorkspaceFileReader.js.map +1 -1
  223. package/workspace/index.d.ts +1 -1
  224. package/workspace/index.d.ts.map +1 -1
  225. package/workspace/index.js +1 -1
  226. package/workspace/index.js.map +1 -1
  227. package/workspace/useWorkspaceEntries.d.ts +11 -0
  228. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  229. package/workspace/useWorkspaceEntries.js.map +1 -1
  230. package/workspace/useWorkspaceFileContent.d.ts.map +1 -1
  231. package/workspace/useWorkspaceFileContent.js +4 -1
  232. package/workspace/useWorkspaceFileContent.js.map +1 -1
  233. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -1
  234. package/workspace/useWorkspaceFileSearch.js +8 -3
  235. package/workspace/useWorkspaceFileSearch.js.map +1 -1
  236. package/workspace/useWorkspaceFiles.d.ts +2 -1
  237. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  238. package/workspace/useWorkspaceFiles.js +3 -2
  239. package/workspace/useWorkspaceFiles.js.map +1 -1
  240. package/workspace/workspaceListingCache.d.ts +1 -1
  241. package/workspace/workspaceListingCache.d.ts.map +1 -1
  242. package/workspace/workspaceListingCache.js +20 -7
  243. package/workspace/workspaceListingCache.js.map +1 -1
  244. package/execution/ArtifactCard.d.ts +0 -81
  245. package/execution/ArtifactCard.d.ts.map +0 -1
  246. package/execution/ArtifactCard.js +0 -130
  247. package/execution/ArtifactCard.js.map +0 -1
  248. package/src/execution/ArtifactCard.tsx +0 -320
@@ -5,9 +5,10 @@ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexe
5
5
  import { cn } from "@stigmer/theme";
6
6
  import {
7
7
  useSessionArtifacts,
8
+ artifactKey,
8
9
  type SessionArtifactEntry,
9
10
  } from "../session/useSessionArtifacts.js";
10
- import { ArtifactCard } from "./ArtifactCard.js";
11
+ import { ArtifactRow } from "./ArtifactRow.js";
11
12
  import { ArtifactPreviewModal } from "./ArtifactPreviewModal.js";
12
13
  import type { ApplyResourceResult } from "../library/useApplyResource.js";
13
14
 
@@ -45,19 +46,16 @@ export interface ArtifactsWidgetProps {
45
46
  * a file-explorer-like view of the conversation's output — no
46
47
  * execution/turn concepts are exposed.
47
48
  *
48
- * Composes {@link ArtifactCard} (summary + detection badges) with
49
- * {@link ArtifactPreviewModal} (full content review + Apply/Push CTA).
50
- * The card's "Preview" action opens the modal; the modal is the sole
51
- * location for Apply/Push actions (review-before-apply pattern).
49
+ * The panel-less counterpart of the session panel's Artifacts facet: it
50
+ * renders the same dense {@link ArtifactRow} list, but having no editor pane
51
+ * to open document tabs into a click opens the {@link ArtifactPreviewModal}
52
+ * (the review-before-apply popup, and the sole home of the Apply/Push CTA).
53
+ * This is the embeddable path a platform builder drops into their own sidebar.
52
54
  *
53
55
  * Returns `null` when the executions list is empty or no execution
54
56
  * has artifacts, matching the conditional-render pattern of
55
57
  * {@link ExecutionProgress} and {@link UsageWidget}.
56
58
  *
57
- * Renders without card chrome — each {@link ArtifactCard} provides its
58
- * own border and padding. The consumer controls the container styling
59
- * (or places the widget directly in a flex/grid layout).
60
- *
61
59
  * All visual properties flow through `--stgm-*` tokens.
62
60
  *
63
61
  * @example
@@ -74,7 +72,7 @@ export interface ArtifactsWidgetProps {
74
72
  * />
75
73
  * ```
76
74
  *
77
- * @see {@link ArtifactCard} — compact summary card per artifact
75
+ * @see {@link ArtifactRow} — the dense row rendered per artifact
78
76
  * @see {@link ArtifactPreviewModal} — full preview with Apply/Push CTA
79
77
  * @see {@link useSessionArtifacts} — headless session-level artifact aggregation hook
80
78
  * @see {@link useExecutionArtifacts} — headless single-execution artifact extraction hook
@@ -88,27 +86,22 @@ export function ArtifactsWidget({
88
86
  const { artifacts, hasArtifacts, artifactCount } =
89
87
  useSessionArtifacts(executions);
90
88
 
91
- // Store the dedup key (sandboxPath or name) instead of a snapshot of
92
- // the full entry. The actual entry is derived from the live artifacts
93
- // list on every render, so the preview modal always reflects the
94
- // latest artifact version even when a newer execution publishes an
95
- // updated artifact for the same path while the modal is open.
89
+ // Store the artifact identity (not a snapshot of the full entry). The entry is
90
+ // re-derived from the live artifacts list on every render, so the modal always
91
+ // reflects the latest artifact version even when a newer execution publishes
92
+ // an updated artifact for the same path while the modal is open.
96
93
  const [previewKey, setPreviewKey] = useState<string | null>(null);
97
94
 
98
95
  const previewEntry = useMemo<SessionArtifactEntry | null>(
99
96
  () =>
100
97
  previewKey !== null
101
- ? artifacts.find(
102
- (e) =>
103
- (e.artifact.sandboxPath || e.artifact.name) === previewKey,
104
- ) ?? null
98
+ ? artifacts.find((e) => artifactKey(e.artifact) === previewKey) ?? null
105
99
  : null,
106
100
  [previewKey, artifacts],
107
101
  );
108
102
 
109
- const handlePreview = useCallback(
110
- (entry: SessionArtifactEntry) =>
111
- setPreviewKey(entry.artifact.sandboxPath || entry.artifact.name),
103
+ const handleOpen = useCallback(
104
+ (entry: SessionArtifactEntry) => setPreviewKey(artifactKey(entry.artifact)),
112
105
  [],
113
106
  );
114
107
 
@@ -123,19 +116,17 @@ export function ArtifactsWidget({
123
116
  </span>
124
117
  </div>
125
118
 
126
- <div role="list" className="space-y-2">
119
+ <ul role="list" className="flex flex-col">
127
120
  {artifacts.map((entry) => (
128
- <div key={entry.artifact.storageKey} role="listitem">
129
- <ArtifactCard
130
- artifact={entry.artifact}
131
- executionId={entry.executionId}
132
- org={org}
133
- hasNameCollision={entry.hasNameCollision}
134
- onPreview={() => handlePreview(entry)}
135
- />
136
- </div>
121
+ <ArtifactRow
122
+ key={artifactKey(entry.artifact)}
123
+ artifact={entry.artifact}
124
+ executionId={entry.executionId}
125
+ hasNameCollision={entry.hasNameCollision}
126
+ onOpen={() => handleOpen(entry)}
127
+ />
137
128
  ))}
138
- </div>
129
+ </ul>
139
130
 
140
131
  {previewEntry && (
141
132
  <ArtifactPreviewModal
@@ -1,6 +1,10 @@
1
1
  import { createContext, useContext } from "react";
2
- import type { FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
2
+ import type {
3
+ CapturedFileChange,
4
+ FileChangeSet,
5
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
3
6
  import {
7
+ fileReviewRowChange,
4
8
  fileReviewRowState,
5
9
  type FileReviewRowState,
6
10
  } from "./file-review-status.js";
@@ -51,3 +55,25 @@ export function useFileReviewRowState(
51
55
  if (!fileChangeSetId) return null;
52
56
  return fileReviewRowState(changeSetsById.get(fileChangeSetId), rowPath);
53
57
  }
58
+
59
+ /**
60
+ * The captured change a stamped tool row should render as its inline diff, or
61
+ * `null` to keep the row's own content view (unstamped row, unknown set, path
62
+ * miss, or a non-reviewable change — see {@link fileReviewRowChange} for the
63
+ * honest-degradation rules).
64
+ *
65
+ * The diff sibling of {@link useFileReviewRowState}: both read the same
66
+ * context entry through the same path matcher, so a row's badge and its diff
67
+ * always describe the same captured artifact.
68
+ *
69
+ * @param fileChangeSetId The row's `ToolCall.file_change_set_id` (may be "").
70
+ * @param rowPath The row's file path (the presenter's primary arg).
71
+ */
72
+ export function useFileReviewRowChange(
73
+ fileChangeSetId: string,
74
+ rowPath: string | null,
75
+ ): CapturedFileChange | null {
76
+ const { changeSetsById } = useContext(FileReviewContext);
77
+ if (!fileChangeSetId) return null;
78
+ return fileReviewRowChange(changeSetsById.get(fileChangeSetId), rowPath);
79
+ }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { lazy, memo, Suspense, useCallback, useMemo, useState } from "react";
3
+ import { lazy, memo, Suspense, useCallback, useMemo, useState, type ComponentType } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
5
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
6
6
  import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
@@ -21,18 +21,19 @@ import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/
21
21
  import { displayFileChangeSets } from "@stigmer/sdk";
22
22
  import { cn } from "@stigmer/theme";
23
23
  import { isTerminalPhase } from "./execution-phases.js";
24
- import { MessageEntry } from "./MessageEntry.js";
24
+ import { MessageEntry, type MessageEntryProps } from "./MessageEntry.js";
25
25
  import { ToolCallGroup } from "./ToolCallGroup.js";
26
26
  import { SubAgentSection } from "./SubAgentSection.js";
27
27
  import { ExecutionPhaseBadge } from "./ExecutionPhaseBadge.js";
28
- import { SetupProgress } from "./SetupProgress.js";
29
- import { ApprovalCard } from "./ApprovalCard.js";
28
+ import { SetupProgress, type SetupProgressProps } from "./SetupProgress.js";
29
+ import { ApprovalCard, type ApprovalCardProps } from "./ApprovalCard.js";
30
30
  import { FileReviewCard } from "./FileReviewCard.js";
31
31
  import { SummarizationCard } from "./SummarizationCard.js";
32
- import { PlanCompletionCard } from "./PlanCompletionCard.js";
33
- import { PlanArtifactCard } from "./PlanArtifactCard.js";
34
- import { PlanStreamingCard } from "./PlanStreamingCard.js";
35
- import { TodoCard } from "./TodoCard.js";
32
+ import { PlanCompletionCard, type PlanCompletionCardProps } from "./PlanCompletionCard.js";
33
+ import { PlanArtifactCard, type PlanArtifactCardProps } from "./PlanArtifactCard.js";
34
+ import { PlanStreamingCard, type PlanStreamingCardProps } from "./PlanStreamingCard.js";
35
+ import { TodoCard, type TodoCardProps } from "./TodoCard.js";
36
+ import type { TodoRowProps } from "./TodoList.js";
36
37
  import { findPlanArtifact } from "../library/detect-plan-artifact.js";
37
38
  import { findStreamingPlan } from "../library/detect-streaming-plan.js";
38
39
  import type { SummarizationEventView } from "./useContextWindow.js";
@@ -63,6 +64,49 @@ const EMPTY_APPROVALS: readonly PendingApproval[] = [];
63
64
  const EMPTY_SUBMITTING_IDS: ReadonlySet<string> = new Set();
64
65
  const EMPTY_APPROVAL_ERRORS: ReadonlyMap<string, Error> = new Map();
65
66
 
67
+ /**
68
+ * Component overrides for the chrome {@link MessageThread} renders — the
69
+ * adaptation surface for hosts whose design system needs different
70
+ * *structure*, not just different `--stgm-*` token values (stigmer#187).
71
+ *
72
+ * Each slot is keyed by the built-in component it replaces and receives
73
+ * exactly that component's exported props interface, so an override can
74
+ * always delegate to the built-in for the cases it does not customize
75
+ * (e.g. a `MessageEntry` slot that restyles `MESSAGE_HUMAN` bubbles and
76
+ * renders the default `MessageEntry` for everything else).
77
+ *
78
+ * Define slot components at module level (or memoize them). The thread's
79
+ * rows rely on `React.memo` + structural sharing to skip re-renders during
80
+ * streaming (DD-009/DD-010); a slot component recreated on every host
81
+ * render defeats that for its rows.
82
+ */
83
+ export interface MessageThreadSlots {
84
+ /**
85
+ * All message bubbles — human, assistant, thinking, and system entries.
86
+ * Also wraps the failed-send bubble (the inline Retry chrome around it
87
+ * stays built-in).
88
+ */
89
+ readonly MessageEntry?: ComponentType<MessageEntryProps>;
90
+ /** The HITL tool-approval gate card. */
91
+ readonly ApprovalCard?: ComponentType<ApprovalCardProps>;
92
+ /** The collapsible in-thread to-dos card. */
93
+ readonly TodoCard?: ComponentType<TodoCardProps>;
94
+ /**
95
+ * One to-do row inside the built-in {@link TodoCard}'s list. Ignored
96
+ * when `TodoCard` is also overridden (the replacement receives it via
97
+ * {@link TodoCardProps.TodoRow} and may forward or ignore it).
98
+ */
99
+ readonly TodoRow?: ComponentType<TodoRowProps>;
100
+ /** Completed Plan turn that published a plan artifact. */
101
+ readonly PlanArtifactCard?: ComponentType<PlanArtifactCardProps>;
102
+ /** Completed Plan turn without an artifact — the bare Implement CTA. */
103
+ readonly PlanCompletionCard?: ComponentType<PlanCompletionCardProps>;
104
+ /** Live stand-in card while a Plan turn is writing its document. */
105
+ readonly PlanStreamingCard?: ComponentType<PlanStreamingCardProps>;
106
+ /** Pre-first-token setup / "Thinking…" indicator. */
107
+ readonly SetupProgress?: ComponentType<SetupProgressProps>;
108
+ }
109
+
66
110
  /** Props for {@link MessageThread}. */
67
111
  export interface MessageThreadProps {
68
112
  /** Completed executions in chronological order. */
@@ -261,6 +305,12 @@ export interface MessageThreadProps {
261
305
  * existing consumers see no layout change.
262
306
  */
263
307
  readonly contentColumn?: ThreadContentColumn;
308
+ /**
309
+ * Component overrides for the thread's chrome — see
310
+ * {@link MessageThreadSlots}. Omitted slots render the built-ins;
311
+ * omitting the prop entirely changes nothing (DD-011).
312
+ */
313
+ readonly slots?: MessageThreadSlots;
264
314
  }
265
315
 
266
316
  /** Reading-column alignment for {@link MessageThread} content. */
@@ -1020,6 +1070,7 @@ export function MessageThread({
1020
1070
  planActionsDisabled,
1021
1071
  planBuildPending,
1022
1072
  contentColumn,
1073
+ slots,
1023
1074
  }: MessageThreadProps) {
1024
1075
  useRenderTracer("MessageThread", { executions, activeStreamExecution });
1025
1076
 
@@ -1124,6 +1175,7 @@ export function MessageThread({
1124
1175
  onRetrySend={onRetrySend}
1125
1176
  onRetryExecution={onRetryExecution}
1126
1177
  onEditMessage={onEditMessage}
1178
+ slots={slots}
1127
1179
  />
1128
1180
  </Suspense>
1129
1181
  </div>
@@ -1152,6 +1204,7 @@ export function MessageThread({
1152
1204
  onRetrySend={onRetrySend}
1153
1205
  onRetryExecution={onRetryExecution}
1154
1206
  onEditMessage={onEditMessage}
1207
+ slots={slots}
1155
1208
  />
1156
1209
  );
1157
1210
  }
@@ -1186,6 +1239,7 @@ interface NonVirtualizedThreadProps {
1186
1239
  readonly onRetrySend?: () => void;
1187
1240
  readonly onRetryExecution?: (message: string) => void;
1188
1241
  readonly onEditMessage?: (text: string) => void;
1242
+ readonly slots?: MessageThreadSlots;
1189
1243
  }
1190
1244
 
1191
1245
  function NonVirtualizedThread({
@@ -1209,6 +1263,7 @@ function NonVirtualizedThread({
1209
1263
  onRetrySend,
1210
1264
  onRetryExecution,
1211
1265
  onEditMessage,
1266
+ slots,
1212
1267
  }: NonVirtualizedThreadProps) {
1213
1268
  const { scrollRef, sentinelRef, contentRef, isFollowing, jumpToLatest } =
1214
1269
  useAutoScroll();
@@ -1252,6 +1307,7 @@ function NonVirtualizedThread({
1252
1307
  onRetrySend={onRetrySend}
1253
1308
  onRetryExecution={onRetryExecution}
1254
1309
  onEditMessage={onEditMessage}
1310
+ slots={slots}
1255
1311
  />
1256
1312
  </ThreadItemWrapper>
1257
1313
  ))}
@@ -1307,6 +1363,7 @@ export interface ThreadItemRendererProps {
1307
1363
  readonly onRetrySend?: () => void;
1308
1364
  readonly onRetryExecution?: (message: string) => void;
1309
1365
  readonly onEditMessage?: (text: string) => void;
1366
+ readonly slots?: MessageThreadSlots;
1310
1367
  }
1311
1368
 
1312
1369
  /**
@@ -1314,6 +1371,11 @@ export interface ThreadItemRendererProps {
1314
1371
  * the non-virtualized `items.map()` path and the virtualized
1315
1372
  * `Virtuoso.itemContent` callback.
1316
1373
  *
1374
+ * Slot resolution happens here, per case, as `slots?.X ?? X` — no merged
1375
+ * defaults object, no allocation, and the memoized row wrappers below
1376
+ * (`ApprovalCardRow`, `FileReviewRecordRow`) keep their DD-010 callback
1377
+ * stabilization around whichever component renders inside them.
1378
+ *
1317
1379
  * Does not receive a `key` prop — the caller is responsible for
1318
1380
  * keying (either via `items.map` or `computeItemKey`).
1319
1381
  *
@@ -1334,16 +1396,22 @@ export function ThreadItemRenderer({
1334
1396
  onRetrySend,
1335
1397
  onRetryExecution,
1336
1398
  onEditMessage,
1399
+ slots,
1337
1400
  }: ThreadItemRendererProps) {
1338
1401
  switch (item.kind) {
1339
- case "message":
1402
+ case "message": {
1403
+ const Entry = slots?.MessageEntry ?? MessageEntry;
1340
1404
  if (item.isFailed) {
1341
1405
  return (
1342
- <FailedUserMessage message={item.message} onRetry={onRetrySend} />
1406
+ <FailedUserMessage
1407
+ message={item.message}
1408
+ onRetry={onRetrySend}
1409
+ MessageEntryComponent={Entry}
1410
+ />
1343
1411
  );
1344
1412
  }
1345
1413
  return (
1346
- <MessageEntry
1414
+ <Entry
1347
1415
  message={item.message}
1348
1416
  className={item.isPending ? "opacity-70" : undefined}
1349
1417
  isPlanDocument={item.isPlanDocument}
@@ -1355,6 +1423,7 @@ export function ThreadItemRenderer({
1355
1423
  }
1356
1424
  />
1357
1425
  );
1426
+ }
1358
1427
  case "tool-group":
1359
1428
  return (
1360
1429
  <ToolCallGroup
@@ -1392,30 +1461,37 @@ export function ThreadItemRenderer({
1392
1461
  onApprovalSubmit={onApprovalSubmit!}
1393
1462
  isSubmitting={submittingApprovalIds?.has(item.pendingApproval.toolCallId) ?? false}
1394
1463
  error={approvalErrors?.get(item.pendingApproval.toolCallId) ?? null}
1464
+ ApprovalCardComponent={slots?.ApprovalCard ?? ApprovalCard}
1395
1465
  />
1396
1466
  );
1397
1467
  case "file-review-record":
1398
1468
  return <FileReviewRecordRow fileChangeSet={item.fileChangeSet} />;
1399
- case "setup-progress":
1469
+ case "setup-progress": {
1470
+ const Setup = slots?.SetupProgress ?? SetupProgress;
1400
1471
  return (
1401
- <SetupProgress
1472
+ <Setup
1402
1473
  workspaceEntries={item.workspaceEntries}
1403
1474
  serverPhase={item.serverPhase}
1404
1475
  isAwaitingResponse={item.isAwaitingResponse}
1405
1476
  />
1406
1477
  );
1478
+ }
1407
1479
  case "context-compacted":
1408
1480
  return <SummarizationCard event={item.event} />;
1409
- case "todos":
1410
- return <TodoCard todos={item.todos} className="mx-4" />;
1411
- case "plan-completion":
1481
+ case "todos": {
1482
+ const Todos = slots?.TodoCard ?? TodoCard;
1483
+ return <Todos todos={item.todos} className="mx-4" TodoRow={slots?.TodoRow} />;
1484
+ }
1485
+ case "plan-completion": {
1412
1486
  // When the plan was published as an artifact, show the richer reviewable
1413
1487
  // card (preview / copy / download / implement). Otherwise fall back to the
1414
1488
  // bare Implement CTA (older executions, or a plan that failed to publish).
1415
1489
  // Only the latest plan receives the build action — a superseded plan's
1416
1490
  // card is review-only, so a stale plan can never be implemented from it.
1491
+ const Artifact = slots?.PlanArtifactCard ?? PlanArtifactCard;
1492
+ const Completion = slots?.PlanCompletionCard ?? PlanCompletionCard;
1417
1493
  return item.planArtifact && item.executionId ? (
1418
- <PlanArtifactCard
1494
+ <Artifact
1419
1495
  executionId={item.executionId}
1420
1496
  artifact={item.planArtifact}
1421
1497
  title={item.planTitle}
@@ -1432,17 +1508,19 @@ export function ThreadItemRenderer({
1432
1508
  buildPending={planBuildPending}
1433
1509
  />
1434
1510
  ) : (
1435
- <PlanCompletionCard
1511
+ <Completion
1436
1512
  onImplement={item.isLatestPlan ? onBuildFromPlan : undefined}
1437
1513
  disabled={planActionsDisabled}
1438
1514
  />
1439
1515
  );
1440
- case "plan-writing":
1516
+ }
1517
+ case "plan-writing": {
1441
1518
  // The live stand-in for a plan the active turn is writing. Emitted only
1442
1519
  // when onOpenPlan is wired (see buildThreadItems' collapseStreamingPlan
1443
1520
  // gate), so the action is always available in practice.
1521
+ const Streaming = slots?.PlanStreamingCard ?? PlanStreamingCard;
1444
1522
  return (
1445
- <PlanStreamingCard
1523
+ <Streaming
1446
1524
  title={item.planTitle}
1447
1525
  sizeBytes={item.planSize}
1448
1526
  onOpenPlan={
@@ -1450,6 +1528,7 @@ export function ThreadItemRenderer({
1450
1528
  }
1451
1529
  />
1452
1530
  );
1531
+ }
1453
1532
  }
1454
1533
  }
1455
1534
 
@@ -1462,17 +1541,22 @@ export function ThreadItemRenderer({
1462
1541
  * (so the typed text is never lost) with an inline, actionable error beneath
1463
1542
  * it. The error copy is intentionally short — the full reason is surfaced by
1464
1543
  * the consumer's send-error banner; this is the in-thread "Retry" affordance.
1544
+ *
1545
+ * The bubble renders through the caller-resolved MessageEntry slot so an
1546
+ * overridden bubble style also applies to failed sends.
1465
1547
  */
1466
1548
  function FailedUserMessage({
1467
1549
  message,
1468
1550
  onRetry,
1551
+ MessageEntryComponent,
1469
1552
  }: {
1470
1553
  message: AgentMessage;
1471
1554
  onRetry?: () => void;
1555
+ MessageEntryComponent: ComponentType<MessageEntryProps>;
1472
1556
  }) {
1473
1557
  return (
1474
1558
  <div className="flex flex-col gap-1">
1475
- <MessageEntry message={message} />
1559
+ <MessageEntryComponent message={message} />
1476
1560
  <div
1477
1561
  role="alert"
1478
1562
  className="mx-4 flex items-center gap-2 text-xs text-destructive"
@@ -1587,6 +1671,9 @@ interface ApprovalCardRowProps {
1587
1671
  // This gate's last failed decision, or null — a stable ref from the
1588
1672
  // approvalErrors map, so the row re-renders only when its error appears/clears.
1589
1673
  readonly error?: Error | null;
1674
+ // The (possibly slotted) card component. The row's callback stabilization
1675
+ // wraps whichever card renders, so a slot override keeps the memo behavior.
1676
+ readonly ApprovalCardComponent: ComponentType<ApprovalCardProps>;
1590
1677
  }
1591
1678
 
1592
1679
  const ApprovalCardRow = memo(function ApprovalCardRow({
@@ -1594,6 +1681,7 @@ const ApprovalCardRow = memo(function ApprovalCardRow({
1594
1681
  onApprovalSubmit,
1595
1682
  isSubmitting,
1596
1683
  error = null,
1684
+ ApprovalCardComponent,
1597
1685
  }: ApprovalCardRowProps) {
1598
1686
  const handleSubmit = useCallback(
1599
1687
  (action: ApprovalAction, comment?: string) => {
@@ -1603,7 +1691,7 @@ const ApprovalCardRow = memo(function ApprovalCardRow({
1603
1691
  );
1604
1692
 
1605
1693
  return (
1606
- <ApprovalCard
1694
+ <ApprovalCardComponent
1607
1695
  pendingApproval={pendingApproval}
1608
1696
  onSubmit={handleSubmit}
1609
1697
  isSubmitting={isSubmitting}
@@ -1,12 +1,17 @@
1
1
  "use client";
2
2
 
3
- import { memo, useMemo } from "react";
3
+ import { memo, useMemo, type ComponentType } from "react";
4
4
  import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
5
5
  import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
6
  import { cn } from "@stigmer/theme";
7
7
  import { useRenderTracer } from "../internal/dev/index.js";
8
8
  import { useAutoDisclosure } from "../internal/useAutoDisclosure.js";
9
- import { TodoList, findActiveTodo, todoCompletionSummary } from "./TodoList.js";
9
+ import {
10
+ TodoList,
11
+ findActiveTodo,
12
+ todoCompletionSummary,
13
+ type TodoRowProps,
14
+ } from "./TodoList.js";
10
15
 
11
16
  /** Props for {@link TodoCard}. */
12
17
  export interface TodoCardProps {
@@ -18,6 +23,12 @@ export interface TodoCardProps {
18
23
  readonly todos: { readonly [key: string]: TodoItem };
19
24
  /** Additional CSS class names for the root container. */
20
25
  readonly className?: string;
26
+ /**
27
+ * Replacement row component, forwarded to the inner {@link TodoList}.
28
+ * Lets a host restyle individual rows while keeping the card's
29
+ * collapse/progress chrome. Defaults to the built-in {@link TodoRow}.
30
+ */
31
+ readonly TodoRow?: ComponentType<TodoRowProps>;
21
32
  }
22
33
 
23
34
  /**
@@ -52,6 +63,7 @@ export interface TodoCardProps {
52
63
  export const TodoCard = memo(function TodoCard({
53
64
  todos,
54
65
  className,
66
+ TodoRow,
55
67
  }: TodoCardProps) {
56
68
  useRenderTracer("TodoCard", { todos });
57
69
 
@@ -121,7 +133,7 @@ export const TodoCard = memo(function TodoCard({
121
133
 
122
134
  {expanded && (
123
135
  <div className="border-t border-border-muted px-2.5 py-2">
124
- <TodoList todos={todos} />
136
+ <TodoList todos={todos} TodoRow={TodoRow} />
125
137
  </div>
126
138
  )}
127
139
  </div>
@@ -139,7 +151,11 @@ export function todoCardPropsEqual(
139
151
  prev: Readonly<TodoCardProps>,
140
152
  next: Readonly<TodoCardProps>,
141
153
  ): boolean {
142
- return prev.todos === next.todos && prev.className === next.className;
154
+ return (
155
+ prev.todos === next.todos &&
156
+ prev.className === next.className &&
157
+ prev.TodoRow === next.TodoRow
158
+ );
143
159
  }
144
160
 
145
161
  // ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useMemo } from "react";
3
+ import { useMemo, type ComponentType } from "react";
4
4
  import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
5
5
  import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
6
  import { cn } from "@stigmer/theme";
@@ -14,6 +14,12 @@ export interface TodoListProps {
14
14
  readonly todos: { readonly [key: string]: TodoItem };
15
15
  /** Additional CSS class names for the root container. */
16
16
  readonly className?: string;
17
+ /**
18
+ * Replacement row component. Receives {@link TodoRowProps} for each
19
+ * item; defaults to the built-in {@link TodoRow}. Renders inside a
20
+ * `<ul role="list">`, so the component must produce an `<li>`.
21
+ */
22
+ readonly TodoRow?: ComponentType<TodoRowProps>;
17
23
  }
18
24
 
19
25
  const STATUS_SORT_ORDER: ReadonlyMap<TodoStatus, number> = new Map([
@@ -47,7 +53,11 @@ function todoSortKey(item: TodoItem): number {
47
53
  * <TodoList todos={todos} />
48
54
  * ```
49
55
  */
50
- export function TodoList({ todos, className }: TodoListProps) {
56
+ export function TodoList({
57
+ todos,
58
+ className,
59
+ TodoRow: RowComponent = TodoRow,
60
+ }: TodoListProps) {
51
61
  const sortedTodos = useMemo(() => {
52
62
  const items = Object.values(todos);
53
63
  if (items.length === 0) return [];
@@ -63,7 +73,7 @@ export function TodoList({ todos, className }: TodoListProps) {
63
73
  aria-label="Tasks"
64
74
  >
65
75
  {sortedTodos.map((item) => (
66
- <TodoRow key={item.id} item={item} />
76
+ <RowComponent key={item.id} item={item} />
67
77
  ))}
68
78
  </ul>
69
79
  );
@@ -108,7 +118,19 @@ export function todoCompletionSummary(
108
118
  // TodoRow — single todo item with status icon
109
119
  // ---------------------------------------------------------------------------
110
120
 
111
- function TodoRow({ item }: { readonly item: TodoItem }) {
121
+ /** Props for {@link TodoRow}. */
122
+ export interface TodoRowProps {
123
+ /** The todo item to render. */
124
+ readonly item: TodoItem;
125
+ }
126
+
127
+ /**
128
+ * One to-do checklist row: status icon plus task description, rendered as
129
+ * an `<li>`. The default row for {@link TodoList} — replace it via the
130
+ * `TodoRow` prop (or `MessageThread`'s `slots.TodoRow`) to restyle rows or
131
+ * bring host iconography while keeping the built-in card chrome.
132
+ */
133
+ export function TodoRow({ item }: TodoRowProps) {
112
134
  const Icon = TODO_ICONS[item.status] ?? TodoPendingIcon;
113
135
  const colorClass = TODO_COLORS[item.status] ?? "text-muted-foreground";
114
136
  const cancelled = item.status === TodoStatus.TODO_CANCELLED;