@stigmer/react 3.1.5 → 3.1.7
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/execution/WriteBackCard.d.ts +7 -1
- package/execution/WriteBackCard.d.ts.map +1 -1
- package/execution/WriteBackCard.js +10 -2
- package/execution/WriteBackCard.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.d.ts.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +7 -3
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +6 -3
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +3 -1
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/OAuthRequiredNotice.d.ts +29 -0
- package/mcp-server/OAuthRequiredNotice.d.ts.map +1 -0
- package/mcp-server/OAuthRequiredNotice.js +29 -0
- package/mcp-server/OAuthRequiredNotice.js.map +1 -0
- package/mcp-server/StdioSandboxNotice.d.ts +43 -0
- package/mcp-server/StdioSandboxNotice.d.ts.map +1 -0
- package/mcp-server/StdioSandboxNotice.js +45 -0
- package/mcp-server/StdioSandboxNotice.js.map +1 -0
- package/mcp-server/useMcpServerCredentials.d.ts +12 -0
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +12 -4
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/package.json +4 -4
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +12 -0
- package/session/SessionViewer.js.map +1 -1
- package/session/facets/ChangesTab.d.ts +24 -6
- package/session/facets/ChangesTab.d.ts.map +1 -1
- package/session/facets/ChangesTab.js +15 -6
- package/session/facets/ChangesTab.js.map +1 -1
- package/session/useSessionRailViews.d.ts +16 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +9 -4
- package/session/useSessionRailViews.js.map +1 -1
- package/src/execution/WriteBackCard.tsx +18 -4
- package/src/execution/__tests__/WriteBackCard.test.tsx +83 -0
- package/src/mcp-server/McpServerConnectDialog.tsx +23 -11
- package/src/mcp-server/McpServerDetailView.tsx +22 -9
- package/src/mcp-server/McpServerPicker.tsx +12 -9
- package/src/mcp-server/OAuthRequiredNotice.tsx +77 -0
- package/src/mcp-server/StdioSandboxNotice.tsx +98 -0
- package/src/mcp-server/__tests__/OAuthRequiredNotice.test.tsx +25 -0
- package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +71 -0
- package/src/mcp-server/useMcpServerCredentials.ts +25 -4
- package/src/session/SessionViewer.tsx +15 -0
- package/src/session/__tests__/useSessionRailViews.test.tsx +12 -0
- package/src/session/facets/ChangesTab.tsx +43 -6
- package/src/session/facets/__tests__/ChangesTab.test.tsx +23 -0
- package/src/session/useSessionRailViews.tsx +31 -3
- package/src/workflow/picker/__tests__/recents.test.ts +4 -4
- package/styles.css +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { McpServerSpec } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
5
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
6
|
+
import { StdioSandboxNotice, isStdioInCloud } from "../StdioSandboxNotice";
|
|
7
|
+
|
|
8
|
+
type McpServerType = McpServerSpec["serverType"];
|
|
9
|
+
|
|
10
|
+
const STDIO = { case: "stdio" } as McpServerType;
|
|
11
|
+
const HTTP = { case: "http" } as McpServerType;
|
|
12
|
+
const UNSET = { case: undefined } as McpServerType;
|
|
13
|
+
|
|
14
|
+
afterEach(cleanup);
|
|
15
|
+
|
|
16
|
+
describe("isStdioInCloud", () => {
|
|
17
|
+
it("is true only for stdio transport in cloud mode", () => {
|
|
18
|
+
expect(isStdioInCloud("cloud", STDIO)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("is false for stdio transport in local mode", () => {
|
|
22
|
+
expect(isStdioInCloud("local", STDIO)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("is false for http transport in cloud mode", () => {
|
|
26
|
+
expect(isStdioInCloud("cloud", HTTP)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("is false when the transport oneof is unset", () => {
|
|
30
|
+
expect(isStdioInCloud("cloud", UNSET)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is false when serverType is undefined", () => {
|
|
34
|
+
expect(isStdioInCloud("cloud", undefined)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function withMode(mode: "cloud" | "local", children: ReactNode) {
|
|
39
|
+
return (
|
|
40
|
+
<DeploymentModeContext.Provider value={mode}>
|
|
41
|
+
{children}
|
|
42
|
+
</DeploymentModeContext.Provider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("StdioSandboxNotice", () => {
|
|
47
|
+
it("renders the sandbox-isolation caveat for stdio in cloud", () => {
|
|
48
|
+
render(withMode("cloud", <StdioSandboxNotice serverType={STDIO} />));
|
|
49
|
+
expect(screen.getByRole("status")).toBeTruthy();
|
|
50
|
+
expect(screen.getByText(/isolated cloud sandbox/i)).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("does not suggest the CLI (avoids inaccurate guidance)", () => {
|
|
54
|
+
render(withMode("cloud", <StdioSandboxNotice serverType={STDIO} />));
|
|
55
|
+
expect(screen.queryByText(/stigmer connect/i)).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("renders nothing for http servers in cloud", () => {
|
|
59
|
+
const { container } = render(
|
|
60
|
+
withMode("cloud", <StdioSandboxNotice serverType={HTTP} />),
|
|
61
|
+
);
|
|
62
|
+
expect(container.firstChild).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("renders nothing for stdio servers in local mode", () => {
|
|
66
|
+
const { container } = render(
|
|
67
|
+
withMode("local", <StdioSandboxNotice serverType={STDIO} />),
|
|
68
|
+
);
|
|
69
|
+
expect(container.firstChild).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -153,6 +153,18 @@ export interface UseMcpServerCredentialsReturn {
|
|
|
153
153
|
* link (when vendor approval is granted).
|
|
154
154
|
*/
|
|
155
155
|
readonly canBringOwnApp: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* `true` when a manually-entered static token is a valid way to
|
|
158
|
+
* authenticate this server. `false` for `oauth_only` servers whose
|
|
159
|
+
* hosted endpoint rejects static tokens (`spec.auth.oauth_only`), where
|
|
160
|
+
* OAuth is the sole credential path.
|
|
161
|
+
*
|
|
162
|
+
* Connect surfaces use this to decide whether to offer the "enter token
|
|
163
|
+
* manually" affordance — offering it on an `oauth_only` server would send
|
|
164
|
+
* the user down a path that cannot succeed. Always `true` for manual-only
|
|
165
|
+
* and PAT-capable servers.
|
|
166
|
+
*/
|
|
167
|
+
readonly manualEntrySupported: boolean;
|
|
156
168
|
/**
|
|
157
169
|
* When `true`, the user has opted to bypass OAuth and enter the
|
|
158
170
|
* `target_env_var` token manually. In this state:
|
|
@@ -251,6 +263,14 @@ export function useMcpServerCredentials(
|
|
|
251
263
|
const oauthTargetEnvVar = auth?.targetEnvVar || null;
|
|
252
264
|
const tokenLifetimeHint = auth?.tokenLifetimeHint || null;
|
|
253
265
|
|
|
266
|
+
// A static token is a valid credential for every server except those whose
|
|
267
|
+
// endpoint declares it rejects them (`oauth_only`). This is the single source
|
|
268
|
+
// of truth the connect surfaces consult before offering manual entry.
|
|
269
|
+
const manualEntrySupported = !auth?.oauthOnly;
|
|
270
|
+
// Defensively force manual override off for oauth_only servers so no surface
|
|
271
|
+
// can enter a dead-end manual-entry state, even if a stale toggle is set.
|
|
272
|
+
const effectiveManualOverride = manualOverride && manualEntrySupported;
|
|
273
|
+
|
|
254
274
|
const oauthStatus = mcpServer?.status?.oauthStatus;
|
|
255
275
|
const isVendorApprovalPending =
|
|
256
276
|
authMode === "oauth" &&
|
|
@@ -298,15 +318,15 @@ export function useMcpServerCredentials(
|
|
|
298
318
|
);
|
|
299
319
|
|
|
300
320
|
const missingVariables = useMemo(() => {
|
|
301
|
-
if (!oauthTargetEnvVar ||
|
|
321
|
+
if (!oauthTargetEnvVar || effectiveManualOverride) return requiredMissing;
|
|
302
322
|
return requiredMissing.filter((v) => v.key !== oauthTargetEnvVar);
|
|
303
|
-
}, [requiredMissing, oauthTargetEnvVar,
|
|
323
|
+
}, [requiredMissing, oauthTargetEnvVar, effectiveManualOverride]);
|
|
304
324
|
|
|
305
325
|
const isReady =
|
|
306
326
|
!personalEnv.isLoading &&
|
|
307
327
|
!grantStatus.isLoading &&
|
|
308
328
|
missingVariables.length === 0 &&
|
|
309
|
-
(authMode === "manual" ||
|
|
329
|
+
(authMode === "manual" || effectiveManualOverride || isOAuthConnected);
|
|
310
330
|
|
|
311
331
|
const saveCredentials = useCallback(
|
|
312
332
|
async (values: Record<string, EnvVarInput>): Promise<void> => {
|
|
@@ -335,6 +355,7 @@ export function useMcpServerCredentials(
|
|
|
335
355
|
effectiveOAuthSource,
|
|
336
356
|
isOrgOAuthApp,
|
|
337
357
|
canBringOwnApp,
|
|
358
|
+
manualEntrySupported,
|
|
338
359
|
missingVariables,
|
|
339
360
|
isReady,
|
|
340
361
|
isLoading: personalEnv.isLoading || grantStatus.isLoading,
|
|
@@ -342,7 +363,7 @@ export function useMcpServerCredentials(
|
|
|
342
363
|
saveCredentials,
|
|
343
364
|
isSaving: personalEnv.isMutating,
|
|
344
365
|
refetch,
|
|
345
|
-
manualOverride,
|
|
366
|
+
manualOverride: effectiveManualOverride,
|
|
346
367
|
setManualOverride,
|
|
347
368
|
};
|
|
348
369
|
}
|
|
@@ -24,6 +24,7 @@ import { SelectionStore } from "../internal/store/selection-store.js";
|
|
|
24
24
|
import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.js";
|
|
25
25
|
import { ThreadSelectionContext } from "../execution/ThreadSelectionContext.js";
|
|
26
26
|
import { useSelectedThreadItem } from "../execution/useThreadSelection.js";
|
|
27
|
+
import { isTerminalPhase } from "../execution/execution-phases.js";
|
|
27
28
|
import { MessageThread, type MessageThreadSlots } from "../execution/MessageThread.js";
|
|
28
29
|
import { FileChangeProgressBar } from "../execution/FileChangeProgressBar.js";
|
|
29
30
|
import { FileReviewDock } from "../execution/FileReviewDock.js";
|
|
@@ -1126,6 +1127,18 @@ function SessionPanelRegion({
|
|
|
1126
1127
|
],
|
|
1127
1128
|
);
|
|
1128
1129
|
|
|
1130
|
+
// Cloud sessions with a git workspace push approved changes back to a
|
|
1131
|
+
// branch/PR, so the Changes facet is offered from the start with an honest
|
|
1132
|
+
// pre-push state. Deliberately NOT for local sessions: local mode edits the
|
|
1133
|
+
// user's own working tree and never writes back (the runner configures no
|
|
1134
|
+
// push credentials there) — promising a PR would be a lie.
|
|
1135
|
+
const expectsWriteBack =
|
|
1136
|
+
flow.executionTarget === "cloud" &&
|
|
1137
|
+
flow.workspace.entries.some((entry) => entry.type === "git");
|
|
1138
|
+
const displayPhase =
|
|
1139
|
+
flow.displayExecution?.status?.phase ??
|
|
1140
|
+
ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
|
|
1141
|
+
|
|
1129
1142
|
// The session facets (Config / Changes / Artifacts / Usage / Inspect) as
|
|
1130
1143
|
// injected rail views — the full inspector feature set inside one panel.
|
|
1131
1144
|
const railViews = useSessionRailViews({
|
|
@@ -1138,6 +1151,8 @@ function SessionPanelRegion({
|
|
|
1138
1151
|
onOpenPlan,
|
|
1139
1152
|
onOpenArtifact,
|
|
1140
1153
|
onActivateArtifact,
|
|
1154
|
+
expectsWriteBack,
|
|
1155
|
+
changesSettled: isTerminalPhase(displayPhase),
|
|
1141
1156
|
});
|
|
1142
1157
|
|
|
1143
1158
|
// Explorer-footer folder attach (desktop only — needs the native picker).
|
|
@@ -80,6 +80,18 @@ describe("useSessionRailViews", () => {
|
|
|
80
80
|
]);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it("offers Changes pre-push when a write-back is expected, without a zero badge", () => {
|
|
84
|
+
const { result } = renderViews({ expectsWriteBack: true });
|
|
85
|
+
const changes = result.current.find((v) => v.id === "changes");
|
|
86
|
+
expect(changes, "cloud git sessions get the facet before any push").toBeTruthy();
|
|
87
|
+
expect(changes?.badge).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("hides Changes when no write-back exists and none is expected", () => {
|
|
91
|
+
const { result } = renderViews({ expectsWriteBack: false });
|
|
92
|
+
expect(result.current.some((v) => v.id === "changes")).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
83
95
|
it("surfaces Artifacts with a count badge", () => {
|
|
84
96
|
artifactsState.hasArtifacts = true;
|
|
85
97
|
artifactsState.artifactCount = 4;
|
|
@@ -6,20 +6,42 @@ import { WriteBackCard } from "../../execution/WriteBackCard.js";
|
|
|
6
6
|
|
|
7
7
|
export interface ChangesTabProps {
|
|
8
8
|
readonly executions: readonly AgentExecution[];
|
|
9
|
+
/**
|
|
10
|
+
* Whether this session is expected to push its approved changes back to a
|
|
11
|
+
* git remote — a CLOUD session with at least one git workspace entry (local
|
|
12
|
+
* sessions edit the user's own working tree and never write back). Enables
|
|
13
|
+
* the pre-write-back states below, so the facet answers "where does my work
|
|
14
|
+
* go?" before any push exists. Defaults to `false`: a bare `ChangesTab`
|
|
15
|
+
* stays the pure write-back list it always was.
|
|
16
|
+
*/
|
|
17
|
+
readonly expectsWriteBack?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the session's latest execution is settled (terminal). Selects
|
|
20
|
+
* between the pre-write-back states: a live turn shows the "will be pushed
|
|
21
|
+
* here" promise, a settled one shows the honest "nothing pushed yet".
|
|
22
|
+
* Only consulted when {@link expectsWriteBack} is set and no write-back
|
|
23
|
+
* exists yet.
|
|
24
|
+
*/
|
|
25
|
+
readonly isSettled?: boolean;
|
|
9
26
|
}
|
|
10
27
|
|
|
11
28
|
/**
|
|
12
|
-
* Changes facet for the session panel — the
|
|
13
|
-
* (branch/commit/PR), one
|
|
29
|
+
* Changes facet for the session panel — the definitive "where did my work go"
|
|
30
|
+
* surface: the session's git write-backs (branch/commit/PR), one
|
|
31
|
+
* `WriteBackCard` per entry, with honest pre-push states for cloud git
|
|
32
|
+
* sessions whose write-back hasn't happened yet.
|
|
14
33
|
*
|
|
15
34
|
* Local file changes deliberately do NOT render here: they live in the
|
|
16
35
|
* transcript, where each stamped edit row shows its diff in place and the
|
|
17
36
|
* per-turn decision bar carries the review controls and file list
|
|
18
|
-
* (`FileReviewCard`).
|
|
19
|
-
*
|
|
20
|
-
* once a write-back exists (see `useSessionRailViews`).
|
|
37
|
+
* (`FileReviewCard`). This facet is exclusively the git OUTCOME surface —
|
|
38
|
+
* what was reviewed there lands here as a branch and pull request.
|
|
21
39
|
*/
|
|
22
|
-
export function ChangesTab({
|
|
40
|
+
export function ChangesTab({
|
|
41
|
+
executions,
|
|
42
|
+
expectsWriteBack = false,
|
|
43
|
+
isSettled = false,
|
|
44
|
+
}: ChangesTabProps) {
|
|
23
45
|
const { writeBacks, hasWriteBacks } = useSessionWriteBacks(executions);
|
|
24
46
|
|
|
25
47
|
if (hasWriteBacks) {
|
|
@@ -34,6 +56,21 @@ export function ChangesTab({ executions }: ChangesTabProps) {
|
|
|
34
56
|
);
|
|
35
57
|
}
|
|
36
58
|
|
|
59
|
+
if (expectsWriteBack) {
|
|
60
|
+
return (
|
|
61
|
+
<div className="flex flex-col items-center justify-center px-4 py-8 text-center">
|
|
62
|
+
<p className="text-xs text-muted-foreground">
|
|
63
|
+
{isSettled
|
|
64
|
+
? "No changes have been pushed yet. When the agent changes files " +
|
|
65
|
+
"and you approve them, they are pushed to a branch and pull " +
|
|
66
|
+
"request here."
|
|
67
|
+
: "Changes stay in the session workspace while you review them. " +
|
|
68
|
+
"Approved changes are pushed to a branch and pull request here."}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
37
74
|
return (
|
|
38
75
|
<div className="flex flex-col items-center justify-center px-4 py-8 text-center">
|
|
39
76
|
<p className="text-xs text-muted-foreground">
|
|
@@ -35,4 +35,27 @@ describe("ChangesTab", () => {
|
|
|
35
35
|
expect(screen.getByText(/no changes yet/i)).toBeTruthy();
|
|
36
36
|
expect(screen.getByText(/pull requests/i)).toBeTruthy();
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
// Pre-push states for a session expected to write back (cloud + git):
|
|
40
|
+
// live turns promise where approved work will land; a settled execution
|
|
41
|
+
// reports honestly that nothing was pushed.
|
|
42
|
+
it("renders the in-review promise while a write-back is expected and the session is live", () => {
|
|
43
|
+
render(<ChangesTab executions={[]} expectsWriteBack isSettled={false} />);
|
|
44
|
+
expect(screen.getByText(/stay in the session workspace/i)).toBeTruthy();
|
|
45
|
+
expect(screen.getByText(/pushed to a branch and pull request here/i)).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("renders the nothing-pushed state when expected but the execution settled without a push", () => {
|
|
49
|
+
render(<ChangesTab executions={[]} expectsWriteBack isSettled />);
|
|
50
|
+
expect(screen.getByText(/no changes have been pushed yet/i)).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("write-backs take precedence over the pre-push states", () => {
|
|
54
|
+
writeBacksReturn.writeBacks = [{ writeBack: { workspaceEntryName: "w1" } }];
|
|
55
|
+
writeBacksReturn.hasWriteBacks = true;
|
|
56
|
+
|
|
57
|
+
render(<ChangesTab executions={[]} expectsWriteBack />);
|
|
58
|
+
expect(screen.getByTestId("write-back-card")).toBeTruthy();
|
|
59
|
+
expect(screen.queryByText(/no changes/i)).toBeNull();
|
|
60
|
+
});
|
|
38
61
|
});
|
|
@@ -54,6 +54,21 @@ export interface UseSessionRailViewsOptions {
|
|
|
54
54
|
* @default true
|
|
55
55
|
*/
|
|
56
56
|
readonly includeExecutionFacets?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the session is expected to push approved changes to a git remote
|
|
59
|
+
* (a cloud session with git workspace entries). When `true` the Changes
|
|
60
|
+
* facet is offered from the start — with an honest pre-push state — instead
|
|
61
|
+
* of only materializing once a write-back exists, so "where does my work
|
|
62
|
+
* go?" always has an answer. See {@link ChangesTabProps.expectsWriteBack}.
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
readonly expectsWriteBack?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Whether the session's latest execution is settled (terminal) — forwarded
|
|
68
|
+
* to the Changes facet's pre-push states. See {@link ChangesTabProps.isSettled}.
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
readonly changesSettled?: boolean;
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
/**
|
|
@@ -77,6 +92,8 @@ export function useSessionRailViews({
|
|
|
77
92
|
onOpenArtifact,
|
|
78
93
|
onActivateArtifact,
|
|
79
94
|
includeExecutionFacets = true,
|
|
95
|
+
expectsWriteBack = false,
|
|
96
|
+
changesSettled = false,
|
|
80
97
|
}: UseSessionRailViewsOptions): readonly SurfaceRailView[] {
|
|
81
98
|
const { hasWriteBacks, writeBackCount } = useSessionWriteBacks(allExecutions);
|
|
82
99
|
const { hasArtifacts, artifactCount } = useSessionArtifacts(allExecutions);
|
|
@@ -94,13 +111,22 @@ export function useSessionRailViews({
|
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
if (includeExecutionFacets) {
|
|
97
|
-
|
|
114
|
+
// Offered when a write-back exists OR the session is expected to
|
|
115
|
+
// produce one — the facet then carries an honest pre-push state
|
|
116
|
+
// instead of being invisible until the first push.
|
|
117
|
+
if (hasWriteBacks || expectsWriteBack) {
|
|
98
118
|
views.push({
|
|
99
119
|
id: "changes",
|
|
100
120
|
label: "Changes",
|
|
101
121
|
icon: <ChangesIcon />,
|
|
102
|
-
badge: writeBackCount,
|
|
103
|
-
content:
|
|
122
|
+
badge: writeBackCount > 0 ? writeBackCount : undefined,
|
|
123
|
+
content: (
|
|
124
|
+
<ChangesTab
|
|
125
|
+
executions={allExecutions}
|
|
126
|
+
expectsWriteBack={expectsWriteBack}
|
|
127
|
+
isSettled={changesSettled}
|
|
128
|
+
/>
|
|
129
|
+
),
|
|
104
130
|
});
|
|
105
131
|
}
|
|
106
132
|
|
|
@@ -145,6 +171,8 @@ export function useSessionRailViews({
|
|
|
145
171
|
}, [
|
|
146
172
|
sessionConfig,
|
|
147
173
|
includeExecutionFacets,
|
|
174
|
+
expectsWriteBack,
|
|
175
|
+
changesSettled,
|
|
148
176
|
hasWriteBacks,
|
|
149
177
|
writeBackCount,
|
|
150
178
|
hasArtifacts,
|
|
@@ -81,10 +81,10 @@ describe("TaskKindRecentsStore", () => {
|
|
|
81
81
|
|
|
82
82
|
it("filters out invalid entries from localStorage", () => {
|
|
83
83
|
const data = [
|
|
84
|
-
{ kind: "valid_kind", timestamp:
|
|
85
|
-
{ kind: 123, timestamp:
|
|
86
|
-
{ timestamp:
|
|
87
|
-
{ kind: "another_valid", timestamp:
|
|
84
|
+
{ kind: "valid_kind", timestamp: 2 },
|
|
85
|
+
{ kind: 123, timestamp: 3 }, // invalid: kind is not string
|
|
86
|
+
{ timestamp: 4 }, // invalid: missing kind
|
|
87
|
+
{ kind: "another_valid", timestamp: 1 },
|
|
88
88
|
];
|
|
89
89
|
globalThis.localStorage.setItem(TEST_KEY, JSON.stringify(data));
|
|
90
90
|
|