@stigmer/react 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composer/SessionComposer.d.ts +37 -5
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -5
- 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 +9 -9
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +18 -145
- 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/FileChangeProgressBar.d.ts +47 -0
- package/execution/FileChangeProgressBar.d.ts.map +1 -0
- package/execution/FileChangeProgressBar.js +49 -0
- package/execution/FileChangeProgressBar.js.map +1 -0
- package/execution/FileChangesView.d.ts.map +1 -1
- package/execution/FileChangesView.js +19 -4
- package/execution/FileChangesView.js.map +1 -1
- package/execution/FileReviewAtoms.d.ts +27 -0
- package/execution/FileReviewAtoms.d.ts.map +1 -0
- package/execution/FileReviewAtoms.js +64 -0
- package/execution/FileReviewAtoms.js.map +1 -0
- package/execution/FileReviewCard.d.ts +2 -2
- package/execution/FileReviewCard.d.ts.map +1 -1
- package/execution/FileReviewCard.js +32 -62
- package/execution/FileReviewCard.js.map +1 -1
- package/execution/MessageEntry.d.ts +0 -9
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +1 -11
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +3 -4
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +25 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +16 -10
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +31 -7
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +4 -4
- package/execution/PlanCompletionCard.d.ts.map +1 -1
- package/execution/PlanCompletionCard.js +4 -4
- package/execution/PlanCompletionCard.js.map +1 -1
- package/execution/PlanStreamingCard.d.ts +7 -2
- package/execution/PlanStreamingCard.d.ts.map +1 -1
- package/execution/PlanStreamingCard.js +11 -6
- package/execution/PlanStreamingCard.js.map +1 -1
- package/execution/index.d.ts +12 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +6 -1
- package/execution/index.js.map +1 -1
- package/execution/use-build-from-plan-hotkey.d.ts +1 -1
- package/execution/use-build-from-plan-hotkey.js +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/useArtifactCopy.d.ts +51 -0
- package/execution/useArtifactCopy.d.ts.map +1 -0
- package/execution/useArtifactCopy.js +75 -0
- package/execution/useArtifactCopy.js.map +1 -0
- package/execution/useArtifactDownload.d.ts.map +1 -1
- package/execution/useArtifactDownload.js +17 -5
- package/execution/useArtifactDownload.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 +16 -3
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.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/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -2
- package/internal/file-tree/FileTreeNode.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/library/detect-plan-artifact.d.ts +27 -9
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +26 -6
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +1 -1
- package/library/index.js.map +1 -1
- package/package.json +5 -4
- package/session/PlanEditor.js +1 -1
- package/session/PlanEditor.js.map +1 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +122 -30
- 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 +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +14 -2
- package/session/plan-document.d.ts.map +1 -1
- package/session/plan-document.js +14 -3
- package/session/plan-document.js.map +1 -1
- package/session/usePlanDraft.d.ts +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 +26 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +51 -6
- 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/src/composer/SessionComposer.tsx +76 -7
- 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 +49 -415
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +0 -62
- package/src/execution/MessageThread.tsx +29 -15
- package/src/execution/PlanArtifactCard.tsx +130 -52
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanStreamingCard.tsx +15 -11
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +1 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
- package/src/execution/__tests__/message-entry.test.tsx +56 -39
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +20 -2
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +17 -3
- package/src/index.ts +17 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- 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/file-tree/FileTreeNode.tsx +13 -2
- 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/library/__tests__/detect-plan-artifact.test.ts +21 -0
- package/src/library/detect-plan-artifact.ts +32 -10
- package/src/library/index.ts +2 -0
- package/src/session/PlanEditor.tsx +1 -1
- package/src/session/SessionViewer.tsx +227 -51
- package/src/session/__tests__/PlanEditor.test.tsx +4 -4
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- 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 +5 -1
- package/src/session/plan-document.ts +14 -3
- package/src/session/usePlanDraft.ts +1 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +80 -10
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/workspace/EditorTabs.tsx +38 -9
- package/src/workspace/ExplorerTree.tsx +10 -4
- package/src/workspace/FileViewer.tsx +27 -6
- package/src/workspace/WorkspaceContentSearch.tsx +44 -20
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileSearch.tsx +51 -28
- package/src/workspace/WorkspaceSurface.tsx +106 -48
- package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
- package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
- package/src/workspace/__tests__/a11y/harness.tsx +140 -0
- package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
- package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
- package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
- package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
- package/styles.css +1 -1
- package/workspace/EditorTabs.d.ts +21 -3
- package/workspace/EditorTabs.d.ts.map +1 -1
- package/workspace/EditorTabs.js +18 -6
- package/workspace/EditorTabs.js.map +1 -1
- package/workspace/ExplorerTree.js +8 -4
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +19 -8
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +23 -2
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +23 -3
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSurface.d.ts.map +1 -1
- package/workspace/WorkspaceSurface.js +54 -18
- package/workspace/WorkspaceSurface.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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
4
|
+
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
import { FileTypeIcon, FolderTypeIcon } from "../internal/file-icons/index.js";
|
|
7
|
+
import { useArtifactDownload } from "./useArtifactDownload.js";
|
|
8
|
+
import { formatArtifactSize } from "./artifact-utils.js";
|
|
9
|
+
|
|
10
|
+
/** Props for {@link ArtifactRow}. */
|
|
11
|
+
export interface ArtifactRowProps {
|
|
12
|
+
/** The execution artifact to render. */
|
|
13
|
+
readonly artifact: ExecutionArtifact;
|
|
14
|
+
/** ID of the execution that produced this artifact — used to mint its download URL. */
|
|
15
|
+
readonly executionId: string;
|
|
16
|
+
/**
|
|
17
|
+
* When `true`, another artifact in the list shares this artifact's display
|
|
18
|
+
* `name` but has a different `sandbox_path`; the row renders the parent
|
|
19
|
+
* directory as a muted subtitle to disambiguate. Typically supplied from
|
|
20
|
+
* {@link SessionArtifactEntry.hasNameCollision}.
|
|
21
|
+
*/
|
|
22
|
+
readonly hasNameCollision?: boolean;
|
|
23
|
+
/** Single click / Enter / Space: open the artifact (a preview tab, or a modal). */
|
|
24
|
+
readonly onOpen: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Double click: promote the artifact to a persistent (pinned) tab. Mirrors the
|
|
27
|
+
* file tree's `onOpenFile` (preview) / `onActivateFile` (pin) split — the
|
|
28
|
+
* leading single click of the double click has already opened the preview, so
|
|
29
|
+
* this promotes it. Omit in panel-less hosts (e.g. the modal-only widget):
|
|
30
|
+
* with no handler the row binds no `onDoubleClick`, so a double click is just
|
|
31
|
+
* two harmless single clicks, exactly as `FileTreeNode` behaves.
|
|
32
|
+
*/
|
|
33
|
+
readonly onActivate?: () => void;
|
|
34
|
+
/** Additional CSS classes for the row's `<li>`. */
|
|
35
|
+
readonly className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One dense, VS Code-style artifact row: a full-width open button (shared
|
|
40
|
+
* file-type icon + name + optional collision subtitle + size) with a sibling,
|
|
41
|
+
* hover/focus-revealed Download button.
|
|
42
|
+
*
|
|
43
|
+
* The open target and the Download control are SIBLINGS, never nested — a
|
|
44
|
+
* `<button>` inside a `<button>` is an axe `nested-interactive` (WCAG 4.1.2)
|
|
45
|
+
* violation. This mirrors `ExplorerRoot`'s header + remove-control pattern.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately execution-pure and presentational: it takes only the artifact,
|
|
48
|
+
* its `executionId`, and open/activate closures — no session-domain types and
|
|
49
|
+
* no content fetch (resource detection and Apply/Push live in the opened
|
|
50
|
+
* document/modal, not the list). The two hosts — the session panel's
|
|
51
|
+
* `ArtifactsTab` facet and the panel-less {@link ArtifactsWidget} — map
|
|
52
|
+
* their entries onto these props and own the behavior wiring.
|
|
53
|
+
*
|
|
54
|
+
* @see ArtifactsTab — session-panel facet (rows open editor-pane document tabs)
|
|
55
|
+
* @see ArtifactsWidget — panel-less embeddable (rows open the preview modal)
|
|
56
|
+
*/
|
|
57
|
+
export function ArtifactRow({
|
|
58
|
+
artifact,
|
|
59
|
+
executionId,
|
|
60
|
+
hasNameCollision = false,
|
|
61
|
+
onOpen,
|
|
62
|
+
onActivate,
|
|
63
|
+
className,
|
|
64
|
+
}: ArtifactRowProps) {
|
|
65
|
+
const isDirectory = artifact.kind === ExecutionArtifactKind.DIRECTORY;
|
|
66
|
+
const { download, isDownloading } = useArtifactDownload(executionId);
|
|
67
|
+
|
|
68
|
+
const parentDir =
|
|
69
|
+
hasNameCollision && artifact.sandboxPath
|
|
70
|
+
? parentDirectory(artifact.sandboxPath)
|
|
71
|
+
: null;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<li className={cn("group flex items-stretch", className)}>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={onOpen}
|
|
78
|
+
onDoubleClick={onActivate}
|
|
79
|
+
title={artifact.sandboxPath || artifact.name}
|
|
80
|
+
className={cn(
|
|
81
|
+
"flex min-w-0 flex-1 items-center gap-2 px-2 py-1 text-left text-xs text-muted-foreground transition-colors",
|
|
82
|
+
"hover:bg-muted hover:text-foreground",
|
|
83
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<span className="shrink-0 text-muted-foreground">
|
|
87
|
+
{isDirectory ? (
|
|
88
|
+
<FolderTypeIcon open={false} />
|
|
89
|
+
) : (
|
|
90
|
+
<FileTypeIcon fileName={artifact.name} />
|
|
91
|
+
)}
|
|
92
|
+
</span>
|
|
93
|
+
<span className="min-w-0 flex-1 truncate text-foreground">
|
|
94
|
+
{artifact.name}
|
|
95
|
+
{isDirectory && "/"}
|
|
96
|
+
{parentDir && (
|
|
97
|
+
<span className="ml-1.5 text-[0.65rem] text-muted-foreground">
|
|
98
|
+
{parentDir}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</span>
|
|
102
|
+
<span className="shrink-0 tabular-nums text-[0.65rem] text-muted-foreground-faint">
|
|
103
|
+
{formatArtifactSize(artifact.sizeBytes)}
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
onClick={() => download(artifact.storageKey, artifact.name)}
|
|
109
|
+
disabled={isDownloading}
|
|
110
|
+
aria-label={
|
|
111
|
+
isDownloading
|
|
112
|
+
? `Preparing ${artifact.name}`
|
|
113
|
+
: `Download ${artifact.name}`
|
|
114
|
+
}
|
|
115
|
+
title={isDirectory ? "Download ZIP" : "Download"}
|
|
116
|
+
className={cn(
|
|
117
|
+
"flex shrink-0 items-center px-2 text-muted-foreground opacity-0 transition-opacity",
|
|
118
|
+
"group-hover:opacity-100 focus-visible:opacity-100",
|
|
119
|
+
"hover:text-foreground disabled:opacity-50",
|
|
120
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
<DownloadIcon />
|
|
124
|
+
</button>
|
|
125
|
+
</li>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Helpers
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Extracts a human-readable parent directory label from a sandbox path.
|
|
135
|
+
* Given `/workspace/configs/agent.yaml` returns `configs/`.
|
|
136
|
+
* Returns `null` when the path has no meaningful parent segment.
|
|
137
|
+
*/
|
|
138
|
+
function parentDirectory(sandboxPath: string): string | null {
|
|
139
|
+
const lastSlash = sandboxPath.lastIndexOf("/");
|
|
140
|
+
if (lastSlash <= 0) return null;
|
|
141
|
+
const parent = sandboxPath.slice(0, lastSlash);
|
|
142
|
+
const segment = parent.slice(parent.lastIndexOf("/") + 1);
|
|
143
|
+
return segment ? `${segment}/` : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function DownloadIcon() {
|
|
147
|
+
return (
|
|
148
|
+
<svg
|
|
149
|
+
width="12"
|
|
150
|
+
height="12"
|
|
151
|
+
viewBox="0 0 12 12"
|
|
152
|
+
fill="none"
|
|
153
|
+
stroke="currentColor"
|
|
154
|
+
strokeWidth="1.5"
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
className="shrink-0"
|
|
158
|
+
aria-hidden="true"
|
|
159
|
+
>
|
|
160
|
+
<path d="M6 1.5V8.5" />
|
|
161
|
+
<path d="M3 6L6 9L9 6" />
|
|
162
|
+
<path d="M2 10.5H10" />
|
|
163
|
+
</svg>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -5,9 +5,10 @@ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexe
|
|
|
5
5
|
import { cn } from "@stigmer/theme";
|
|
6
6
|
import {
|
|
7
7
|
useSessionArtifacts,
|
|
8
|
+
artifactKey,
|
|
8
9
|
type SessionArtifactEntry,
|
|
9
10
|
} from "../session/useSessionArtifacts.js";
|
|
10
|
-
import {
|
|
11
|
+
import { ArtifactRow } from "./ArtifactRow.js";
|
|
11
12
|
import { ArtifactPreviewModal } from "./ArtifactPreviewModal.js";
|
|
12
13
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
13
14
|
|
|
@@ -45,19 +46,16 @@ export interface ArtifactsWidgetProps {
|
|
|
45
46
|
* a file-explorer-like view of the conversation's output — no
|
|
46
47
|
* execution/turn concepts are exposed.
|
|
47
48
|
*
|
|
48
|
-
*
|
|
49
|
-
* {@link
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* The panel-less counterpart of the session panel's Artifacts facet: it
|
|
50
|
+
* renders the same dense {@link ArtifactRow} list, but — having no editor pane
|
|
51
|
+
* to open document tabs into — a click opens the {@link ArtifactPreviewModal}
|
|
52
|
+
* (the review-before-apply popup, and the sole home of the Apply/Push CTA).
|
|
53
|
+
* This is the embeddable path a platform builder drops into their own sidebar.
|
|
52
54
|
*
|
|
53
55
|
* Returns `null` when the executions list is empty or no execution
|
|
54
56
|
* has artifacts, matching the conditional-render pattern of
|
|
55
57
|
* {@link ExecutionProgress} and {@link UsageWidget}.
|
|
56
58
|
*
|
|
57
|
-
* Renders without card chrome — each {@link ArtifactCard} provides its
|
|
58
|
-
* own border and padding. The consumer controls the container styling
|
|
59
|
-
* (or places the widget directly in a flex/grid layout).
|
|
60
|
-
*
|
|
61
59
|
* All visual properties flow through `--stgm-*` tokens.
|
|
62
60
|
*
|
|
63
61
|
* @example
|
|
@@ -74,7 +72,7 @@ export interface ArtifactsWidgetProps {
|
|
|
74
72
|
* />
|
|
75
73
|
* ```
|
|
76
74
|
*
|
|
77
|
-
* @see {@link
|
|
75
|
+
* @see {@link ArtifactRow} — the dense row rendered per artifact
|
|
78
76
|
* @see {@link ArtifactPreviewModal} — full preview with Apply/Push CTA
|
|
79
77
|
* @see {@link useSessionArtifacts} — headless session-level artifact aggregation hook
|
|
80
78
|
* @see {@link useExecutionArtifacts} — headless single-execution artifact extraction hook
|
|
@@ -88,27 +86,22 @@ export function ArtifactsWidget({
|
|
|
88
86
|
const { artifacts, hasArtifacts, artifactCount } =
|
|
89
87
|
useSessionArtifacts(executions);
|
|
90
88
|
|
|
91
|
-
// Store the
|
|
92
|
-
// the
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
// updated artifact for the same path while the modal is open.
|
|
89
|
+
// Store the artifact identity (not a snapshot of the full entry). The entry is
|
|
90
|
+
// re-derived from the live artifacts list on every render, so the modal always
|
|
91
|
+
// reflects the latest artifact version — even when a newer execution publishes
|
|
92
|
+
// an updated artifact for the same path while the modal is open.
|
|
96
93
|
const [previewKey, setPreviewKey] = useState<string | null>(null);
|
|
97
94
|
|
|
98
95
|
const previewEntry = useMemo<SessionArtifactEntry | null>(
|
|
99
96
|
() =>
|
|
100
97
|
previewKey !== null
|
|
101
|
-
? artifacts.find(
|
|
102
|
-
(e) =>
|
|
103
|
-
(e.artifact.sandboxPath || e.artifact.name) === previewKey,
|
|
104
|
-
) ?? null
|
|
98
|
+
? artifacts.find((e) => artifactKey(e.artifact) === previewKey) ?? null
|
|
105
99
|
: null,
|
|
106
100
|
[previewKey, artifacts],
|
|
107
101
|
);
|
|
108
102
|
|
|
109
|
-
const
|
|
110
|
-
(entry: SessionArtifactEntry) =>
|
|
111
|
-
setPreviewKey(entry.artifact.sandboxPath || entry.artifact.name),
|
|
103
|
+
const handleOpen = useCallback(
|
|
104
|
+
(entry: SessionArtifactEntry) => setPreviewKey(artifactKey(entry.artifact)),
|
|
112
105
|
[],
|
|
113
106
|
);
|
|
114
107
|
|
|
@@ -123,19 +116,17 @@ export function ArtifactsWidget({
|
|
|
123
116
|
</span>
|
|
124
117
|
</div>
|
|
125
118
|
|
|
126
|
-
<
|
|
119
|
+
<ul role="list" className="flex flex-col">
|
|
127
120
|
{artifacts.map((entry) => (
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
121
|
+
<ArtifactRow
|
|
122
|
+
key={artifactKey(entry.artifact)}
|
|
123
|
+
artifact={entry.artifact}
|
|
124
|
+
executionId={entry.executionId}
|
|
125
|
+
hasNameCollision={entry.hasNameCollision}
|
|
126
|
+
onOpen={() => handleOpen(entry)}
|
|
127
|
+
/>
|
|
137
128
|
))}
|
|
138
|
-
</
|
|
129
|
+
</ul>
|
|
139
130
|
|
|
140
131
|
{previewEntry && (
|
|
141
132
|
<ArtifactPreviewModal
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { memo, useState } from "react";
|
|
4
|
+
import type { FileChangeProgress } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
import { FileKindBadge, FileLineStats } from "./FileReviewAtoms.js";
|
|
7
|
+
import { FilePathLink } from "./FilePathLink.js";
|
|
8
|
+
|
|
9
|
+
/** Props for {@link FileChangeProgressBar}. */
|
|
10
|
+
export interface FileChangeProgressBarProps {
|
|
11
|
+
/**
|
|
12
|
+
* The transient mid-run progress snapshot for the active turn (DD-32). Feed
|
|
13
|
+
* this from `useSessionConversation().fileChangeProgress`. Renders nothing when
|
|
14
|
+
* undefined or when no files have changed yet — the server clears it once the
|
|
15
|
+
* turn's change set leaves CAPTURING, so the bar disappears exactly when the
|
|
16
|
+
* decision surface ({@link FileReviewDock}) takes over.
|
|
17
|
+
*/
|
|
18
|
+
readonly progress: FileChangeProgress | undefined;
|
|
19
|
+
/** Additional CSS class names for the root container. */
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The live "N files changed so far" strip for a turn that is still running.
|
|
25
|
+
*
|
|
26
|
+
* WHY IT EXISTS. The transcript streams per-edit tool rows, but a change made by
|
|
27
|
+
* a shell command (a `sed`, a build step) has no tool row, and there is no
|
|
28
|
+
* running net-of-many-edits rollup until the turn ends. This bar fills that gap:
|
|
29
|
+
* a compact, NON-INTERACTIVE count of the workspace delta accumulating during
|
|
30
|
+
* the turn, pinned above the composer where the {@link FileReviewDock} will later
|
|
31
|
+
* appear. The two are mutually exclusive per turn (progress shows while CAPTURING;
|
|
32
|
+
* the dock shows once AWAITING_REVIEW), so the bar hands off cleanly to the dock.
|
|
33
|
+
*
|
|
34
|
+
* NEVER A DECISION SURFACE. Progress is non-authoritative and carries no file
|
|
35
|
+
* bodies — it is never reviewable or decidable. The reviewed diff is the
|
|
36
|
+
* turn-boundary candidate ({@link FileReviewDock} / the stamped transcript rows).
|
|
37
|
+
* A secret-like path is surfaced by name with its counts withheld (zeroed), so
|
|
38
|
+
* `FileLineStats` renders nothing for it.
|
|
39
|
+
*
|
|
40
|
+
* A11Y. Only the summary count is an `aria-live` region — the per-file list is
|
|
41
|
+
* not — so a screen reader hears "3 files changing" as the count moves, not a
|
|
42
|
+
* per-file barrage on every ~2s refresh.
|
|
43
|
+
*
|
|
44
|
+
* Purely presentational (headless-first, DD-003): the data lives in
|
|
45
|
+
* `useSessionConversation().fileChangeProgress`. `SessionViewer` mounts it above
|
|
46
|
+
* the dock by default.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const conv = useSessionConversation(sessionId);
|
|
51
|
+
* <FileChangeProgressBar progress={conv.fileChangeProgress} />
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const FileChangeProgressBar = memo(function FileChangeProgressBar({
|
|
55
|
+
progress,
|
|
56
|
+
className,
|
|
57
|
+
}: FileChangeProgressBarProps) {
|
|
58
|
+
const [expanded, setExpanded] = useState(false);
|
|
59
|
+
|
|
60
|
+
if (!progress || progress.filesChanged === 0) return null;
|
|
61
|
+
|
|
62
|
+
const { filesChanged, linesAdded, linesRemoved, entries } = progress;
|
|
63
|
+
const hiddenCount = filesChanged - entries.length;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
role="region"
|
|
68
|
+
aria-label="File changes in progress"
|
|
69
|
+
data-cursor-target="file-change-progress-bar"
|
|
70
|
+
className={cn("border-t border-border-muted px-4 py-2", className)}
|
|
71
|
+
>
|
|
72
|
+
<div className="flex items-center gap-2">
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
onClick={() => setExpanded((prev) => !prev)}
|
|
76
|
+
aria-expanded={expanded}
|
|
77
|
+
className="flex min-w-0 flex-1 items-center gap-2 text-left text-xs text-muted-foreground hover:text-foreground"
|
|
78
|
+
>
|
|
79
|
+
<span
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
className={cn("shrink-0 transition-transform", expanded && "rotate-90")}
|
|
82
|
+
>
|
|
83
|
+
▸
|
|
84
|
+
</span>
|
|
85
|
+
{/* Only the count is live: a screen reader hears the aggregate move,
|
|
86
|
+
not a per-file announcement on every refresh. */}
|
|
87
|
+
<span aria-live="polite" className="min-w-0 truncate">
|
|
88
|
+
{filesChanged} {filesChanged === 1 ? "file" : "files"} changing…
|
|
89
|
+
</span>
|
|
90
|
+
</button>
|
|
91
|
+
<FileLineStats linesAdded={linesAdded} linesRemoved={linesRemoved} />
|
|
92
|
+
</div>
|
|
93
|
+
{expanded && (
|
|
94
|
+
<ul className="mt-2 max-h-[30vh] space-y-1 overflow-y-auto">
|
|
95
|
+
{entries.map((entry) => {
|
|
96
|
+
const path = entry.pathAfter || entry.pathBefore;
|
|
97
|
+
return (
|
|
98
|
+
<li key={path} className="flex items-center gap-2">
|
|
99
|
+
<FileKindBadge kind={entry.kind} />
|
|
100
|
+
<FilePathLink
|
|
101
|
+
path={path}
|
|
102
|
+
dirDisplay="dim"
|
|
103
|
+
className="min-w-0 flex-1 text-xs"
|
|
104
|
+
/>
|
|
105
|
+
<FileLineStats
|
|
106
|
+
linesAdded={entry.linesAdded}
|
|
107
|
+
linesRemoved={entry.linesRemoved}
|
|
108
|
+
/>
|
|
109
|
+
</li>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
{hiddenCount > 0 && (
|
|
113
|
+
<li className="text-xs text-muted-foreground-faint">
|
|
114
|
+
… and {hiddenCount} more
|
|
115
|
+
</li>
|
|
116
|
+
)}
|
|
117
|
+
</ul>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
});
|
|
@@ -167,7 +167,13 @@ export function FileChangeDiff({
|
|
|
167
167
|
if (change.captureLevel === FileChangeCaptureLevel.HUNK_ONLY) {
|
|
168
168
|
return { additions: change.linesAdded, deletions: change.linesRemoved };
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
// Whole-file: count the rendered hunks (what the user actually sees). When
|
|
171
|
+
// there are none to count — the body is offloaded/loading/truncated — fall
|
|
172
|
+
// back to the capture-time counts, which are computed with the same diff
|
|
173
|
+
// algorithm and so agree with the hunks whenever both exist.
|
|
174
|
+
const computed = countHunks(hunks);
|
|
175
|
+
if (computed.additions > 0 || computed.deletions > 0) return computed;
|
|
176
|
+
return { additions: change.linesAdded, deletions: change.linesRemoved };
|
|
171
177
|
}, [change.captureLevel, change.linesAdded, change.linesRemoved, hunks]);
|
|
172
178
|
|
|
173
179
|
const showStatsRow = showStats && (stats.additions > 0 || stats.deletions > 0);
|
|
@@ -343,8 +349,11 @@ function countHunks(hunks: readonly DiffHunk[]): {
|
|
|
343
349
|
* Projects a {@link FileChange} into a {@link FileDiffEntry} for the file list.
|
|
344
350
|
*
|
|
345
351
|
* Counts: hunk-only uses the runner's authoritative numbers; whole-file inline
|
|
346
|
-
* is derived from the content
|
|
347
|
-
*
|
|
352
|
+
* is derived from the content (identical to the runner's capture-time counts by
|
|
353
|
+
* construction — same diff algorithm). A whole-file side with no synchronous
|
|
354
|
+
* content (offloaded / binary) falls back to the runner's capture-time
|
|
355
|
+
* `linesAdded`/`linesRemoved` — zero when no count exists, which the list
|
|
356
|
+
* renders as no stat.
|
|
348
357
|
*/
|
|
349
358
|
function toFileDiffEntry(change: FileChange): FileDiffEntry {
|
|
350
359
|
const changeType =
|
|
@@ -370,7 +379,12 @@ function toFileDiffEntry(change: FileChange): FileDiffEntry {
|
|
|
370
379
|
return { path: change.path, changeType, additions, deletions };
|
|
371
380
|
}
|
|
372
381
|
|
|
373
|
-
return {
|
|
382
|
+
return {
|
|
383
|
+
path: change.path,
|
|
384
|
+
changeType,
|
|
385
|
+
additions: change.linesAdded,
|
|
386
|
+
deletions: change.linesRemoved,
|
|
387
|
+
};
|
|
374
388
|
}
|
|
375
389
|
|
|
376
390
|
/** Inline text of a side, or null when absent/offloaded/binary. */
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FileChangeKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared presentational atoms for the file-review surfaces — the single source
|
|
8
|
+
* for the `+N −M` stat and the change-kind marker, so the review card, the
|
|
9
|
+
* compact list, and the mid-run progress bar can never drift in how they render
|
|
10
|
+
* a file's magnitude or kind.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The `+N −M` stat for one file (or a whole set), in the same visual vocabulary
|
|
15
|
+
* as the tool-row diff stats. Renders nothing when both counts are zero — the
|
|
16
|
+
* honest signal that no count exists (binary or secret-withheld changes, records
|
|
17
|
+
* captured before counts were stamped) — rather than a misleading `+0 −0`.
|
|
18
|
+
*/
|
|
19
|
+
export function FileLineStats({
|
|
20
|
+
linesAdded,
|
|
21
|
+
linesRemoved,
|
|
22
|
+
}: {
|
|
23
|
+
readonly linesAdded: number;
|
|
24
|
+
readonly linesRemoved: number;
|
|
25
|
+
}) {
|
|
26
|
+
if (linesAdded === 0 && linesRemoved === 0) return null;
|
|
27
|
+
return (
|
|
28
|
+
<span
|
|
29
|
+
className="shrink-0 text-xs tabular-nums"
|
|
30
|
+
aria-label={`${linesAdded} added, ${linesRemoved} removed`}
|
|
31
|
+
data-cursor-target="file-review-line-stats"
|
|
32
|
+
>
|
|
33
|
+
<span className="text-diff-added-fg">+{linesAdded}</span>{" "}
|
|
34
|
+
<span className="text-diff-removed-fg">-{linesRemoved}</span>
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The kind letter, tone, and a11y name for each wire {@link FileChangeKind}. */
|
|
40
|
+
const KIND_BADGE: Partial<
|
|
41
|
+
Record<FileChangeKind, { letter: string; colorClass: string; label: string }>
|
|
42
|
+
> = {
|
|
43
|
+
[FileChangeKind.ADD]: {
|
|
44
|
+
letter: "A",
|
|
45
|
+
colorClass: "text-diff-added-fg",
|
|
46
|
+
label: "added",
|
|
47
|
+
},
|
|
48
|
+
[FileChangeKind.MODIFY]: {
|
|
49
|
+
letter: "M",
|
|
50
|
+
colorClass: "text-diff-hunk-header-fg",
|
|
51
|
+
label: "modified",
|
|
52
|
+
},
|
|
53
|
+
[FileChangeKind.DELETE]: {
|
|
54
|
+
letter: "D",
|
|
55
|
+
colorClass: "text-diff-removed-fg",
|
|
56
|
+
label: "deleted",
|
|
57
|
+
},
|
|
58
|
+
[FileChangeKind.RENAME]: {
|
|
59
|
+
letter: "R",
|
|
60
|
+
colorClass: "text-diff-hunk-header-fg",
|
|
61
|
+
label: "renamed",
|
|
62
|
+
},
|
|
63
|
+
// A binary change is a modification whose diff is not text; the letter stays
|
|
64
|
+
// M and the binary-ness is told by the row's "Keep anyway" + reason note.
|
|
65
|
+
[FileChangeKind.BINARY_CHANGE]: {
|
|
66
|
+
letter: "M",
|
|
67
|
+
colorClass: "text-diff-hunk-header-fg",
|
|
68
|
+
label: "modified (binary)",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The single-letter change-kind marker — the same M/A/D visual vocabulary as
|
|
74
|
+
* the version-history `DiffFileList`, extended with R for captured renames.
|
|
75
|
+
* The letter itself is the non-color channel; the full word is exposed to
|
|
76
|
+
* assistive tech. An UNSPECIFIED kind renders nothing rather than guessing.
|
|
77
|
+
*/
|
|
78
|
+
export function FileKindBadge({ kind }: { kind: FileChangeKind }) {
|
|
79
|
+
const badge = KIND_BADGE[kind];
|
|
80
|
+
if (!badge) return null;
|
|
81
|
+
return (
|
|
82
|
+
<span
|
|
83
|
+
className={cn("shrink-0 font-mono text-[10px] font-bold", badge.colorClass)}
|
|
84
|
+
aria-label={badge.label}
|
|
85
|
+
>
|
|
86
|
+
{badge.letter}
|
|
87
|
+
</span>
|
|
88
|
+
);
|
|
89
|
+
}
|