@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
package/src/execution/index.ts
CHANGED
|
@@ -169,7 +169,11 @@ export { MessageEntry } from "./MessageEntry.js";
|
|
|
169
169
|
export type { MessageEntryProps } from "./MessageEntry.js";
|
|
170
170
|
|
|
171
171
|
export { MessageThread } from "./MessageThread.js";
|
|
172
|
-
export type {
|
|
172
|
+
export type {
|
|
173
|
+
MessageThreadProps,
|
|
174
|
+
MessageThreadSlots,
|
|
175
|
+
ThreadContentColumn,
|
|
176
|
+
} from "./MessageThread.js";
|
|
173
177
|
|
|
174
178
|
export { ThreadSkeleton } from "./ThreadSkeleton.js";
|
|
175
179
|
export type { ThreadSkeletonProps } from "./ThreadSkeleton.js";
|
|
@@ -182,11 +186,12 @@ export type { ExecutionProgressProps } from "./ExecutionProgress.js";
|
|
|
182
186
|
|
|
183
187
|
export {
|
|
184
188
|
TodoList,
|
|
189
|
+
TodoRow,
|
|
185
190
|
TodoInProgressIcon,
|
|
186
191
|
findActiveTodo,
|
|
187
192
|
todoCompletionSummary,
|
|
188
193
|
} from "./TodoList.js";
|
|
189
|
-
export type { TodoListProps } from "./TodoList.js";
|
|
194
|
+
export type { TodoListProps, TodoRowProps } from "./TodoList.js";
|
|
190
195
|
|
|
191
196
|
export { TodoCard } from "./TodoCard.js";
|
|
192
197
|
export type { TodoCardProps } from "./TodoCard.js";
|
|
@@ -204,6 +209,7 @@ export {
|
|
|
204
209
|
deriveEffectiveVerdicts,
|
|
205
210
|
changeForRowPath,
|
|
206
211
|
fileReviewRowState,
|
|
212
|
+
fileReviewRowChange,
|
|
207
213
|
} from "./file-review-status.js";
|
|
208
214
|
export type {
|
|
209
215
|
FileReviewability,
|
|
@@ -239,15 +245,31 @@ export type { FileChangeProgressBarProps } from "./FileChangeProgressBar.js";
|
|
|
239
245
|
export { ApprovalContext, useApproval } from "./ApprovalContext.js";
|
|
240
246
|
export type { ApprovalContextValue, UseApprovalResult } from "./ApprovalContext.js";
|
|
241
247
|
|
|
242
|
-
export {
|
|
248
|
+
export {
|
|
249
|
+
FileReviewContext,
|
|
250
|
+
useFileReviewRowState,
|
|
251
|
+
useFileReviewRowChange,
|
|
252
|
+
} from "./FileReviewContext.js";
|
|
243
253
|
export type { FileReviewContextValue } from "./FileReviewContext.js";
|
|
244
254
|
|
|
245
|
-
export {
|
|
246
|
-
export type {
|
|
255
|
+
export { ArtifactRow } from "./ArtifactRow.js";
|
|
256
|
+
export type { ArtifactRowProps } from "./ArtifactRow.js";
|
|
247
257
|
|
|
248
258
|
export { ArtifactContentRenderer } from "./ArtifactContentRenderer.js";
|
|
249
259
|
export type { ArtifactContentRendererProps } from "./ArtifactContentRenderer.js";
|
|
250
260
|
|
|
261
|
+
export { ArtifactContentBody } from "./ArtifactContentBody.js";
|
|
262
|
+
export type { ArtifactContentBodyProps } from "./ArtifactContentBody.js";
|
|
263
|
+
|
|
264
|
+
export { ArtifactDocument } from "./ArtifactDocument.js";
|
|
265
|
+
export type { ArtifactDocumentProps } from "./ArtifactDocument.js";
|
|
266
|
+
|
|
267
|
+
export { useArtifactInspection } from "./useArtifactInspection.js";
|
|
268
|
+
export type {
|
|
269
|
+
ArtifactInspection,
|
|
270
|
+
UseArtifactInspectionOptions,
|
|
271
|
+
} from "./useArtifactInspection.js";
|
|
272
|
+
|
|
251
273
|
export { ArtifactPreviewContent, ArtifactPreviewModal } from "./ArtifactPreviewModal.js";
|
|
252
274
|
export type { ArtifactPreviewContentProps, ArtifactPreviewModalProps } from "./ArtifactPreviewModal.js";
|
|
253
275
|
|
|
@@ -100,6 +100,11 @@ export interface UseArtifactContentReturn {
|
|
|
100
100
|
* When the same file is overwritten during execution, the `storageKey` stays
|
|
101
101
|
* stable but `contentHash` changes, triggering a re-fetch so the UI never
|
|
102
102
|
* shows stale content. Pass `undefined` or omit for backwards compatibility.
|
|
103
|
+
* @param cacheKey - Optional cross-mount cache key (DD-014). When set, a remount
|
|
104
|
+
* with the same key renders the previously-fetched content instantly and
|
|
105
|
+
* refetches in the background (no skeleton flash) — used by the artifact
|
|
106
|
+
* document so switching back to a recently-viewed tab is instant. Omit for the
|
|
107
|
+
* default fetch-on-mount behavior (card / modal / plan callers are unchanged).
|
|
103
108
|
*
|
|
104
109
|
* @example
|
|
105
110
|
* ```tsx
|
|
@@ -142,6 +147,7 @@ export function useArtifactContent(
|
|
|
142
147
|
storageKey: string | null,
|
|
143
148
|
entryPath?: string | null,
|
|
144
149
|
contentHash?: string,
|
|
150
|
+
cacheKey?: string,
|
|
145
151
|
): UseArtifactContentReturn {
|
|
146
152
|
const stigmer = useStigmer();
|
|
147
153
|
|
|
@@ -164,6 +170,7 @@ export function useArtifactContent(
|
|
|
164
170
|
: null,
|
|
165
171
|
[executionId, storageKey, entryPath, contentHash, stigmer],
|
|
166
172
|
EMPTY_ARTIFACT,
|
|
173
|
+
cacheKey ? { cacheKey } : undefined,
|
|
167
174
|
);
|
|
168
175
|
|
|
169
176
|
return {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
5
|
+
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
|
+
import { useArtifactContent } from "./useArtifactContent.js";
|
|
7
|
+
import { isTextArtifact } from "./artifact-utils.js";
|
|
8
|
+
import { useDetectStigmerResource } from "../library/useDetectStigmerResource.js";
|
|
9
|
+
import { useDetectSkillPackage } from "../library/useDetectSkillPackage.js";
|
|
10
|
+
import type { SkillPackageDetection } from "../library/detect-skill-package.js";
|
|
11
|
+
import {
|
|
12
|
+
useApplyResource,
|
|
13
|
+
type ApplyResourceResult,
|
|
14
|
+
} from "../library/useApplyResource.js";
|
|
15
|
+
|
|
16
|
+
const COPIED_FEEDBACK_MS = 2000;
|
|
17
|
+
|
|
18
|
+
/** Options for {@link useArtifactInspection}. */
|
|
19
|
+
export interface UseArtifactInspectionOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Cross-mount cache key for the content fetch (DD-014). Set it so a remount
|
|
22
|
+
* with the same key renders the previously-fetched content instantly and
|
|
23
|
+
* refetches in the background — the artifact document passes it so switching
|
|
24
|
+
* back to a recently-viewed tab is instant. Omit for fetch-on-mount (the
|
|
25
|
+
* modal, which resets on every open).
|
|
26
|
+
*/
|
|
27
|
+
readonly cacheKey?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Called after a resource is successfully applied or a skill package pushed.
|
|
30
|
+
* The consumer uses it for post-apply behavior (toast, navigate to Library).
|
|
31
|
+
*/
|
|
32
|
+
readonly onApplied?: (result: ApplyResourceResult) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Everything a chrome (the modal action bar, the document toolbar, the content
|
|
37
|
+
* body) needs to inspect and act on one artifact: fetched content, resource
|
|
38
|
+
* detection, the Apply/Push action, and clipboard copy.
|
|
39
|
+
*/
|
|
40
|
+
export interface ArtifactInspection {
|
|
41
|
+
/** Decoded text content, or `null` (directory / binary / loading / error). */
|
|
42
|
+
readonly content: string | null;
|
|
43
|
+
/** Server-detected content type, or `null`. */
|
|
44
|
+
readonly contentType: string | null;
|
|
45
|
+
/** Whether the fetched content was truncated by the server's size cap. */
|
|
46
|
+
readonly isTruncated: boolean;
|
|
47
|
+
/** `true` while the content request is in-flight (first load). */
|
|
48
|
+
readonly isLoading: boolean;
|
|
49
|
+
/** Content-fetch error, or `null`. */
|
|
50
|
+
readonly error: Error | null;
|
|
51
|
+
/** Re-fetch the artifact content. */
|
|
52
|
+
readonly refetch: () => void;
|
|
53
|
+
|
|
54
|
+
/** Whether the artifact is a directory (skill package candidate). */
|
|
55
|
+
readonly isDirectory: boolean;
|
|
56
|
+
/** Skill-package detection for directory artifacts. */
|
|
57
|
+
readonly skillDetection: SkillPackageDetection;
|
|
58
|
+
|
|
59
|
+
/** `true` when the artifact is a recognized Stigmer resource or skill. */
|
|
60
|
+
readonly isDetected: boolean;
|
|
61
|
+
/** `true` while detection is still resolving (content / skill fetch). */
|
|
62
|
+
readonly isDetecting: boolean;
|
|
63
|
+
/** Detection badge label (`"Agent detected"`, `"Skill · N files"`), or `null`. */
|
|
64
|
+
readonly detectionLabel: string | null;
|
|
65
|
+
|
|
66
|
+
/** Apply/Push CTA label (`"Apply to org"`, `"Push Skill to org"`), or `null`. */
|
|
67
|
+
readonly ctaLabel: string | null;
|
|
68
|
+
/** Apply the detected resource / push the detected skill. */
|
|
69
|
+
readonly apply: () => Promise<void>;
|
|
70
|
+
/** `true` while an apply/push is in-flight. */
|
|
71
|
+
readonly isApplying: boolean;
|
|
72
|
+
/** The successful apply result, or `null`. */
|
|
73
|
+
readonly applyResult: ApplyResourceResult | null;
|
|
74
|
+
/** Apply/push error, or `null`. */
|
|
75
|
+
readonly applyError: Error | null;
|
|
76
|
+
|
|
77
|
+
/** Copy the in-memory content to the clipboard. No-op without content. */
|
|
78
|
+
readonly copy: () => void;
|
|
79
|
+
/** `true` for a short window after a successful copy. */
|
|
80
|
+
readonly copied: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Headless behavior hook consolidating the artifact inspect-and-act pipeline
|
|
85
|
+
* that was previously inlined per artifact surface: content fetch,
|
|
86
|
+
* Agent/McpServer YAML detection, skill-package detection, the Apply/Push
|
|
87
|
+
* mutation, and in-memory clipboard copy.
|
|
88
|
+
*
|
|
89
|
+
* It is the single writer of this pipeline's derived state, consumed by every
|
|
90
|
+
* artifact chrome — the modal action bar and the editor-area
|
|
91
|
+
* `ArtifactDocument` toolbar (DD-003 headless-first). Because there is exactly
|
|
92
|
+
* one place computing `detectionLabel` / `ctaLabel` / the apply flow, the two
|
|
93
|
+
* surfaces cannot drift.
|
|
94
|
+
*
|
|
95
|
+
* All four underlying hooks are called unconditionally with null-gated
|
|
96
|
+
* arguments (their documented skip contract), so this composition is
|
|
97
|
+
* React-rules-clean regardless of the artifact kind.
|
|
98
|
+
*
|
|
99
|
+
* @param artifact - The artifact to inspect.
|
|
100
|
+
* @param executionId - Execution that produced it (content + skill fetching).
|
|
101
|
+
* @param org - Organization for the "Apply to [org]" / "Push Skill to [org]" CTA.
|
|
102
|
+
* @param options - {@link UseArtifactInspectionOptions}.
|
|
103
|
+
*/
|
|
104
|
+
export function useArtifactInspection(
|
|
105
|
+
artifact: ExecutionArtifact,
|
|
106
|
+
executionId: string,
|
|
107
|
+
org: string,
|
|
108
|
+
options?: UseArtifactInspectionOptions,
|
|
109
|
+
): ArtifactInspection {
|
|
110
|
+
const isDirectory = artifact.kind === ExecutionArtifactKind.DIRECTORY;
|
|
111
|
+
const canFetchContent = !isDirectory && isTextArtifact(artifact);
|
|
112
|
+
|
|
113
|
+
const {
|
|
114
|
+
content,
|
|
115
|
+
contentType,
|
|
116
|
+
isTruncated,
|
|
117
|
+
isLoading,
|
|
118
|
+
error,
|
|
119
|
+
refetch,
|
|
120
|
+
} = useArtifactContent(
|
|
121
|
+
canFetchContent ? executionId : null,
|
|
122
|
+
canFetchContent ? artifact.storageKey : null,
|
|
123
|
+
undefined,
|
|
124
|
+
artifact.contentHash || undefined,
|
|
125
|
+
options?.cacheKey,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const yamlDetection = useDetectStigmerResource(
|
|
129
|
+
canFetchContent ? content : null,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const { detection: skillDetection, isLoading: isSkillLoading } =
|
|
133
|
+
useDetectSkillPackage(
|
|
134
|
+
isDirectory ? artifact : null,
|
|
135
|
+
isDirectory ? executionId : null,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const isDetected = yamlDetection.detected || skillDetection.detected;
|
|
139
|
+
const isDetecting =
|
|
140
|
+
(canFetchContent && isLoading) || (isDirectory && isSkillLoading);
|
|
141
|
+
|
|
142
|
+
let detectionLabel: string | null = null;
|
|
143
|
+
if (yamlDetection.detected) {
|
|
144
|
+
detectionLabel = `${yamlDetection.displayName} detected`;
|
|
145
|
+
} else if (skillDetection.detected) {
|
|
146
|
+
const count = skillDetection.fileCount;
|
|
147
|
+
detectionLabel = `Skill \u00B7 ${count} ${count === 1 ? "file" : "files"}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let ctaLabel: string | null = null;
|
|
151
|
+
if (yamlDetection.detected) {
|
|
152
|
+
ctaLabel = `Apply to ${org}`;
|
|
153
|
+
} else if (skillDetection.detected) {
|
|
154
|
+
ctaLabel = `Push Skill to ${org}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Apply / Push ---------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
const { applyYamlResource, pushSkillPackage, isApplying, error: applyError, clearError } =
|
|
160
|
+
useApplyResource();
|
|
161
|
+
const [applyResult, setApplyResult] = useState<ApplyResourceResult | null>(
|
|
162
|
+
null,
|
|
163
|
+
);
|
|
164
|
+
const onApplied = options?.onApplied;
|
|
165
|
+
|
|
166
|
+
const apply = useCallback(async () => {
|
|
167
|
+
clearError();
|
|
168
|
+
try {
|
|
169
|
+
let result: ApplyResourceResult;
|
|
170
|
+
if (yamlDetection.detected && content) {
|
|
171
|
+
result = await applyYamlResource(content, org);
|
|
172
|
+
} else if (skillDetection.detected) {
|
|
173
|
+
result = await pushSkillPackage({
|
|
174
|
+
org,
|
|
175
|
+
executionId,
|
|
176
|
+
storageKey: artifact.storageKey,
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setApplyResult(result);
|
|
182
|
+
onApplied?.(result);
|
|
183
|
+
} catch {
|
|
184
|
+
// error state is owned by useApplyResource
|
|
185
|
+
}
|
|
186
|
+
}, [
|
|
187
|
+
yamlDetection.detected,
|
|
188
|
+
skillDetection.detected,
|
|
189
|
+
content,
|
|
190
|
+
org,
|
|
191
|
+
executionId,
|
|
192
|
+
artifact.storageKey,
|
|
193
|
+
applyYamlResource,
|
|
194
|
+
pushSkillPackage,
|
|
195
|
+
clearError,
|
|
196
|
+
onApplied,
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
// --- Copy -----------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
const [copied, setCopied] = useState(false);
|
|
202
|
+
const copy = useCallback(() => {
|
|
203
|
+
if (!content) return;
|
|
204
|
+
void navigator.clipboard.writeText(content).then(() => {
|
|
205
|
+
setCopied(true);
|
|
206
|
+
setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
|
|
207
|
+
});
|
|
208
|
+
}, [content]);
|
|
209
|
+
|
|
210
|
+
return useMemo(
|
|
211
|
+
() => ({
|
|
212
|
+
content,
|
|
213
|
+
contentType,
|
|
214
|
+
isTruncated,
|
|
215
|
+
isLoading,
|
|
216
|
+
error,
|
|
217
|
+
refetch,
|
|
218
|
+
isDirectory,
|
|
219
|
+
skillDetection,
|
|
220
|
+
isDetected,
|
|
221
|
+
isDetecting,
|
|
222
|
+
detectionLabel,
|
|
223
|
+
ctaLabel,
|
|
224
|
+
apply,
|
|
225
|
+
isApplying,
|
|
226
|
+
applyResult,
|
|
227
|
+
applyError,
|
|
228
|
+
copy,
|
|
229
|
+
copied,
|
|
230
|
+
}),
|
|
231
|
+
[
|
|
232
|
+
content,
|
|
233
|
+
contentType,
|
|
234
|
+
isTruncated,
|
|
235
|
+
isLoading,
|
|
236
|
+
error,
|
|
237
|
+
refetch,
|
|
238
|
+
isDirectory,
|
|
239
|
+
skillDetection,
|
|
240
|
+
isDetected,
|
|
241
|
+
isDetecting,
|
|
242
|
+
detectionLabel,
|
|
243
|
+
ctaLabel,
|
|
244
|
+
apply,
|
|
245
|
+
isApplying,
|
|
246
|
+
applyResult,
|
|
247
|
+
applyError,
|
|
248
|
+
copy,
|
|
249
|
+
copied,
|
|
250
|
+
],
|
|
251
|
+
);
|
|
252
|
+
}
|
|
@@ -93,6 +93,17 @@ export interface CreateAgentExecutionInput {
|
|
|
93
93
|
* Populated by the drag-to-reference feature in SessionComposer.
|
|
94
94
|
*/
|
|
95
95
|
readonly workspaceFileRefs?: string[];
|
|
96
|
+
/**
|
|
97
|
+
* ID of the execution this one supersedes via edit-and-resubmit.
|
|
98
|
+
*
|
|
99
|
+
* Set when the user stopped an in-flight turn, edited its message, and
|
|
100
|
+
* resubmitted. Chat threads hide the superseded execution so the edited
|
|
101
|
+
* message replaces the original in place; history surfaces keep the full
|
|
102
|
+
* record. Maps to `AgentExecutionSpec.supersedes_execution_id`.
|
|
103
|
+
*
|
|
104
|
+
* Display-level only — the runner does not rewind model context.
|
|
105
|
+
*/
|
|
106
|
+
readonly supersedesExecutionId?: string;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
/** Resolved output of {@link UseCreateAgentExecutionReturn.create}. */
|
|
@@ -199,6 +210,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
|
|
|
199
210
|
runtimeEnv: input.runtimeEnv,
|
|
200
211
|
attachments: input.attachments,
|
|
201
212
|
workspaceFileRefs: input.workspaceFileRefs,
|
|
213
|
+
supersedesExecutionId: input.supersedesExecutionId,
|
|
202
214
|
});
|
|
203
215
|
|
|
204
216
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { renderHook } from "@testing-library/react";
|
|
3
3
|
import { useGitHubFileReader } from "../useGitHubFileReader";
|
|
4
|
+
import { WorkspaceFileNotFoundError } from "../../workspace/WorkspaceFileReader";
|
|
4
5
|
import type { WorkspaceEntry } from "../../workspace/useWorkspaceEntries";
|
|
5
6
|
|
|
6
7
|
function gitEntry(overrides?: Partial<WorkspaceEntry>): WorkspaceEntry {
|
|
@@ -120,11 +121,43 @@ describe("useGitHubFileReader", () => {
|
|
|
120
121
|
);
|
|
121
122
|
});
|
|
122
123
|
|
|
123
|
-
it("throws on a
|
|
124
|
+
it("throws the typed WorkspaceFileNotFoundError on a 404", async () => {
|
|
124
125
|
vi.mocked(globalThis.fetch).mockResolvedValue(new Response("nope", { status: 404 }));
|
|
125
126
|
|
|
126
127
|
const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
|
|
127
|
-
await expect(result.current!(gitEntry(), "missing.ts")).rejects.toThrow(
|
|
128
|
+
await expect(result.current!(gitEntry(), "missing.ts")).rejects.toThrow(
|
|
129
|
+
WorkspaceFileNotFoundError,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("throws a generic error (not the typed not-found) on other non-OK responses", async () => {
|
|
134
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response("boom", { status: 500 }));
|
|
135
|
+
|
|
136
|
+
const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
|
|
137
|
+
const error = await result.current!(gitEntry(), "src/index.ts").then(
|
|
138
|
+
() => null,
|
|
139
|
+
(e: unknown) => e,
|
|
140
|
+
);
|
|
141
|
+
expect(error).toBeInstanceOf(Error);
|
|
142
|
+
expect((error as Error).message).toContain("HTTP 500");
|
|
143
|
+
expect(error).not.toBeInstanceOf(WorkspaceFileNotFoundError);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("reads at readRef in preference to gitBranch when both are set", async () => {
|
|
147
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
148
|
+
jsonResponse({ encoding: "base64", size: 1, content: b64("x"), sha: "s1" }),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
|
|
152
|
+
await result.current!(
|
|
153
|
+
gitEntry({ gitBranch: "main", readRef: "abc123def456" }),
|
|
154
|
+
"notes.md",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
158
|
+
expect.stringContaining("?ref=abc123def456"),
|
|
159
|
+
expect.anything(),
|
|
160
|
+
);
|
|
128
161
|
});
|
|
129
162
|
|
|
130
163
|
it("throws when the path is a directory (Contents API returns an array)", async () => {
|
|
@@ -148,6 +148,20 @@ describe("useGitHubTreeLister", () => {
|
|
|
148
148
|
);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("lists at readRef in preference to gitBranch when both are set", async () => {
|
|
152
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
153
|
+
new Response(JSON.stringify(SAMPLE_TREE_RESPONSE), { status: 200 }),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const { result } = renderHook(() => useGitHubTreeLister("ghp_abc"));
|
|
157
|
+
await result.current!(gitEntry({ gitBranch: "main", readRef: "abc123def456" }));
|
|
158
|
+
|
|
159
|
+
expect(vi.mocked(globalThis.fetch)).toHaveBeenCalledWith(
|
|
160
|
+
expect.stringContaining("/git/trees/abc123def456?"),
|
|
161
|
+
expect.anything(),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
151
165
|
it("encodes branch names with special characters", async () => {
|
|
152
166
|
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
153
167
|
new Response(JSON.stringify(SAMPLE_TREE_RESPONSE), { status: 200 }),
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback } from "react";
|
|
4
4
|
import type { WorkspaceEntry } from "../workspace/useWorkspaceEntries.js";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
WorkspaceFileNotFoundError,
|
|
7
|
+
type WorkspaceFileContent,
|
|
8
|
+
type WorkspaceFileReader,
|
|
8
9
|
} from "../workspace/WorkspaceFileReader.js";
|
|
9
10
|
import { base64ToBytes, normalizeGitHubContent } from "./decodeGitHubContent.js";
|
|
10
11
|
import { parseGitUrl } from "./parseGitUrl.js";
|
|
@@ -58,7 +59,9 @@ function encodePath(path: string): string {
|
|
|
58
59
|
* (non-git entry, missing/unparseable URL) — the "unsupported here" state.
|
|
59
60
|
* - **Throws** on a real failure: a non-OK response, or a directory path
|
|
60
61
|
* (which the Contents API returns as a JSON array). Consumers surface these
|
|
61
|
-
* as an error, distinct from the null "unsupported" state.
|
|
62
|
+
* as an error, distinct from the null "unsupported" state. A 404 throws the
|
|
63
|
+
* typed {@link WorkspaceFileNotFoundError} so consumers can fall back to
|
|
64
|
+
* session-captured content for files not yet pushed to the ref.
|
|
62
65
|
*
|
|
63
66
|
* @example
|
|
64
67
|
* ```tsx
|
|
@@ -80,14 +83,24 @@ export function useGitHubFileReader(
|
|
|
80
83
|
const parsed = parseGitUrl(entry.gitUrl);
|
|
81
84
|
if (!parsed) return null;
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
// readRef (the session's write-back commit SHA, when one exists) wins
|
|
87
|
+
// over the configured branch: agent-created files live on the pushed
|
|
88
|
+
// write-back commit, not the base branch. The Contents API accepts a
|
|
89
|
+
// branch name or a commit SHA interchangeably as `ref`.
|
|
90
|
+
const ref = entry.readRef || entry.gitBranch || "main";
|
|
84
91
|
const url =
|
|
85
92
|
`${GITHUB_API}/${parsed.owner}/${parsed.repo}/contents/${encodePath(path)}` +
|
|
86
|
-
`?ref=${encodeURIComponent(
|
|
93
|
+
`?ref=${encodeURIComponent(ref)}`;
|
|
87
94
|
|
|
88
95
|
const resp = await fetch(url, {
|
|
89
96
|
headers: { Authorization: `Bearer ${token}` },
|
|
90
97
|
});
|
|
98
|
+
// 404 is typed: a file that isn't at this ref (yet) is an expected,
|
|
99
|
+
// recoverable state — e.g. an agent-created file whose write-back
|
|
100
|
+
// hasn't pushed — that consumers handle differently from a failure.
|
|
101
|
+
if (resp.status === 404) {
|
|
102
|
+
throw new WorkspaceFileNotFoundError(path);
|
|
103
|
+
}
|
|
91
104
|
if (!resp.ok) {
|
|
92
105
|
throw new Error(
|
|
93
106
|
`GitHub content fetch failed for ${path} (HTTP ${resp.status})`,
|
|
@@ -38,7 +38,8 @@ const TRUNCATION_MARKER: WorkspaceFileEntry = {
|
|
|
38
38
|
* Creates a {@link WorkspaceFileLister} backed by the GitHub Trees API.
|
|
39
39
|
*
|
|
40
40
|
* Uses the existing client-side OAuth token (same one that powers
|
|
41
|
-
* `GitHubRepoPicker`) to call `GET /repos/{owner}/{repo}/git/trees/{
|
|
41
|
+
* `GitHubRepoPicker`) to call `GET /repos/{owner}/{repo}/git/trees/{ref}?recursive=1`,
|
|
42
|
+
* where the ref is the entry's `readRef` (write-back commit) or `gitBranch`.
|
|
42
43
|
*
|
|
43
44
|
* Returns `null` for:
|
|
44
45
|
* - Non-git workspace entries
|
|
@@ -64,8 +65,11 @@ export function useGitHubTreeLister(
|
|
|
64
65
|
const parsed = parseGitUrl(entry.gitUrl);
|
|
65
66
|
if (!parsed) return null;
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// readRef (the session's write-back commit SHA, when one exists) wins
|
|
69
|
+
// over the configured branch, so the tree includes agent-created files.
|
|
70
|
+
// The Trees API accepts a branch name or a commit SHA interchangeably.
|
|
71
|
+
const ref = entry.readRef || entry.gitBranch || "main";
|
|
72
|
+
const url = `${GITHUB_TREES_API}/${parsed.owner}/${parsed.repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
|
|
69
73
|
|
|
70
74
|
const resp = await fetch(url, {
|
|
71
75
|
headers: { Authorization: `Bearer ${token}` },
|
package/src/index.ts
CHANGED
|
@@ -71,6 +71,7 @@ export {
|
|
|
71
71
|
useWorkspaceContentSearch,
|
|
72
72
|
useWorkspaceSources,
|
|
73
73
|
MAX_WORKSPACE_FILE_READ_BYTES,
|
|
74
|
+
WorkspaceFileNotFoundError,
|
|
74
75
|
WorkspaceEditor,
|
|
75
76
|
WorkspaceFileSearch,
|
|
76
77
|
WorkspaceContentSearch,
|
|
@@ -129,7 +130,9 @@ export {
|
|
|
129
130
|
useSessionExecutions,
|
|
130
131
|
useSessionConversation,
|
|
131
132
|
useSessionArtifacts,
|
|
133
|
+
artifactKey,
|
|
132
134
|
useSessionWriteBacks,
|
|
135
|
+
useWorkspaceReadRefs,
|
|
133
136
|
useSessionFileChanges,
|
|
134
137
|
useSessionUsage,
|
|
135
138
|
useAgentRefFromSession,
|
|
@@ -154,6 +157,7 @@ export {
|
|
|
154
157
|
PlanStreamingDocument,
|
|
155
158
|
PLAN_DOCUMENT_ENTRY_ID,
|
|
156
159
|
PLAN_DOCUMENT_PATH,
|
|
160
|
+
ARTIFACT_DOCUMENT_ENTRY_ID,
|
|
157
161
|
SetupTab,
|
|
158
162
|
} from "./session/index.js";
|
|
159
163
|
export type {
|
|
@@ -231,6 +235,7 @@ export {
|
|
|
231
235
|
deriveEffectiveVerdicts,
|
|
232
236
|
changeForRowPath,
|
|
233
237
|
fileReviewRowState,
|
|
238
|
+
fileReviewRowChange,
|
|
234
239
|
foldFileReviewEventStream,
|
|
235
240
|
displayFileChangeSets,
|
|
236
241
|
toDisplayFileChange,
|
|
@@ -288,8 +293,12 @@ export {
|
|
|
288
293
|
useApproval,
|
|
289
294
|
FileReviewContext,
|
|
290
295
|
useFileReviewRowState,
|
|
291
|
-
|
|
296
|
+
useFileReviewRowChange,
|
|
297
|
+
ArtifactRow,
|
|
292
298
|
ArtifactContentRenderer,
|
|
299
|
+
ArtifactContentBody,
|
|
300
|
+
ArtifactDocument,
|
|
301
|
+
useArtifactInspection,
|
|
293
302
|
ArtifactPreviewContent,
|
|
294
303
|
ArtifactPreviewModal,
|
|
295
304
|
ArtifactsWidget,
|
|
@@ -381,8 +390,12 @@ export type {
|
|
|
381
390
|
ApprovalContextValue,
|
|
382
391
|
UseApprovalResult,
|
|
383
392
|
FileReviewContextValue,
|
|
384
|
-
|
|
393
|
+
ArtifactRowProps,
|
|
385
394
|
ArtifactContentRendererProps,
|
|
395
|
+
ArtifactContentBodyProps,
|
|
396
|
+
ArtifactDocumentProps,
|
|
397
|
+
ArtifactInspection,
|
|
398
|
+
UseArtifactInspectionOptions,
|
|
386
399
|
ArtifactRenderMode,
|
|
387
400
|
ArtifactPreviewContentProps,
|
|
388
401
|
ArtifactPreviewModalProps,
|