@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
|
@@ -17,14 +17,14 @@ import {
|
|
|
17
17
|
WorkspaceSurface,
|
|
18
18
|
type SurfaceVirtualDocument,
|
|
19
19
|
} from "../workspace/WorkspaceSurface.js";
|
|
20
|
-
import type { InteractionModeOption, SessionComposerHandle } from "../composer/index.js";
|
|
20
|
+
import type { InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
|
|
21
21
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
22
22
|
import { ResizableSplit } from "../internal/ResizableSplit.js";
|
|
23
23
|
import { SelectionStore } from "../internal/store/selection-store.js";
|
|
24
24
|
import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.js";
|
|
25
25
|
import { ThreadSelectionContext } from "../execution/ThreadSelectionContext.js";
|
|
26
26
|
import { useSelectedThreadItem } from "../execution/useThreadSelection.js";
|
|
27
|
-
import { MessageThread } from "../execution/MessageThread.js";
|
|
27
|
+
import { MessageThread, type MessageThreadSlots } from "../execution/MessageThread.js";
|
|
28
28
|
import { FileChangeProgressBar } from "../execution/FileChangeProgressBar.js";
|
|
29
29
|
import { FileReviewDock } from "../execution/FileReviewDock.js";
|
|
30
30
|
import {
|
|
@@ -47,11 +47,18 @@ import { usePlanDraft, planDraftKey, type PlanDraftController } from "./usePlanD
|
|
|
47
47
|
import { PlanEditor } from "./PlanEditor.js";
|
|
48
48
|
import { PlanStreamingDocument } from "./PlanStreamingDocument.js";
|
|
49
49
|
import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
|
|
50
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
|
|
51
|
+
import { ArtifactDocument } from "../execution/ArtifactDocument.js";
|
|
50
52
|
import { useSessionRailViews } from "./useSessionRailViews.js";
|
|
51
53
|
import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
|
|
52
54
|
import { SessionPanelChip } from "./SessionPanelChip.js";
|
|
53
55
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
54
|
-
import {
|
|
56
|
+
import { useWorkspaceReadRefs } from "./useWorkspaceReadRefs.js";
|
|
57
|
+
import {
|
|
58
|
+
useSessionArtifacts,
|
|
59
|
+
artifactKey,
|
|
60
|
+
type SessionArtifactEntry,
|
|
61
|
+
} from "./useSessionArtifacts.js";
|
|
55
62
|
import type { SetupTabProps } from "./facets/SetupTab.js";
|
|
56
63
|
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
57
64
|
import type { RuntimeEnvProvider } from "./runtime-env.js";
|
|
@@ -222,6 +229,15 @@ export interface SessionViewerProps {
|
|
|
222
229
|
readonly accessSlot?: ReactNode;
|
|
223
230
|
/** Called after a resource is applied from the Artifacts tab. */
|
|
224
231
|
readonly onApplied?: (result: ApplyResourceResult) => void;
|
|
232
|
+
/**
|
|
233
|
+
* Component overrides for the conversation thread's chrome (message
|
|
234
|
+
* bubbles, approval card, to-dos card, plan cards, thinking indicator) —
|
|
235
|
+
* forwarded to the inner {@link MessageThread}. See
|
|
236
|
+
* {@link MessageThreadSlots}. Define the object and its components at
|
|
237
|
+
* module level (or memoize) so thread rows keep their streaming
|
|
238
|
+
* re-render guarantees.
|
|
239
|
+
*/
|
|
240
|
+
readonly threadSlots?: MessageThreadSlots;
|
|
225
241
|
/** Additional CSS classes for the root container. */
|
|
226
242
|
readonly className?: string;
|
|
227
243
|
}
|
|
@@ -283,6 +299,7 @@ export function SessionViewer({
|
|
|
283
299
|
headerActions,
|
|
284
300
|
accessSlot,
|
|
285
301
|
onApplied,
|
|
302
|
+
threadSlots,
|
|
286
303
|
className,
|
|
287
304
|
}: SessionViewerProps) {
|
|
288
305
|
const flow = useSessionPageFlow({ sessionId, org, getRuntimeEnv });
|
|
@@ -455,6 +472,28 @@ export function SessionViewer({
|
|
|
455
472
|
[sessionPlan, panel.openPlanDocument],
|
|
456
473
|
);
|
|
457
474
|
|
|
475
|
+
// "Open artifact" (an Artifacts-facet row) → an editor-pane document tab.
|
|
476
|
+
// Writing the editors store re-renders only the panel subtree (which
|
|
477
|
+
// subscribes), never this streaming column. Plan artifacts are routed to the
|
|
478
|
+
// plan tab upstream (in ArtifactsTab), so they never reach here.
|
|
479
|
+
const handleOpenArtifact = useCallback(
|
|
480
|
+
(entry: SessionArtifactEntry) => {
|
|
481
|
+
panel.openArtifact(entry.artifact);
|
|
482
|
+
},
|
|
483
|
+
[panel.openArtifact],
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// Double-click an artifact row → pin its tab (the leading single click already
|
|
487
|
+
// opened the preview via `handleOpenArtifact`). Mirrors the file tree's
|
|
488
|
+
// preview/pin split; plan artifacts are routed to the plan tab upstream (in
|
|
489
|
+
// ArtifactsTab) and never reach here.
|
|
490
|
+
const handleActivateArtifact = useCallback(
|
|
491
|
+
(entry: SessionArtifactEntry) => {
|
|
492
|
+
panel.pinArtifact(entry.artifact);
|
|
493
|
+
},
|
|
494
|
+
[panel.pinArtifact],
|
|
495
|
+
);
|
|
496
|
+
|
|
458
497
|
// Open a transcript tool-call file path in the panel's read-only editor.
|
|
459
498
|
// Resolves the (possibly absolute / subdir-prefixed) path to a
|
|
460
499
|
// repo/root-relative selection the editor can fetch; on a definite hit it
|
|
@@ -544,6 +583,7 @@ export function SessionViewer({
|
|
|
544
583
|
onDismissPlanAttachFailed={() => setPlanAttachFailed(false)}
|
|
545
584
|
onFilePathClick={handleTranscriptFilePathClick}
|
|
546
585
|
isEndUser={isEndUser}
|
|
586
|
+
threadSlots={threadSlots}
|
|
547
587
|
/>
|
|
548
588
|
}
|
|
549
589
|
secondary={
|
|
@@ -561,6 +601,8 @@ export function SessionViewer({
|
|
|
561
601
|
planDraft={planDraft}
|
|
562
602
|
openPlanExecutionId={openPlanExecutionId}
|
|
563
603
|
onOpenPlan={handleOpenPlan}
|
|
604
|
+
onOpenArtifact={handleOpenArtifact}
|
|
605
|
+
onActivateArtifact={handleActivateArtifact}
|
|
564
606
|
enableLocal={enableLocal}
|
|
565
607
|
onBrowseLocalFolder={onBrowseLocalFolder}
|
|
566
608
|
workspaceFileLister={workspaceFileLister}
|
|
@@ -607,6 +649,7 @@ interface ConversationColumnProps {
|
|
|
607
649
|
*/
|
|
608
650
|
readonly onFilePathClick: (path: string) => boolean;
|
|
609
651
|
readonly isEndUser: boolean;
|
|
652
|
+
readonly threadSlots?: MessageThreadSlots;
|
|
610
653
|
}
|
|
611
654
|
|
|
612
655
|
const ConversationColumn = memo(function ConversationColumn({
|
|
@@ -628,6 +671,7 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
628
671
|
onDismissPlanAttachFailed,
|
|
629
672
|
onFilePathClick,
|
|
630
673
|
isEndUser,
|
|
674
|
+
threadSlots,
|
|
631
675
|
}: ConversationColumnProps) {
|
|
632
676
|
const { conv } = flow;
|
|
633
677
|
// Approval failures are NOT folded into this banner: they now surface in-card,
|
|
@@ -652,18 +696,55 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
652
696
|
void conv.stop();
|
|
653
697
|
}, [conv.stop]);
|
|
654
698
|
|
|
655
|
-
// Edit-and-resubmit: stop the in-flight turn
|
|
656
|
-
// the original text
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
699
|
+
// Edit-and-resubmit: stop the in-flight turn, pre-fill the composer with
|
|
700
|
+
// the original text, and remember which execution is being edited. The
|
|
701
|
+
// append-only execution log is never rewritten — submitting while editing
|
|
702
|
+
// creates a NEW execution that carries `supersedesExecutionId`, and the
|
|
703
|
+
// conversation read model hides the superseded turn so the edited message
|
|
704
|
+
// replaces the original in place (Cursor-style, stigmer/stigmer#181).
|
|
705
|
+
//
|
|
706
|
+
// The editing state is explicit (banner + cancel in the composer) so the
|
|
707
|
+
// supersede link only attaches when the user actually resubmits the edit.
|
|
708
|
+
// Cancelling drops the link — a subsequent unrelated message appends
|
|
709
|
+
// normally without hiding any history.
|
|
710
|
+
const [editingExecutionId, setEditingExecutionId] = useState<string | null>(
|
|
711
|
+
null,
|
|
712
|
+
);
|
|
713
|
+
const activeExecutionId = conv.activeStreamExecution?.metadata?.id ?? null;
|
|
714
|
+
|
|
660
715
|
const handleEditMessage = useCallback(
|
|
661
716
|
(text: string) => {
|
|
662
717
|
void conv.stop();
|
|
718
|
+
setEditingExecutionId(activeExecutionId);
|
|
663
719
|
composerRef.current?.setMessage(text);
|
|
664
720
|
composerRef.current?.focus();
|
|
665
721
|
},
|
|
666
|
-
[conv.stop, composerRef],
|
|
722
|
+
[conv.stop, activeExecutionId, composerRef],
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const handleCancelEdit = useCallback(() => {
|
|
726
|
+
setEditingExecutionId(null);
|
|
727
|
+
composerRef.current?.setMessage("");
|
|
728
|
+
}, [composerRef]);
|
|
729
|
+
|
|
730
|
+
// Submit wrapper that attaches the supersede link while editing, then
|
|
731
|
+
// exits editing mode. Ordinary (non-editing) submits pass through as-is.
|
|
732
|
+
const handleComposerSubmit = useCallback(
|
|
733
|
+
(
|
|
734
|
+
message: string,
|
|
735
|
+
modelName?: string,
|
|
736
|
+
context?: SessionComposerSubmitContext,
|
|
737
|
+
) => {
|
|
738
|
+
void flow.handleSubmit(
|
|
739
|
+
message,
|
|
740
|
+
modelName,
|
|
741
|
+
editingExecutionId
|
|
742
|
+
? { ...context, supersedesExecutionId: editingExecutionId }
|
|
743
|
+
: context,
|
|
744
|
+
);
|
|
745
|
+
setEditingExecutionId(null);
|
|
746
|
+
},
|
|
747
|
+
[flow.handleSubmit, editingExecutionId],
|
|
667
748
|
);
|
|
668
749
|
|
|
669
750
|
// The composer-docked FileReviewDock sits OUTSIDE MessageThread, so the
|
|
@@ -703,6 +784,7 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
703
784
|
planActionsDisabled={!conv.canSendFollowUp || isBuildingFromPlan}
|
|
704
785
|
planBuildPending={isBuildingFromPlan}
|
|
705
786
|
contentColumn="center"
|
|
787
|
+
slots={threadSlots}
|
|
706
788
|
className="flex-1"
|
|
707
789
|
/>
|
|
708
790
|
<div className={CONVERSATION_COLUMN_CLASS}>
|
|
@@ -743,11 +825,13 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
743
825
|
</FilePathContext.Provider>
|
|
744
826
|
<SessionComposer
|
|
745
827
|
ref={composerRef}
|
|
746
|
-
onSubmit={
|
|
828
|
+
onSubmit={handleComposerSubmit}
|
|
747
829
|
isSubmitting={conv.isSending}
|
|
748
830
|
disabled={!conv.canSendFollowUp}
|
|
749
831
|
onStop={conv.isStoppable ? handleStop : undefined}
|
|
750
832
|
isStopping={conv.isStopping}
|
|
833
|
+
isEditing={editingExecutionId != null}
|
|
834
|
+
onCancelEdit={handleCancelEdit}
|
|
751
835
|
org={org}
|
|
752
836
|
harness={flow.harness}
|
|
753
837
|
defaultModelId={modelId}
|
|
@@ -815,6 +899,10 @@ interface SessionPanelRegionProps {
|
|
|
815
899
|
readonly openPlanExecutionId: string | null;
|
|
816
900
|
/** Opens a plan in the plan tab (routed to the Artifacts facet's plan.md). */
|
|
817
901
|
readonly onOpenPlan: (executionId: string) => void;
|
|
902
|
+
/** Opens an artifact as an editor-pane document (routed to the Artifacts facet). */
|
|
903
|
+
readonly onOpenArtifact: (entry: SessionArtifactEntry) => void;
|
|
904
|
+
/** Pins an artifact's document tab — the double-click half of the open/pin split. */
|
|
905
|
+
readonly onActivateArtifact: (entry: SessionArtifactEntry) => void;
|
|
818
906
|
readonly enableLocal: boolean;
|
|
819
907
|
readonly onBrowseLocalFolder?: () => Promise<string | null>;
|
|
820
908
|
readonly workspaceFileLister?: WorkspaceFileLister;
|
|
@@ -836,6 +924,8 @@ function SessionPanelRegion({
|
|
|
836
924
|
planDraft,
|
|
837
925
|
openPlanExecutionId,
|
|
838
926
|
onOpenPlan,
|
|
927
|
+
onOpenArtifact,
|
|
928
|
+
onActivateArtifact,
|
|
839
929
|
enableLocal,
|
|
840
930
|
onBrowseLocalFolder,
|
|
841
931
|
workspaceFileLister,
|
|
@@ -847,6 +937,9 @@ function SessionPanelRegion({
|
|
|
847
937
|
const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
|
|
848
938
|
panel.editorsStore,
|
|
849
939
|
);
|
|
940
|
+
// Session artifacts, deduped by identity — the source both the Artifacts
|
|
941
|
+
// rail-view badge and the open artifact document tabs resolve against.
|
|
942
|
+
const { artifacts: sessionArtifacts } = useSessionArtifacts(flow.allExecutions);
|
|
850
943
|
|
|
851
944
|
// Honor a pending jump-to-line reveal only while it targets the active editor
|
|
852
945
|
// (an EditorReveal is structurally a RevealTarget). Switching tabs changes
|
|
@@ -902,30 +995,28 @@ function SessionPanelRegion({
|
|
|
902
995
|
// tab and its turn then ended without publishing, with no earlier plan to
|
|
903
996
|
// fall back to.
|
|
904
997
|
const openPlanIsLatest = openPlan?.executionId === sessionPlan?.executionId;
|
|
905
|
-
const
|
|
906
|
-
return
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
},
|
|
928
|
-
];
|
|
998
|
+
const planVirtualDocument = useMemo<SurfaceVirtualDocument>(() => {
|
|
999
|
+
return {
|
|
1000
|
+
entryId: PLAN_DOCUMENT_ENTRY_ID,
|
|
1001
|
+
path: PLAN_DOCUMENT_PATH,
|
|
1002
|
+
content: streamingPlan ? (
|
|
1003
|
+
<PlanStreamingDocument
|
|
1004
|
+
key={`streaming-${streamingPlan.executionId}`}
|
|
1005
|
+
displayText={streamingPlan.displayText}
|
|
1006
|
+
/>
|
|
1007
|
+
) : openPlan ? (
|
|
1008
|
+
<PlanEditor
|
|
1009
|
+
key={planDraftKey(openPlan)}
|
|
1010
|
+
plan={openPlan}
|
|
1011
|
+
draft={openPlanIsLatest ? planDraft : undefined}
|
|
1012
|
+
onBuildFromPlan={openPlanIsLatest ? onImplementPlan : undefined}
|
|
1013
|
+
buildDisabled={implementPlanDisabled}
|
|
1014
|
+
readOnly={!openPlanIsLatest}
|
|
1015
|
+
/>
|
|
1016
|
+
) : (
|
|
1017
|
+
<PlanUnavailableNotice />
|
|
1018
|
+
),
|
|
1019
|
+
};
|
|
929
1020
|
}, [
|
|
930
1021
|
streamingPlan,
|
|
931
1022
|
openPlan,
|
|
@@ -935,6 +1026,50 @@ function SessionPanelRegion({
|
|
|
935
1026
|
implementPlanDisabled,
|
|
936
1027
|
]);
|
|
937
1028
|
|
|
1029
|
+
// The open artifact document tabs (the artifact virtual-document FAMILY).
|
|
1030
|
+
// Built in a SEPARATE memo from the plan doc so artifact-tab churn never
|
|
1031
|
+
// recreates the plan doc (and vice versa). Bounded to what is actually open:
|
|
1032
|
+
// one document per editor whose entry id is the artifact family's, resolved
|
|
1033
|
+
// back to its `SessionArtifactEntry` by the same `artifactKey` used to open
|
|
1034
|
+
// it (single source of truth for artifact identity). Only the ACTIVE doc's
|
|
1035
|
+
// `content` is mounted by the surface, so building these elements is cheap;
|
|
1036
|
+
// keying on `contentHash` remounts a tab whose artifact was overwritten by a
|
|
1037
|
+
// later execution. A tab whose artifact no longer exists degrades to an
|
|
1038
|
+
// honest notice rather than vanishing.
|
|
1039
|
+
const artifactByKey = useMemo(
|
|
1040
|
+
() =>
|
|
1041
|
+
new Map(sessionArtifacts.map((entry) => [artifactKey(entry.artifact), entry])),
|
|
1042
|
+
[sessionArtifacts],
|
|
1043
|
+
);
|
|
1044
|
+
const artifactVirtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(() => {
|
|
1045
|
+
return editors
|
|
1046
|
+
.filter((editor) => editor.entryId === ARTIFACT_DOCUMENT_ENTRY_ID)
|
|
1047
|
+
.map((editor) => {
|
|
1048
|
+
const entry = artifactByKey.get(editor.path);
|
|
1049
|
+
return {
|
|
1050
|
+
entryId: ARTIFACT_DOCUMENT_ENTRY_ID,
|
|
1051
|
+
path: editor.path,
|
|
1052
|
+
content: entry ? (
|
|
1053
|
+
<ArtifactDocument
|
|
1054
|
+
key={entry.artifact.contentHash || editor.path}
|
|
1055
|
+
artifact={entry.artifact}
|
|
1056
|
+
executionId={entry.executionId}
|
|
1057
|
+
org={org}
|
|
1058
|
+
isTerminal={entry.isTerminal}
|
|
1059
|
+
onApplied={onApplied}
|
|
1060
|
+
/>
|
|
1061
|
+
) : (
|
|
1062
|
+
<ArtifactUnavailableNotice />
|
|
1063
|
+
),
|
|
1064
|
+
};
|
|
1065
|
+
});
|
|
1066
|
+
}, [editors, artifactByKey, org, onApplied]);
|
|
1067
|
+
|
|
1068
|
+
const virtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(
|
|
1069
|
+
() => [planVirtualDocument, ...artifactVirtualDocuments],
|
|
1070
|
+
[planVirtualDocument, artifactVirtualDocuments],
|
|
1071
|
+
);
|
|
1072
|
+
|
|
938
1073
|
const handleRemoveAgent = useCallback(() => {
|
|
939
1074
|
flow.setAgentRef(null);
|
|
940
1075
|
flow.setResolution(null);
|
|
@@ -1001,6 +1136,8 @@ function SessionPanelRegion({
|
|
|
1001
1136
|
onApplied,
|
|
1002
1137
|
onImplementPlan,
|
|
1003
1138
|
onOpenPlan,
|
|
1139
|
+
onOpenArtifact,
|
|
1140
|
+
onActivateArtifact,
|
|
1004
1141
|
});
|
|
1005
1142
|
|
|
1006
1143
|
// Explorer-footer folder attach (desktop only — needs the native picker).
|
|
@@ -1011,9 +1148,19 @@ function SessionPanelRegion({
|
|
|
1011
1148
|
if (path) flow.workspace.addLocalPath(path);
|
|
1012
1149
|
}, [onBrowseLocalFolder, flow.workspace.addLocalPath]);
|
|
1013
1150
|
|
|
1151
|
+
// Entries decorated with per-entry read refs (the latest write-back commit
|
|
1152
|
+
// SHA), so the surface's read-side capabilities — file viewer, tree listing,
|
|
1153
|
+
// name search — resolve at the ref the agent's changes actually live on. A
|
|
1154
|
+
// read-side projection only: `flow.workspace.entries` stays undecorated for
|
|
1155
|
+
// setup and session input.
|
|
1156
|
+
const surfaceEntries = useWorkspaceReadRefs(
|
|
1157
|
+
flow.allExecutions,
|
|
1158
|
+
flow.workspace.entries,
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1014
1161
|
return (
|
|
1015
1162
|
<WorkspaceSurface
|
|
1016
|
-
entries={
|
|
1163
|
+
entries={surfaceEntries}
|
|
1017
1164
|
lister={workspaceFileLister}
|
|
1018
1165
|
reader={workspaceFileReader}
|
|
1019
1166
|
searcher={workspaceContentSearcher}
|
|
@@ -1125,6 +1272,28 @@ function PlanUnavailableNotice() {
|
|
|
1125
1272
|
);
|
|
1126
1273
|
}
|
|
1127
1274
|
|
|
1275
|
+
/**
|
|
1276
|
+
* Shown in an artifact document tab whose artifact is no longer among the
|
|
1277
|
+
* session's outputs (an edge only reachable if the executions backing the tab
|
|
1278
|
+
* changed underneath it). The tab stays closable; the content is honest rather
|
|
1279
|
+
* than blank.
|
|
1280
|
+
*/
|
|
1281
|
+
function ArtifactUnavailableNotice() {
|
|
1282
|
+
return (
|
|
1283
|
+
<div
|
|
1284
|
+
role="status"
|
|
1285
|
+
className="mx-auto flex w-full max-w-3xl flex-col items-center gap-1 px-4 py-8 text-center"
|
|
1286
|
+
>
|
|
1287
|
+
<p className="text-xs font-medium text-foreground">
|
|
1288
|
+
This artifact is no longer available.
|
|
1289
|
+
</p>
|
|
1290
|
+
<p className="text-xs text-muted-foreground">
|
|
1291
|
+
It may have been replaced by a newer version of this session.
|
|
1292
|
+
</p>
|
|
1293
|
+
</div>
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1128
1297
|
/**
|
|
1129
1298
|
* Non-blocking notice for a build that fell back to message-only implement
|
|
1130
1299
|
* (the approved plan.md could not be uploaded/attached). The build itself
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, act, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
5
|
+
import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
6
|
+
import {
|
|
7
|
+
ExecutionArtifactKind,
|
|
8
|
+
ExecutionPhase,
|
|
9
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
10
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "../artifact-document";
|
|
11
|
+
import { artifactKey, type SessionArtifactEntry } from "../useSessionArtifacts";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Wiring-contract test for the Artifacts facet's open/pin gestures. The
|
|
15
|
+
// `onOpenArtifact` (single-click preview) and `onActivateArtifact` (double-click
|
|
16
|
+
// pin) callbacks must thread from SessionViewer, through the intermediate
|
|
17
|
+
// SessionPanelRegion, into useSessionRailViews' <ArtifactsTab>, and — when
|
|
18
|
+
// invoked — land as an artifact virtual-document tab on the editors store.
|
|
19
|
+
// WorkspaceSurface renders as a prop-capturing probe; the store + panel-region
|
|
20
|
+
// wire between them is the subject under test. Tab MECHANICS (preview-slot
|
|
21
|
+
// reuse, pin promotion) live in workspace-editors-store.test.ts and are not
|
|
22
|
+
// re-tested here.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
type CapturedProps = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
const threadProps: CapturedProps[] = [];
|
|
28
|
+
vi.mock("../../execution/MessageThread", () => ({
|
|
29
|
+
MessageThread: (props: CapturedProps) => {
|
|
30
|
+
threadProps.push(props);
|
|
31
|
+
return <div data-testid="thread-probe" />;
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const surfaceProps: CapturedProps[] = [];
|
|
36
|
+
vi.mock("../../workspace/WorkspaceSurface", () => ({
|
|
37
|
+
WorkspaceSurface: (props: CapturedProps) => {
|
|
38
|
+
surfaceProps.push(props);
|
|
39
|
+
return <div data-testid="surface-probe" />;
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("../../composer", async (importOriginal) => {
|
|
44
|
+
const actual = await importOriginal<typeof import("../../composer")>();
|
|
45
|
+
return {
|
|
46
|
+
...actual,
|
|
47
|
+
SessionComposer: () => <div data-testid="composer-probe" />,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const artifact = create(ExecutionArtifactSchema, {
|
|
52
|
+
name: "agent.yaml",
|
|
53
|
+
kind: ExecutionArtifactKind.FILE,
|
|
54
|
+
sizeBytes: 512n,
|
|
55
|
+
sandboxPath: ".stigmer/agent.yaml",
|
|
56
|
+
storageKey: "artifacts/aex_1/agent.yaml",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const artifactExecution = create(AgentExecutionSchema, {
|
|
60
|
+
metadata: { id: "aex_1" },
|
|
61
|
+
status: {
|
|
62
|
+
phase: ExecutionPhase.EXECUTION_COMPLETED,
|
|
63
|
+
artifacts: [artifact],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const entry: SessionArtifactEntry = {
|
|
68
|
+
artifact,
|
|
69
|
+
executionId: "aex_1",
|
|
70
|
+
isTerminal: true,
|
|
71
|
+
hasNameCollision: false,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const stubWorkspace = {
|
|
75
|
+
entries: [],
|
|
76
|
+
hasEntries: false,
|
|
77
|
+
toInput: vi.fn().mockReturnValue([]),
|
|
78
|
+
addGitRepo: vi.fn(),
|
|
79
|
+
addLocalPath: vi.fn(),
|
|
80
|
+
remove: vi.fn(),
|
|
81
|
+
clear: vi.fn(),
|
|
82
|
+
clearLocal: vi.fn(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const stubConv = {
|
|
86
|
+
session: { spec: {} },
|
|
87
|
+
isLoading: false,
|
|
88
|
+
loadError: null,
|
|
89
|
+
completedExecutions: [artifactExecution],
|
|
90
|
+
activeStreamExecution: null,
|
|
91
|
+
pendingUserMessage: null,
|
|
92
|
+
workspaceEntries: [],
|
|
93
|
+
fileChangeSets: [],
|
|
94
|
+
submitFileDecision: vi.fn(),
|
|
95
|
+
submittingFileDecisionKeys: new Set<string>(),
|
|
96
|
+
fileDecisionErrors: new Map<string, Error>(),
|
|
97
|
+
sendError: null,
|
|
98
|
+
stopError: null,
|
|
99
|
+
streamError: null,
|
|
100
|
+
reconnectStream: vi.fn(),
|
|
101
|
+
submittingApprovalIds: new Set<string>(),
|
|
102
|
+
approvalErrors: new Map<string, Error>(),
|
|
103
|
+
isStoppable: false,
|
|
104
|
+
isStopping: false,
|
|
105
|
+
isSending: false,
|
|
106
|
+
canSendFollowUp: true,
|
|
107
|
+
isReconnecting: false,
|
|
108
|
+
connectTimedOut: false,
|
|
109
|
+
isSlow: false,
|
|
110
|
+
stop: vi.fn(),
|
|
111
|
+
retryLastSend: vi.fn(),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const stubSessionPageFlow = {
|
|
115
|
+
conv: stubConv,
|
|
116
|
+
harness: "native" as const,
|
|
117
|
+
executionTarget: undefined,
|
|
118
|
+
model: [undefined, vi.fn()] as const,
|
|
119
|
+
interactionMode: ["agent" as const, vi.fn()] as const,
|
|
120
|
+
agentRef: { org: "acme", slug: "support-bot" },
|
|
121
|
+
setAgentRef: vi.fn(),
|
|
122
|
+
resolution: null,
|
|
123
|
+
setResolution: vi.fn(),
|
|
124
|
+
isDefaultAgent: false,
|
|
125
|
+
mcpServerUsages: [],
|
|
126
|
+
setMcpServerUsages: vi.fn(),
|
|
127
|
+
skillRefs: [],
|
|
128
|
+
setSkillRefs: vi.fn(),
|
|
129
|
+
workspace: stubWorkspace,
|
|
130
|
+
sessionVariables: { entries: [], isEmpty: true, clear: vi.fn() },
|
|
131
|
+
autoApproveAll: false,
|
|
132
|
+
setAutoApproveAll: vi.fn(),
|
|
133
|
+
handleSubmit: vi.fn(),
|
|
134
|
+
submitError: null as Error | null,
|
|
135
|
+
displayExecution: artifactExecution,
|
|
136
|
+
allExecutions: [artifactExecution],
|
|
137
|
+
sandboxWorkspaceRoot: "/home/daytona/workspace",
|
|
138
|
+
};
|
|
139
|
+
vi.mock("../useSessionPageFlow", () => ({
|
|
140
|
+
useSessionPageFlow: () => stubSessionPageFlow,
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
vi.mock("../../hooks", () => ({
|
|
144
|
+
useStigmer: () => ({
|
|
145
|
+
agentExecution: {
|
|
146
|
+
uploadAttachment: vi.fn(),
|
|
147
|
+
getArtifactContent: vi.fn(),
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
import { SessionViewer } from "../SessionViewer";
|
|
153
|
+
|
|
154
|
+
/** The latest editors array the (mocked) WorkspaceSurface received. */
|
|
155
|
+
function latestEditors(): ReadonlyArray<{
|
|
156
|
+
entryId: string;
|
|
157
|
+
path: string;
|
|
158
|
+
preview: boolean;
|
|
159
|
+
}> {
|
|
160
|
+
return (surfaceProps.at(-1)?.editors ?? []) as never;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Read a callback off the rail-view <ArtifactsTab> element the surface received. */
|
|
164
|
+
function artifactsTabCallback(
|
|
165
|
+
name: "onOpenArtifact" | "onActivateArtifact",
|
|
166
|
+
): (entry: SessionArtifactEntry) => void {
|
|
167
|
+
const views = surfaceProps.at(-1)?.extraViews as
|
|
168
|
+
| ReadonlyArray<{ id: string; content: { props: CapturedProps } }>
|
|
169
|
+
| undefined;
|
|
170
|
+
const artifactsView = views?.find((v) => v.id === "artifacts");
|
|
171
|
+
if (!artifactsView) throw new Error("Artifacts rail view not present");
|
|
172
|
+
return artifactsView.content.props[name] as (e: SessionArtifactEntry) => void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
threadProps.length = 0;
|
|
177
|
+
surfaceProps.length = 0;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
cleanup();
|
|
182
|
+
vi.clearAllMocks();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("SessionViewer — Artifacts facet open/pin wiring", () => {
|
|
186
|
+
it("threads onOpenArtifact + onActivateArtifact into the Artifacts rail view", () => {
|
|
187
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
188
|
+
fireEvent.click(screen.getByRole("button", { name: "Show panel" }));
|
|
189
|
+
|
|
190
|
+
expect(typeof artifactsTabCallback("onOpenArtifact")).toBe("function");
|
|
191
|
+
expect(typeof artifactsTabCallback("onActivateArtifact")).toBe("function");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("opening an artifact lands a PREVIEW tab; activating it pins the tab", () => {
|
|
195
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
196
|
+
fireEvent.click(screen.getByRole("button", { name: "Show panel" }));
|
|
197
|
+
|
|
198
|
+
// Single-click preview → one preview tab keyed by the artifact identity.
|
|
199
|
+
act(() => artifactsTabCallback("onOpenArtifact")(entry));
|
|
200
|
+
expect(latestEditors()).toEqual([
|
|
201
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: artifactKey(artifact), preview: true },
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
// Double-click pin → the same tab, now persistent.
|
|
205
|
+
act(() => artifactsTabCallback("onActivateArtifact")(entry));
|
|
206
|
+
expect(latestEditors()).toEqual([
|
|
207
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: artifactKey(artifact), preview: false },
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
});
|