@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
|
@@ -117,6 +117,15 @@ export interface SendFollowUpOptions {
|
|
|
117
117
|
* @see {@link CreateAgentExecutionInput.workspaceFileRefs}
|
|
118
118
|
*/
|
|
119
119
|
readonly workspaceFileRefs?: string[];
|
|
120
|
+
/**
|
|
121
|
+
* ID of the execution this follow-up supersedes (edit-and-resubmit).
|
|
122
|
+
*
|
|
123
|
+
* The conversation read model hides the superseded execution so the
|
|
124
|
+
* edited message replaces the original in place.
|
|
125
|
+
*
|
|
126
|
+
* @see {@link CreateAgentExecutionInput.supersedesExecutionId}
|
|
127
|
+
*/
|
|
128
|
+
readonly supersedesExecutionId?: string;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
/**
|
|
@@ -129,7 +138,14 @@ export interface SendFollowUpOptions {
|
|
|
129
138
|
export interface UseSessionConversationReturn {
|
|
130
139
|
/** The session object, or null while loading. */
|
|
131
140
|
readonly session: Session | null;
|
|
132
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Executions in terminal phases, in chronological order.
|
|
143
|
+
*
|
|
144
|
+
* Excludes turns replaced via edit-and-resubmit (an execution whose id
|
|
145
|
+
* appears as another execution's `supersedesExecutionId`) — the edited
|
|
146
|
+
* message stands in the original's place. Superseded records remain in
|
|
147
|
+
* execution history surfaces; only the conversation view hides them.
|
|
148
|
+
*/
|
|
133
149
|
readonly completedExecutions: readonly AgentExecution[];
|
|
134
150
|
/** Currently streaming execution (stream or fetch fallback), or null. */
|
|
135
151
|
readonly activeStreamExecution: AgentExecution | null;
|
|
@@ -491,12 +507,37 @@ export function useSessionConversation(
|
|
|
491
507
|
}
|
|
492
508
|
}, [activeExecutionId, stream.phase, refetch]);
|
|
493
509
|
|
|
510
|
+
// Executions replaced via edit-and-resubmit. The successor carries
|
|
511
|
+
// `spec.supersedes_execution_id`; hiding the superseded turn makes the
|
|
512
|
+
// edited message read as a single corrected exchange (in-place replace).
|
|
513
|
+
// The link must also be read off the live stream copy — right after a
|
|
514
|
+
// resubmit, the successor streams before the list refetch delivers it.
|
|
515
|
+
// Display-only: the raw `executions` list (and therefore active-id
|
|
516
|
+
// resolution above) is never filtered.
|
|
517
|
+
//
|
|
518
|
+
// Dep is the scalar link, NOT stream.execution: the stream object changes
|
|
519
|
+
// reference every frame, and rebuilding the Set per frame would hand
|
|
520
|
+
// MessageThread a fresh executions array mid-stream, defeating its
|
|
521
|
+
// memoization (DD-010).
|
|
522
|
+
const streamSupersededId =
|
|
523
|
+
stream.execution?.spec?.supersedesExecutionId || null;
|
|
524
|
+
const supersededIds = useMemo(() => {
|
|
525
|
+
const ids = new Set<string>();
|
|
526
|
+
for (const e of executions) {
|
|
527
|
+
const superseded = e.spec?.supersedesExecutionId;
|
|
528
|
+
if (superseded) ids.add(superseded);
|
|
529
|
+
}
|
|
530
|
+
if (streamSupersededId) ids.add(streamSupersededId);
|
|
531
|
+
return ids;
|
|
532
|
+
}, [executions, streamSupersededId]);
|
|
533
|
+
|
|
494
534
|
const completedExecutions = useMemo(() => {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
(
|
|
498
|
-
|
|
499
|
-
|
|
535
|
+
return executions.filter((e) => {
|
|
536
|
+
const id = e.metadata?.id ?? "";
|
|
537
|
+
if (activeExecutionId && id === activeExecutionId) return false;
|
|
538
|
+
return !supersededIds.has(id);
|
|
539
|
+
});
|
|
540
|
+
}, [executions, activeExecutionId, supersededIds]);
|
|
500
541
|
|
|
501
542
|
const fetchedActiveExecution = useMemo(() => {
|
|
502
543
|
if (!activeExecutionId) return null;
|
|
@@ -604,6 +645,7 @@ export function useSessionConversation(
|
|
|
604
645
|
buildFromPlan: options?.buildFromPlan,
|
|
605
646
|
autoApproveAll: options?.autoApproveAll,
|
|
606
647
|
workspaceFileRefs: options?.workspaceFileRefs,
|
|
648
|
+
supersedesExecutionId: options?.supersedesExecutionId,
|
|
607
649
|
});
|
|
608
650
|
setPendingExecutionId(result.executionId);
|
|
609
651
|
refetch();
|
|
@@ -410,6 +410,7 @@ export function useSessionPageFlow(
|
|
|
410
410
|
// not from the composer (the pre-arm toggle was removed).
|
|
411
411
|
autoApproveAll: autoApproveAll || undefined,
|
|
412
412
|
workspaceFileRefs: context?.workspaceFileRefs,
|
|
413
|
+
supersedesExecutionId: context?.supersedesExecutionId,
|
|
413
414
|
});
|
|
414
415
|
|
|
415
416
|
sessionVariables.clear();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import type { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
5
6
|
import { isTerminalPhase } from "../execution/execution-phases.js";
|
|
6
7
|
import type { SelectedThreadItem } from "../internal/store/selection-store.js";
|
|
7
8
|
import {
|
|
@@ -10,6 +11,8 @@ import {
|
|
|
10
11
|
type WorkspaceEditorsStore,
|
|
11
12
|
} from "../internal/store/index.js";
|
|
12
13
|
import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
|
|
14
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
|
|
15
|
+
import { artifactKey } from "./useSessionArtifacts.js";
|
|
13
16
|
|
|
14
17
|
/** Options for {@link useSessionPanel}. */
|
|
15
18
|
export interface UseSessionPanelOptions {
|
|
@@ -118,6 +121,25 @@ export interface SessionPanelController {
|
|
|
118
121
|
* both when a plan STARTS STREAMING and when it publishes (see `planKey`).
|
|
119
122
|
*/
|
|
120
123
|
readonly openPlanDocument: () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Open (or focus) an artifact as an editor-pane document tab and expand the
|
|
126
|
+
* panel. Uses the PREVIEW slot — like files and content-search hits, casual
|
|
127
|
+
* artifact browsing reuses one tab (single-click), and double-clicking the
|
|
128
|
+
* tab pins it. Distinct from `openPlanDocument`, which pins its single tab: a
|
|
129
|
+
* plan is the turn's deliverable, artifacts are browsable outputs. The
|
|
130
|
+
* artifact's {@link artifactKey} is the tab identity, shared with the
|
|
131
|
+
* `SurfaceVirtualDocument` the viewer builds for the open tab.
|
|
132
|
+
*/
|
|
133
|
+
readonly openArtifact: (artifact: ExecutionArtifact) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Pin an artifact's tab (clear its preview state) — the double-click half of
|
|
136
|
+
* the open/activate split, the encapsulated sibling of {@link openArtifact}.
|
|
137
|
+
* Delegates to the editors store's generic `pin`, exactly as the file tree
|
|
138
|
+
* pins via `pinEditor`: the leading single-click of the double-click has
|
|
139
|
+
* already opened the preview tab, so this promotes it to a persistent tab. A
|
|
140
|
+
* no-op if the artifact is not open.
|
|
141
|
+
*/
|
|
142
|
+
readonly pinArtifact: (artifact: ExecutionArtifact) => void;
|
|
121
143
|
/**
|
|
122
144
|
* Report the current thread-item selection. Called from an effect inside the
|
|
123
145
|
* panel subtree (the level that subscribes to the selection store), keeping
|
|
@@ -224,6 +246,21 @@ export function useSessionPanel({
|
|
|
224
246
|
[editorsStore],
|
|
225
247
|
);
|
|
226
248
|
|
|
249
|
+
const openArtifact = useCallback(
|
|
250
|
+
(artifact: ExecutionArtifact) => {
|
|
251
|
+
editorsStore.openPreview(ARTIFACT_DOCUMENT_ENTRY_ID, artifactKey(artifact));
|
|
252
|
+
setIsOpen(true);
|
|
253
|
+
},
|
|
254
|
+
[editorsStore],
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const pinArtifact = useCallback(
|
|
258
|
+
(artifact: ExecutionArtifact) => {
|
|
259
|
+
editorsStore.pin(ARTIFACT_DOCUMENT_ENTRY_ID, artifactKey(artifact));
|
|
260
|
+
},
|
|
261
|
+
[editorsStore],
|
|
262
|
+
);
|
|
263
|
+
|
|
227
264
|
const activateEditor = useCallback(
|
|
228
265
|
(entryId: string, path: string) => editorsStore.activate(entryId, path),
|
|
229
266
|
[editorsStore],
|
|
@@ -268,6 +305,8 @@ export function useSessionPanel({
|
|
|
268
305
|
pinEditor,
|
|
269
306
|
closeEditor,
|
|
270
307
|
openPlanDocument,
|
|
308
|
+
openArtifact,
|
|
309
|
+
pinArtifact,
|
|
271
310
|
notifySelection,
|
|
272
311
|
}),
|
|
273
312
|
[
|
|
@@ -282,6 +321,8 @@ export function useSessionPanel({
|
|
|
282
321
|
pinEditor,
|
|
283
322
|
closeEditor,
|
|
284
323
|
openPlanDocument,
|
|
324
|
+
openArtifact,
|
|
325
|
+
pinArtifact,
|
|
285
326
|
notifySelection,
|
|
286
327
|
],
|
|
287
328
|
);
|
|
@@ -6,7 +6,10 @@ import type { SurfaceRailView } from "../workspace/WorkspaceSurface.js";
|
|
|
6
6
|
import type { SelectedThreadItem } from "../internal/store/selection-store.js";
|
|
7
7
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
8
8
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useSessionArtifacts,
|
|
11
|
+
type SessionArtifactEntry,
|
|
12
|
+
} from "./useSessionArtifacts.js";
|
|
10
13
|
import { SetupTab, type SetupTabProps } from "./facets/SetupTab.js";
|
|
11
14
|
import { ChangesTab } from "./facets/ChangesTab.js";
|
|
12
15
|
import { ArtifactsTab } from "./facets/ArtifactsTab.js";
|
|
@@ -33,6 +36,17 @@ export interface UseSessionRailViewsOptions {
|
|
|
33
36
|
* of the preview modal.
|
|
34
37
|
*/
|
|
35
38
|
readonly onOpenPlan?: (executionId: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Open a (non-plan) artifact as an editor-pane document tab. Routed to the
|
|
41
|
+
* Artifacts facet so clicking an artifact opens it in the editor area (VS
|
|
42
|
+
* Code "each file is a tab") instead of the preview modal.
|
|
43
|
+
*/
|
|
44
|
+
readonly onOpenArtifact?: (entry: SessionArtifactEntry) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Pin a (non-plan) artifact's document tab — the double-click half of the
|
|
47
|
+
* Artifacts facet's open/activate split. Routed through to the facet's rows.
|
|
48
|
+
*/
|
|
49
|
+
readonly onActivateArtifact?: (entry: SessionArtifactEntry) => void;
|
|
36
50
|
/**
|
|
37
51
|
* Whether to offer the execution-derived facets (Changes / Artifacts /
|
|
38
52
|
* Usage). The launcher passes `false` — before a session exists there are no
|
|
@@ -60,6 +74,8 @@ export function useSessionRailViews({
|
|
|
60
74
|
onApplied,
|
|
61
75
|
onImplementPlan,
|
|
62
76
|
onOpenPlan,
|
|
77
|
+
onOpenArtifact,
|
|
78
|
+
onActivateArtifact,
|
|
63
79
|
includeExecutionFacets = true,
|
|
64
80
|
}: UseSessionRailViewsOptions): readonly SurfaceRailView[] {
|
|
65
81
|
const { hasWriteBacks, writeBackCount } = useSessionWriteBacks(allExecutions);
|
|
@@ -101,6 +117,8 @@ export function useSessionRailViews({
|
|
|
101
117
|
onApplied={onApplied}
|
|
102
118
|
onImplementPlan={onImplementPlan}
|
|
103
119
|
onOpenPlan={onOpenPlan}
|
|
120
|
+
onOpenArtifact={onOpenArtifact}
|
|
121
|
+
onActivateArtifact={onActivateArtifact}
|
|
104
122
|
/>
|
|
105
123
|
),
|
|
106
124
|
});
|
|
@@ -136,6 +154,8 @@ export function useSessionRailViews({
|
|
|
136
154
|
onApplied,
|
|
137
155
|
onImplementPlan,
|
|
138
156
|
onOpenPlan,
|
|
157
|
+
onOpenArtifact,
|
|
158
|
+
onActivateArtifact,
|
|
139
159
|
selectedItem,
|
|
140
160
|
]);
|
|
141
161
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
5
|
+
import type { WorkspaceEntry } from "../workspace/useWorkspaceEntries.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pure derivation hook that projects the session's workspace write-backs onto
|
|
9
|
+
* its workspace entries as per-entry read refs.
|
|
10
|
+
*
|
|
11
|
+
* The agent's file changes land on a write-back branch (`stigmer/<id>`), not
|
|
12
|
+
* the branch the session was configured with — so read-side surfaces (file
|
|
13
|
+
* viewer, tree listing, name search) pointed at the configured branch would
|
|
14
|
+
* 404 on agent-created files and silently show stale content for modified
|
|
15
|
+
* ones. This hook returns the entries with `readRef` set to the latest
|
|
16
|
+
* write-back commit SHA, which those surfaces resolve in preference to
|
|
17
|
+
* `gitBranch`. A commit SHA (not the branch name) so reads are immutable-ref
|
|
18
|
+
* exact, immune to branch eventual-consistency.
|
|
19
|
+
*
|
|
20
|
+
* **Ref selection:** the latest write-back with a non-empty `commitSha` wins
|
|
21
|
+
* per workspace entry, across all executions in chronological order. FAILED
|
|
22
|
+
* write-back records carry no SHA and never regress the ref.
|
|
23
|
+
*
|
|
24
|
+
* **Entry matching** mirrors the runner's `WriteBackCoordinator.resolveEntry`:
|
|
25
|
+
* single-entry sessions provision with an empty `workspaceEntryName`, so when
|
|
26
|
+
* the session has exactly one git entry every write-back applies to it;
|
|
27
|
+
* multi-entry sessions match strictly by name.
|
|
28
|
+
*
|
|
29
|
+
* **Reference stability (DD-010):** the decorated array is memoized on the
|
|
30
|
+
* entries array and a value signature of the derived refs — streaming frames
|
|
31
|
+
* that don't change any write-back SHA return the identical array, so
|
|
32
|
+
* downstream listing/content effects never churn.
|
|
33
|
+
*
|
|
34
|
+
* @param executions - All executions for the session, in chronological order
|
|
35
|
+
* (same input contract as {@link useSessionWriteBacks}).
|
|
36
|
+
* @param entries - The session's workspace entries. Returned as-is (same
|
|
37
|
+
* reference) when no entry derives a ref.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const surfaceEntries = useWorkspaceReadRefs(flow.allExecutions, flow.workspace.entries);
|
|
42
|
+
* <WorkspaceSurface entries={surfaceEntries} ... />
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @see useSessionWriteBacks — the write-back list this projection derives from
|
|
46
|
+
* @see WorkspaceEntry.readRef — the field this hook populates
|
|
47
|
+
*/
|
|
48
|
+
export function useWorkspaceReadRefs(
|
|
49
|
+
executions: readonly AgentExecution[],
|
|
50
|
+
entries: readonly WorkspaceEntry[],
|
|
51
|
+
): readonly WorkspaceEntry[] {
|
|
52
|
+
// Cheap per-render pass; memoizing on `executions` would defeat the point —
|
|
53
|
+
// its identity changes every streaming frame while the SHAs rarely do. The
|
|
54
|
+
// signature collapses those frames into a stable memo key.
|
|
55
|
+
const refByName = collectLatestShas(executions);
|
|
56
|
+
const signature = [...refByName]
|
|
57
|
+
.map(([name, sha]) => `${name}\u0001${sha}`)
|
|
58
|
+
.join("\u0000");
|
|
59
|
+
|
|
60
|
+
return useMemo(() => {
|
|
61
|
+
if (refByName.size === 0) return entries;
|
|
62
|
+
|
|
63
|
+
const gitEntries = entries.filter((entry) => entry.type === "git");
|
|
64
|
+
let decorated = false;
|
|
65
|
+
|
|
66
|
+
const next = entries.map((entry) => {
|
|
67
|
+
const readRef = resolveReadRef(entry, gitEntries.length, refByName);
|
|
68
|
+
if (!readRef || readRef === entry.readRef) return entry;
|
|
69
|
+
decorated = true;
|
|
70
|
+
return { ...entry, readRef };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return decorated ? next : entries;
|
|
74
|
+
// `signature` is the value-equality key for `refByName` (rebuilt each
|
|
75
|
+
// render); the map itself must stay out of the deps.
|
|
76
|
+
}, [entries, signature]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Latest non-empty write-back commit SHA per workspace entry name. */
|
|
80
|
+
function collectLatestShas(
|
|
81
|
+
executions: readonly AgentExecution[],
|
|
82
|
+
): Map<string, string> {
|
|
83
|
+
const refByName = new Map<string, string>();
|
|
84
|
+
for (const execution of executions) {
|
|
85
|
+
for (const wb of execution.status?.workspaceWriteBacks ?? []) {
|
|
86
|
+
if (wb.commitSha) refByName.set(wb.workspaceEntryName, wb.commitSha);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return refByName;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The write-back SHA for one entry, honoring the runner's single-entry
|
|
94
|
+
* convention: with exactly one git entry, any write-back applies to it
|
|
95
|
+
* (single-entry sessions write back under an empty entry name).
|
|
96
|
+
*/
|
|
97
|
+
function resolveReadRef(
|
|
98
|
+
entry: WorkspaceEntry,
|
|
99
|
+
gitEntryCount: number,
|
|
100
|
+
refByName: ReadonlyMap<string, string>,
|
|
101
|
+
): string | undefined {
|
|
102
|
+
if (entry.type !== "git") return undefined;
|
|
103
|
+
|
|
104
|
+
const byName = refByName.get(entry.name);
|
|
105
|
+
if (byName) return byName;
|
|
106
|
+
|
|
107
|
+
if (gitEntryCount === 1) {
|
|
108
|
+
// Single-entry sessions write back under an empty entry name; a lone
|
|
109
|
+
// unambiguous name is equally safe to claim. Anything else (multiple
|
|
110
|
+
// names against one entry) is malformed status data — decorate nothing
|
|
111
|
+
// rather than guess.
|
|
112
|
+
const unnamed = refByName.get("");
|
|
113
|
+
if (unnamed) return unnamed;
|
|
114
|
+
if (refByName.size === 1) return refByName.values().next().value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
@@ -11,14 +11,21 @@ import {
|
|
|
11
11
|
} from "react";
|
|
12
12
|
import { cn } from "@stigmer/theme";
|
|
13
13
|
import type { FileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
FileChangeCaptureLevel,
|
|
16
|
+
FileChangeType,
|
|
17
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
15
18
|
import { ArtifactContentRenderer } from "../execution/ArtifactContentRenderer.js";
|
|
16
19
|
import { FileChangeDiff } from "../execution/FileChangesView.js";
|
|
20
|
+
import { useFileChangeContent } from "../execution/useFileChangeContent.js";
|
|
17
21
|
import type { RevealTarget } from "../internal/useRevealLine.js";
|
|
18
22
|
import type { SelectedWorkspaceFile } from "../internal/store/workspace-file-selection-store.js";
|
|
19
23
|
import { useWorkspaceFileContent } from "./useWorkspaceFileContent.js";
|
|
20
24
|
import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
|
|
21
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
WorkspaceFileNotFoundError,
|
|
27
|
+
type WorkspaceFileReader,
|
|
28
|
+
} from "./WorkspaceFileReader.js";
|
|
22
29
|
|
|
23
30
|
/** Line-skeleton widths, mirroring `FileContentStateView` in `ArtifactPreviewModal`. */
|
|
24
31
|
const SKELETON_LINE_WIDTHS = [85, 72, 90, 65, 78, 88, 70, 82] as const;
|
|
@@ -218,22 +225,16 @@ export const FileViewer = forwardRef<FileViewerHandle, FileViewerProps>(
|
|
|
218
225
|
<FileChangeDiff change={change} showFileName={false} showStats />
|
|
219
226
|
</div>
|
|
220
227
|
) : (
|
|
221
|
-
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
isUnsupported={isUnsupported}
|
|
232
|
-
error={error}
|
|
233
|
-
onRetry={refetch}
|
|
234
|
-
reveal={reveal}
|
|
235
|
-
/>
|
|
236
|
-
</>
|
|
228
|
+
<FileViewerBody
|
|
229
|
+
basename={basename}
|
|
230
|
+
content={content}
|
|
231
|
+
isLoading={isLoading}
|
|
232
|
+
isUnsupported={isUnsupported}
|
|
233
|
+
error={error}
|
|
234
|
+
onRetry={refetch}
|
|
235
|
+
reveal={reveal}
|
|
236
|
+
change={change}
|
|
237
|
+
/>
|
|
237
238
|
)}
|
|
238
239
|
</div>
|
|
239
240
|
</div>
|
|
@@ -328,6 +329,23 @@ function ViewerModeToggle({
|
|
|
328
329
|
// Body — exactly one state, mirroring FileContentStateView + workspace extras
|
|
329
330
|
// ---------------------------------------------------------------------------
|
|
330
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Whether the session captured this change's full after-content — the
|
|
334
|
+
* precondition for serving it when the live substrate can't (a cloud file not
|
|
335
|
+
* yet pushed to any readable ref). A DELETE has no after side, and a
|
|
336
|
+
* HUNK_ONLY capture carries only the diff, never the whole file.
|
|
337
|
+
*/
|
|
338
|
+
function hasCapturedAfterContent(
|
|
339
|
+
change: FileChange | undefined,
|
|
340
|
+
): change is FileChange {
|
|
341
|
+
return (
|
|
342
|
+
change !== undefined &&
|
|
343
|
+
change.changeType !== FileChangeType.DELETE &&
|
|
344
|
+
change.captureLevel !== FileChangeCaptureLevel.HUNK_ONLY &&
|
|
345
|
+
change.after !== undefined
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
331
349
|
function FileViewerBody({
|
|
332
350
|
basename,
|
|
333
351
|
content,
|
|
@@ -336,6 +354,7 @@ function FileViewerBody({
|
|
|
336
354
|
error,
|
|
337
355
|
onRetry,
|
|
338
356
|
reveal,
|
|
357
|
+
change,
|
|
339
358
|
}: {
|
|
340
359
|
readonly basename: string;
|
|
341
360
|
readonly content: ReturnType<typeof useWorkspaceFileContent>["content"];
|
|
@@ -344,7 +363,20 @@ function FileViewerBody({
|
|
|
344
363
|
readonly error: Error | null;
|
|
345
364
|
readonly onRetry: () => void;
|
|
346
365
|
readonly reveal?: RevealTarget;
|
|
366
|
+
readonly change?: FileChange;
|
|
347
367
|
}) {
|
|
368
|
+
// A live read that can't be served (unsupported substrate, or the file
|
|
369
|
+
// isn't at the read ref yet) degrades to the session's captured
|
|
370
|
+
// after-content when the change carries it — the same bytes the Diff view
|
|
371
|
+
// renders, so nothing is fabricated. Other errors keep the retry path.
|
|
372
|
+
const isNotFound = error instanceof WorkspaceFileNotFoundError;
|
|
373
|
+
const canFallBackToCaptured =
|
|
374
|
+
(isUnsupported || isNotFound) && hasCapturedAfterContent(change);
|
|
375
|
+
|
|
376
|
+
if (canFallBackToCaptured) {
|
|
377
|
+
return <CapturedFileContent change={change} basename={basename} reveal={reveal} />;
|
|
378
|
+
}
|
|
379
|
+
|
|
348
380
|
// Unsupported comes first: a missing reader means there is nothing to load.
|
|
349
381
|
if (isUnsupported) {
|
|
350
382
|
return (
|
|
@@ -355,21 +387,25 @@ function FileViewerBody({
|
|
|
355
387
|
}
|
|
356
388
|
|
|
357
389
|
if (isLoading) {
|
|
390
|
+
return <LoadingSkeleton />;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (isNotFound) {
|
|
394
|
+
// Expected while the agent's write-back is still syncing to the ref —
|
|
395
|
+
// a calm notice (DD-006), not a failure.
|
|
358
396
|
return (
|
|
359
|
-
<div
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
/>
|
|
372
|
-
))}
|
|
397
|
+
<div className="flex flex-col items-center justify-center gap-2 p-8 text-center">
|
|
398
|
+
<p className="text-xs text-muted-foreground">
|
|
399
|
+
This file isn’t available in the workspace source yet. The
|
|
400
|
+
agent’s latest changes may still be syncing.
|
|
401
|
+
</p>
|
|
402
|
+
<button
|
|
403
|
+
type="button"
|
|
404
|
+
onClick={onRetry}
|
|
405
|
+
className="text-xs font-medium text-foreground underline underline-offset-2 hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm"
|
|
406
|
+
>
|
|
407
|
+
Check again
|
|
408
|
+
</button>
|
|
373
409
|
</div>
|
|
374
410
|
);
|
|
375
411
|
}
|
|
@@ -417,12 +453,82 @@ function FileViewerBody({
|
|
|
417
453
|
}
|
|
418
454
|
|
|
419
455
|
return (
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
456
|
+
<>
|
|
457
|
+
{change && (
|
|
458
|
+
<p className="border-b border-border-muted px-4 py-1.5 text-[0.65rem] text-muted-foreground">
|
|
459
|
+
Live — may differ from the reviewed change.
|
|
460
|
+
</p>
|
|
461
|
+
)}
|
|
462
|
+
<ArtifactContentRenderer
|
|
463
|
+
content={content.text}
|
|
464
|
+
fileName={basename}
|
|
465
|
+
isTruncated={content.truncated}
|
|
466
|
+
reveal={reveal}
|
|
467
|
+
/>
|
|
468
|
+
</>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The captured-content fallback: renders a change's after-side text (the
|
|
474
|
+
* bytes the session already holds — inline or offloaded to artifact storage)
|
|
475
|
+
* when no live substrate can serve the file. Mounted only when eligible, so
|
|
476
|
+
* its content fetch never runs for live-served files.
|
|
477
|
+
*/
|
|
478
|
+
function CapturedFileContent({
|
|
479
|
+
change,
|
|
480
|
+
basename,
|
|
481
|
+
reveal,
|
|
482
|
+
}: {
|
|
483
|
+
readonly change: FileChange;
|
|
484
|
+
readonly basename: string;
|
|
485
|
+
readonly reveal?: RevealTarget;
|
|
486
|
+
}) {
|
|
487
|
+
const { afterText, isBinary, isLoading, error } = useFileChangeContent(change);
|
|
488
|
+
|
|
489
|
+
if (isLoading) {
|
|
490
|
+
return <LoadingSkeleton />;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (isBinary) {
|
|
494
|
+
return <MessageState>Binary file — not shown.</MessageState>;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (error || afterText === null) {
|
|
498
|
+
return <MessageState>Content not available for preview.</MessageState>;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return (
|
|
502
|
+
<>
|
|
503
|
+
<p className="border-b border-border-muted px-4 py-1.5 text-[0.65rem] text-muted-foreground">
|
|
504
|
+
As of the agent’s last change — not yet in the workspace source.
|
|
505
|
+
</p>
|
|
506
|
+
<ArtifactContentRenderer
|
|
507
|
+
content={afterText}
|
|
508
|
+
fileName={basename}
|
|
509
|
+
reveal={reveal}
|
|
510
|
+
/>
|
|
511
|
+
</>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function LoadingSkeleton() {
|
|
516
|
+
return (
|
|
517
|
+
<div
|
|
518
|
+
role="status"
|
|
519
|
+
className="space-y-2 p-4"
|
|
520
|
+
aria-busy="true"
|
|
521
|
+
aria-label="Loading file"
|
|
522
|
+
>
|
|
523
|
+
{SKELETON_LINE_WIDTHS.map((width, i) => (
|
|
524
|
+
<div
|
|
525
|
+
key={i}
|
|
526
|
+
className="h-4 animate-pulse rounded bg-muted"
|
|
527
|
+
style={{ width: `${width}%` }}
|
|
528
|
+
aria-hidden="true"
|
|
529
|
+
/>
|
|
530
|
+
))}
|
|
531
|
+
</div>
|
|
426
532
|
);
|
|
427
533
|
}
|
|
428
534
|
|
|
@@ -32,6 +32,25 @@ export interface WorkspaceFileContent {
|
|
|
32
32
|
readonly truncated?: boolean;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Thrown by a {@link WorkspaceFileReader} when the requested path does not
|
|
37
|
+
* exist at the entry's read ref — as opposed to a transport or permission
|
|
38
|
+
* failure.
|
|
39
|
+
*
|
|
40
|
+
* The distinction matters to consumers: a file the agent just created exists
|
|
41
|
+
* in session state before it exists at any readable ref, so "not found" is a
|
|
42
|
+
* recoverable, expected condition (the viewer falls back to the session's
|
|
43
|
+
* captured content), while a network error or 5xx is a genuine failure worth
|
|
44
|
+
* a retry affordance. Platform builders implementing custom readers should
|
|
45
|
+
* throw this class for their substrate's not-found condition.
|
|
46
|
+
*/
|
|
47
|
+
export class WorkspaceFileNotFoundError extends Error {
|
|
48
|
+
constructor(path: string) {
|
|
49
|
+
super(`"${path}" doesn't exist at the workspace's current ref.`);
|
|
50
|
+
this.name = "WorkspaceFileNotFoundError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
/**
|
|
36
55
|
* Platform-injected callback that reads the content of a single workspace file.
|
|
37
56
|
*
|
|
@@ -46,6 +65,8 @@ export interface WorkspaceFileContent {
|
|
|
46
65
|
* - **Throws** on a genuine failure (404, network error, 5xx, unreadable file,
|
|
47
66
|
* a directory path). Consumers catch this into a distinct error state — a
|
|
48
67
|
* clicked file that 404s is "failed to load," not "unsupported here."
|
|
68
|
+
* A missing path should throw {@link WorkspaceFileNotFoundError} so
|
|
69
|
+
* consumers can distinguish "not there yet" from "broken."
|
|
49
70
|
*
|
|
50
71
|
* `path` is repo-relative for git entries and root-relative for local entries —
|
|
51
72
|
* exactly the strings the matching `WorkspaceFileLister` emits, so a file-tree
|