@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.
- package/composer/SessionComposer.d.ts +31 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +12 -4
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +4 -4
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +15 -142
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/FileReviewContext.d.ts +15 -1
- package/execution/FileReviewContext.d.ts.map +1 -1
- package/execution/FileReviewContext.js +20 -1
- package/execution/FileReviewContext.js.map +1 -1
- package/execution/MessageThread.d.ts +65 -2
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +42 -23
- package/execution/MessageThread.js.map +1 -1
- package/execution/TodoCard.d.ts +8 -0
- package/execution/TodoCard.d.ts.map +1 -1
- package/execution/TodoCard.js +6 -4
- package/execution/TodoCard.js.map +1 -1
- package/execution/TodoList.d.ts +20 -1
- package/execution/TodoList.d.ts.map +1 -1
- package/execution/TodoList.js +9 -6
- package/execution/TodoList.js.map +1 -1
- package/execution/ToolCallDetail.d.ts +3 -2
- package/execution/ToolCallDetail.d.ts.map +1 -1
- package/execution/ToolCallDetail.js +49 -16
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/file-review-status.d.ts +15 -0
- package/execution/file-review-status.d.ts.map +1 -1
- package/execution/file-review-status.js +22 -0
- package/execution/file-review-status.js.map +1 -1
- package/execution/index.d.ts +13 -7
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +7 -4
- package/execution/index.js.map +1 -1
- package/execution/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +11 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/github/useGitHubFileReader.d.ts +4 -2
- package/github/useGitHubFileReader.d.ts.map +1 -1
- package/github/useGitHubFileReader.js +16 -3
- package/github/useGitHubFileReader.js.map +1 -1
- package/github/useGitHubTreeLister.d.ts +2 -1
- package/github/useGitHubTreeLister.d.ts.map +1 -1
- package/github/useGitHubTreeLister.js +7 -3
- package/github/useGitHubTreeLister.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/VirtualizedThread.d.ts +3 -2
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +3 -2
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/package.json +5 -4
- package/session/SessionViewer.d.ts +11 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +101 -21
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +3 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +5 -1
- package/session/index.js.map +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +17 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +32 -4
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/session/useWorkspaceReadRefs.d.ts +45 -0
- package/session/useWorkspaceReadRefs.d.ts.map +1 -0
- package/session/useWorkspaceReadRefs.js +104 -0
- package/session/useWorkspaceReadRefs.js.map +1 -0
- package/src/composer/SessionComposer.tsx +69 -1
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +39 -405
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileReviewContext.ts +27 -1
- package/src/execution/MessageThread.tsx +110 -22
- package/src/execution/TodoCard.tsx +20 -4
- package/src/execution/TodoList.tsx +26 -4
- package/src/execution/ToolCallDetail.tsx +69 -16
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/ToolCallDetail.test.tsx +188 -2
- package/src/execution/__tests__/file-review-status.test.ts +47 -0
- package/src/execution/__tests__/message-entry.test.tsx +50 -2
- package/src/execution/__tests__/message-thread-slots.test.tsx +463 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/file-review-status.ts +24 -0
- package/src/execution/index.ts +27 -5
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +12 -0
- package/src/github/__tests__/useGitHubFileReader.test.ts +35 -2
- package/src/github/__tests__/useGitHubTreeLister.test.ts +14 -0
- package/src/github/useGitHubFileReader.ts +19 -6
- package/src/github/useGitHubTreeLister.ts +7 -3
- package/src/index.ts +15 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/VirtualizedThread.tsx +5 -1
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/session/SessionViewer.tsx +204 -35
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +172 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/__tests__/useWorkspaceReadRefs.test.ts +245 -0
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +7 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +48 -6
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/session/useWorkspaceReadRefs.ts +118 -0
- package/src/workspace/FileViewer.tsx +144 -38
- package/src/workspace/WorkspaceFileReader.ts +21 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +138 -3
- package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +20 -0
- package/src/workspace/__tests__/workspaceListingCache.test.ts +114 -0
- package/src/workspace/index.ts +4 -1
- package/src/workspace/useWorkspaceEntries.ts +11 -0
- package/src/workspace/useWorkspaceFileContent.ts +4 -1
- package/src/workspace/useWorkspaceFileSearch.ts +8 -3
- package/src/workspace/useWorkspaceFiles.ts +3 -2
- package/src/workspace/workspaceListingCache.ts +21 -7
- package/styles.css +1 -1
- package/workspace/FileViewer.d.ts +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +55 -5
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceFileReader.d.ts +17 -0
- package/workspace/WorkspaceFileReader.d.ts.map +1 -1
- package/workspace/WorkspaceFileReader.js +18 -0
- package/workspace/WorkspaceFileReader.js.map +1 -1
- package/workspace/index.d.ts +1 -1
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +1 -1
- package/workspace/index.js.map +1 -1
- package/workspace/useWorkspaceEntries.d.ts +11 -0
- package/workspace/useWorkspaceEntries.d.ts.map +1 -1
- package/workspace/useWorkspaceEntries.js.map +1 -1
- package/workspace/useWorkspaceFileContent.d.ts.map +1 -1
- package/workspace/useWorkspaceFileContent.js +4 -1
- package/workspace/useWorkspaceFileContent.js.map +1 -1
- package/workspace/useWorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/useWorkspaceFileSearch.js +8 -3
- package/workspace/useWorkspaceFileSearch.js.map +1 -1
- package/workspace/useWorkspaceFiles.d.ts +2 -1
- package/workspace/useWorkspaceFiles.d.ts.map +1 -1
- package/workspace/useWorkspaceFiles.js +3 -2
- package/workspace/useWorkspaceFiles.js.map +1 -1
- package/workspace/workspaceListingCache.d.ts +1 -1
- package/workspace/workspaceListingCache.d.ts.map +1 -1
- package/workspace/workspaceListingCache.js +20 -7
- package/workspace/workspaceListingCache.js.map +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- package/src/execution/ArtifactCard.tsx +0 -320
|
@@ -1,15 +1,47 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
|
|
2
2
|
import { render, cleanup } from "@testing-library/react";
|
|
3
3
|
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
4
4
|
import {
|
|
5
5
|
ToolCallSchema,
|
|
6
6
|
type ToolCall,
|
|
7
7
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
8
|
+
import {
|
|
9
|
+
CapturedFileChangeSchema,
|
|
10
|
+
FileChangeSetSchema,
|
|
11
|
+
type FileChangeSet,
|
|
12
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
8
13
|
import {
|
|
9
14
|
ApprovalPolicySource,
|
|
15
|
+
FileChangeKind,
|
|
16
|
+
FileReviewBlockReason,
|
|
17
|
+
ToolCallStatus,
|
|
10
18
|
ToolKind,
|
|
11
19
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
12
|
-
import {
|
|
20
|
+
import type { UseFileChangeContentReturn } from "../useFileChangeContent";
|
|
21
|
+
|
|
22
|
+
// Mock the diff-body content resolver (the seam FileChangesView.test.tsx also
|
|
23
|
+
// mocks): FileChangeDiff calls it unconditionally, and its real implementation
|
|
24
|
+
// needs a Stigmer client for offloaded bodies — irrelevant to these tests.
|
|
25
|
+
let mockContent: UseFileChangeContentReturn;
|
|
26
|
+
|
|
27
|
+
vi.mock("../useFileChangeContent", () => ({
|
|
28
|
+
useFileChangeContent: () => mockContent,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const { ToolCallDetail } = await import("../ToolCallDetail");
|
|
32
|
+
const { FileReviewContext } = await import("../FileReviewContext");
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
mockContent = {
|
|
36
|
+
beforeText: "alpha\n",
|
|
37
|
+
afterText: "captured-after\n",
|
|
38
|
+
isBinary: false,
|
|
39
|
+
isLoading: false,
|
|
40
|
+
error: null,
|
|
41
|
+
isTruncated: false,
|
|
42
|
+
downloadUrl: null,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
13
45
|
|
|
14
46
|
afterEach(cleanup);
|
|
15
47
|
|
|
@@ -21,6 +53,9 @@ function makeToolCall(opts: {
|
|
|
21
53
|
approvalPolicySource?: ApprovalPolicySource;
|
|
22
54
|
startedAt?: string;
|
|
23
55
|
completedAt?: string;
|
|
56
|
+
status?: ToolCallStatus;
|
|
57
|
+
error?: string;
|
|
58
|
+
fileChangeSetId?: string;
|
|
24
59
|
}): ToolCall {
|
|
25
60
|
return create(ToolCallSchema, {
|
|
26
61
|
id: opts.name,
|
|
@@ -32,6 +67,9 @@ function makeToolCall(opts: {
|
|
|
32
67
|
opts.approvalPolicySource ?? ApprovalPolicySource.UNSPECIFIED,
|
|
33
68
|
startedAt: opts.startedAt ?? "",
|
|
34
69
|
completedAt: opts.completedAt ?? "",
|
|
70
|
+
status: opts.status ?? ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
71
|
+
error: opts.error ?? "",
|
|
72
|
+
fileChangeSetId: opts.fileChangeSetId ?? "",
|
|
35
73
|
});
|
|
36
74
|
}
|
|
37
75
|
|
|
@@ -193,3 +231,151 @@ describe("ToolCallDetail — edit body has no duplicate stats", () => {
|
|
|
193
231
|
expect(container.textContent).not.toContain("+1");
|
|
194
232
|
});
|
|
195
233
|
});
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Write body — captured diff on a stamped row, single-bounded fallback
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
describe("ToolCallDetail — write body", () => {
|
|
240
|
+
const SET_ID = "aex-1:0";
|
|
241
|
+
// BoundedContent's collapsed pixel clamp (PREVIEW_MAX_HEIGHT). Its presence/
|
|
242
|
+
// absence is how these tests pin WHICH truncation mechanism bounds the body —
|
|
243
|
+
// jsdom has no layout, so the reveal control itself only appears for the
|
|
244
|
+
// line-count mechanism (CollapsibleCode), never for the pixel clamp.
|
|
245
|
+
const CLAMP = ".max-h-48";
|
|
246
|
+
|
|
247
|
+
/** A change set capturing one reviewable modify of `path`. */
|
|
248
|
+
function capturedSet(
|
|
249
|
+
path = "notes.md",
|
|
250
|
+
overrides?: { diffComplete?: boolean; blockedReason?: FileReviewBlockReason },
|
|
251
|
+
): FileChangeSet {
|
|
252
|
+
return create(FileChangeSetSchema, {
|
|
253
|
+
id: SET_ID,
|
|
254
|
+
changes: [
|
|
255
|
+
create(CapturedFileChangeSchema, {
|
|
256
|
+
id: `${SET_ID}:${path}`,
|
|
257
|
+
pathBefore: path,
|
|
258
|
+
pathAfter: path,
|
|
259
|
+
kind: FileChangeKind.MODIFY,
|
|
260
|
+
diffComplete: overrides?.diffComplete ?? true,
|
|
261
|
+
blockedReason:
|
|
262
|
+
overrides?.blockedReason ?? FileReviewBlockReason.UNSPECIFIED,
|
|
263
|
+
}),
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function writeCall(opts?: Partial<Parameters<typeof makeToolCall>[0]>): ToolCall {
|
|
269
|
+
return makeToolCall({
|
|
270
|
+
name: "write_file",
|
|
271
|
+
toolKind: ToolKind.FILE_WRITE,
|
|
272
|
+
args: { file_path: "notes.md", contents: "proposed body\n" },
|
|
273
|
+
result: "Successfully wrote to 'notes.md'",
|
|
274
|
+
...opts,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderWithSet(toolCall: ToolCall, set: FileChangeSet | null) {
|
|
279
|
+
const changeSetsById = new Map(set ? [[SET_ID, set]] : []);
|
|
280
|
+
return render(
|
|
281
|
+
<FileReviewContext.Provider value={{ changeSetsById }}>
|
|
282
|
+
<ToolCallDetail toolCall={toolCall} />
|
|
283
|
+
</FileReviewContext.Provider>,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
it("renders the captured net diff (not the Content block) on a stamped row", () => {
|
|
288
|
+
const { container } = renderWithSet(
|
|
289
|
+
writeCall({ fileChangeSetId: SET_ID }),
|
|
290
|
+
capturedSet(),
|
|
291
|
+
);
|
|
292
|
+
// The captured before/after renders as a real diff table...
|
|
293
|
+
expect(container.querySelector('[data-cursor-target="file-diff"]')).not.toBeNull();
|
|
294
|
+
expect(container.textContent).toContain("captured-after");
|
|
295
|
+
// ...bounded by the shared pixel clamp, with the +N -M stats the write
|
|
296
|
+
// row's header does not carry, and without the fallback's raw args body.
|
|
297
|
+
// (The diff table itself carries a hidden a11y column header named
|
|
298
|
+
// "Content", so the discriminator is the body text, not the label.)
|
|
299
|
+
expect(container.querySelector(CLAMP)).not.toBeNull();
|
|
300
|
+
expect(container.textContent).toContain("+1");
|
|
301
|
+
expect(container.textContent).not.toContain("proposed body");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("renders exactly one reveal control for the content fallback (double-expander regression)", () => {
|
|
305
|
+
const longBody = Array.from({ length: 55 }, (_, i) => `line ${i + 1}`).join("\n");
|
|
306
|
+
const { container, getAllByRole } = render(
|
|
307
|
+
<ToolCallDetail
|
|
308
|
+
toolCall={writeCall({ args: { file_path: "notes.md", contents: longBody } })}
|
|
309
|
+
/>,
|
|
310
|
+
);
|
|
311
|
+
// The file view self-truncates (line-count mechanism, one control)...
|
|
312
|
+
const reveals = getAllByRole("button").filter((b) =>
|
|
313
|
+
/^Show /.test(b.textContent ?? ""),
|
|
314
|
+
);
|
|
315
|
+
expect(reveals).toHaveLength(1);
|
|
316
|
+
expect(reveals[0].textContent).toContain("Show all 55 lines");
|
|
317
|
+
// ...and is never ALSO wrapped in BoundedContent's pixel clamp.
|
|
318
|
+
expect(container.querySelector(CLAMP)).toBeNull();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("falls back to the Content block for every unresolvable capture", () => {
|
|
322
|
+
const cases: Array<[string, ToolCall, FileChangeSet | null]> = [
|
|
323
|
+
["unstamped row", writeCall(), capturedSet()],
|
|
324
|
+
["set missing from context", writeCall({ fileChangeSetId: SET_ID }), null],
|
|
325
|
+
[
|
|
326
|
+
"path not in set",
|
|
327
|
+
writeCall({ fileChangeSetId: SET_ID }),
|
|
328
|
+
capturedSet("other.md"),
|
|
329
|
+
],
|
|
330
|
+
[
|
|
331
|
+
"non-reviewable (secret-withheld) change",
|
|
332
|
+
writeCall({ fileChangeSetId: SET_ID }),
|
|
333
|
+
capturedSet("notes.md", {
|
|
334
|
+
diffComplete: false,
|
|
335
|
+
blockedReason: FileReviewBlockReason.SECRET_WITHHELD,
|
|
336
|
+
}),
|
|
337
|
+
],
|
|
338
|
+
];
|
|
339
|
+
for (const [label, toolCall, set] of cases) {
|
|
340
|
+
const { container } = renderWithSet(toolCall, set);
|
|
341
|
+
expect(container.textContent, label).toContain("Content");
|
|
342
|
+
expect(container.textContent, label).toContain("proposed body");
|
|
343
|
+
expect(
|
|
344
|
+
container.querySelector('[data-cursor-target="file-diff"]'),
|
|
345
|
+
label,
|
|
346
|
+
).toBeNull();
|
|
347
|
+
cleanup();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("keeps a FAILED write's error view inside the pixel clamp (bounding-rule lock)", () => {
|
|
352
|
+
const { container } = render(
|
|
353
|
+
<ToolCallDetail
|
|
354
|
+
toolCall={writeCall({
|
|
355
|
+
status: ToolCallStatus.TOOL_CALL_FAILED,
|
|
356
|
+
error: "EACCES: permission denied, open 'notes.md'",
|
|
357
|
+
})}
|
|
358
|
+
/>,
|
|
359
|
+
);
|
|
360
|
+
expect(container.textContent).toContain("permission denied");
|
|
361
|
+
// The error <pre> has no internal truncation, so BoundedContent bounds it.
|
|
362
|
+
expect(container.querySelector(CLAMP)).not.toBeNull();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("leaves a stamped EDIT row on its per-call args diff, never the captured net diff", () => {
|
|
366
|
+
const edit = makeToolCall({
|
|
367
|
+
name: "edit_file",
|
|
368
|
+
toolKind: ToolKind.FILE_EDIT,
|
|
369
|
+
args: { file_path: "notes.md", old_string: "alpha", new_string: "edit-args-b" },
|
|
370
|
+
result: "Successfully replaced 1 occurrence in 'notes.md'",
|
|
371
|
+
fileChangeSetId: SET_ID,
|
|
372
|
+
});
|
|
373
|
+
const { container } = renderWithSet(edit, capturedSet());
|
|
374
|
+
// The per-call diff from the row's own args renders...
|
|
375
|
+
expect(container.textContent).toContain("edit-args-b");
|
|
376
|
+
// ...not the change set's captured net content...
|
|
377
|
+
expect(container.textContent).not.toContain("captured-after");
|
|
378
|
+
// ...still inside the shared pixel clamp (unchanged behavior).
|
|
379
|
+
expect(container.querySelector(CLAMP)).not.toBeNull();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
changeSetReviewability,
|
|
25
25
|
deriveEffectiveVerdicts,
|
|
26
26
|
fileReviewability,
|
|
27
|
+
fileReviewRowChange,
|
|
27
28
|
fileReviewRowState,
|
|
28
29
|
} from "../file-review-status";
|
|
29
30
|
|
|
@@ -291,6 +292,52 @@ describe("changeForRowPath", () => {
|
|
|
291
292
|
});
|
|
292
293
|
});
|
|
293
294
|
|
|
295
|
+
describe("fileReviewRowChange", () => {
|
|
296
|
+
it("returns null with no set or path", () => {
|
|
297
|
+
const set = reviewSet({
|
|
298
|
+
status: FileChangeSetStatus.AWAITING_REVIEW,
|
|
299
|
+
changes: [fileChange("f1", "a.ts")],
|
|
300
|
+
});
|
|
301
|
+
expect(fileReviewRowChange(undefined, "a.ts")).toBeNull();
|
|
302
|
+
expect(fileReviewRowChange(set, null)).toBeNull();
|
|
303
|
+
expect(fileReviewRowChange(set, "")).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("resolves a reviewable change by row path (same matcher as the badge)", () => {
|
|
307
|
+
const set = reviewSet({
|
|
308
|
+
status: FileChangeSetStatus.AWAITING_REVIEW,
|
|
309
|
+
changes: [fileChange("f1", "src/a.ts")],
|
|
310
|
+
});
|
|
311
|
+
expect(fileReviewRowChange(set, "src/a.ts")?.id).toBe("f1");
|
|
312
|
+
expect(fileReviewRowChange(set, "/home/user/ws/src/a.ts")?.id).toBe("f1");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("returns null for a path absent from the set", () => {
|
|
316
|
+
const set = reviewSet({
|
|
317
|
+
status: FileChangeSetStatus.AWAITING_REVIEW,
|
|
318
|
+
changes: [fileChange("f1", "a.ts")],
|
|
319
|
+
});
|
|
320
|
+
expect(fileReviewRowChange(set, "gone.ts")).toBeNull();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("returns null for a non-reviewable change (honest degradation)", () => {
|
|
324
|
+
const set = reviewSet({
|
|
325
|
+
status: FileChangeSetStatus.AWAITING_REVIEW,
|
|
326
|
+
changes: [
|
|
327
|
+
create(CapturedFileChangeSchema, {
|
|
328
|
+
id: "f1",
|
|
329
|
+
pathBefore: "secret.env",
|
|
330
|
+
pathAfter: "secret.env",
|
|
331
|
+
kind: FileChangeKind.MODIFY,
|
|
332
|
+
diffComplete: false,
|
|
333
|
+
blockedReason: FileReviewBlockReason.SECRET_WITHHELD,
|
|
334
|
+
}),
|
|
335
|
+
],
|
|
336
|
+
});
|
|
337
|
+
expect(fileReviewRowChange(set, "secret.env")).toBeNull();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
294
341
|
describe("fileReviewRowState", () => {
|
|
295
342
|
it("returns null with no set (not yet projected / unknown id)", () => {
|
|
296
343
|
expect(fileReviewRowState(undefined, "a.ts")).toBeNull();
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
-
import { render, cleanup } from "@testing-library/react";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
|
+
import { render, cleanup, waitFor } from "@testing-library/react";
|
|
3
3
|
import { create } from "@bufbuild/protobuf";
|
|
4
4
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
5
|
import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
6
|
import { MessageEntry } from "../MessageEntry";
|
|
7
|
+
import type { MermaidModule } from "../../internal/mermaid-loader";
|
|
8
|
+
|
|
9
|
+
// Stub only the library loader: the real MermaidDiagram + markdown overrides
|
|
10
|
+
// run end-to-end through Streamdown, so these tests cover the full chat path
|
|
11
|
+
// from fenced source to rendered diagram.
|
|
12
|
+
vi.mock("../../internal/mermaid-loader", () => ({
|
|
13
|
+
loadMermaid: vi.fn().mockResolvedValue({
|
|
14
|
+
initialize: vi.fn(),
|
|
15
|
+
render: vi
|
|
16
|
+
.fn()
|
|
17
|
+
.mockResolvedValue({ svg: "<svg data-testid='mermaid-svg'></svg>" }),
|
|
18
|
+
} as unknown as MermaidModule),
|
|
19
|
+
}));
|
|
7
20
|
|
|
8
21
|
afterEach(cleanup);
|
|
9
22
|
|
|
@@ -169,6 +182,41 @@ describe("MessageEntry — AI messages (Streamdown)", () => {
|
|
|
169
182
|
article!.querySelectorAll('span[class*="hljs-"]').length,
|
|
170
183
|
).toBeGreaterThan(0);
|
|
171
184
|
});
|
|
185
|
+
|
|
186
|
+
it("renders a completed ```mermaid fence as a diagram, not a code block", async () => {
|
|
187
|
+
const msg = makeMessage(
|
|
188
|
+
MessageType.MESSAGE_AI,
|
|
189
|
+
"Here is the flow:\n\n```mermaid\nflowchart LR\n A --> B\n```",
|
|
190
|
+
);
|
|
191
|
+
const { container } = render(<MessageEntry message={msg} />);
|
|
192
|
+
|
|
193
|
+
const article = queryArticle(container, "AI response");
|
|
194
|
+
await waitFor(() =>
|
|
195
|
+
expect(
|
|
196
|
+
article!.querySelector('[role="img"][aria-label="Mermaid diagram"]'),
|
|
197
|
+
).not.toBeNull(),
|
|
198
|
+
);
|
|
199
|
+
expect(article!.querySelector("[data-testid='mermaid-svg']")).not.toBeNull();
|
|
200
|
+
// The fenced source is no longer shown as a code block.
|
|
201
|
+
expect(article!.querySelector("pre")).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("keeps an unclosed ```mermaid fence as a code block while streaming", () => {
|
|
205
|
+
const msg = makeMessage(
|
|
206
|
+
MessageType.MESSAGE_AI,
|
|
207
|
+
"```mermaid\nflowchart LR\n A --> B",
|
|
208
|
+
{ isStreaming: true },
|
|
209
|
+
);
|
|
210
|
+
const { container } = render(<MessageEntry message={msg} />);
|
|
211
|
+
|
|
212
|
+
const article = queryArticle(container, "AI response");
|
|
213
|
+
// Mid-stream the source stays a plain code block — no diagram, and no
|
|
214
|
+
// half-parsed error flashing.
|
|
215
|
+
expect(
|
|
216
|
+
article!.querySelector('[role="img"][aria-label="Mermaid diagram"]'),
|
|
217
|
+
).toBeNull();
|
|
218
|
+
expect(article!.textContent).toContain("flowchart LR");
|
|
219
|
+
});
|
|
172
220
|
});
|
|
173
221
|
|
|
174
222
|
// ---------------------------------------------------------------------------
|