Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions apps/desktop/src/lib/work-panel-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ export type WorkPanelContext = WorkPanelTabsState & {
fileRequest: { path: string; seq: number; mimeType?: string } | null;
};

export type ReviewArtifactEvent = {
toolName?: string;
isError?: boolean;
result: unknown;
};

let newWorkPanelTabSequence = 0;

export function emptyWorkPanelContext(): WorkPanelContext {
Expand Down Expand Up @@ -226,22 +220,6 @@ export function fileWorkPanelTab(path: string, mimeType?: string): WorkPanelTab
};
}

export function toolResultRoot(result: unknown): string | null {
if (!result || typeof result !== "object") return null;
const details = (result as { details?: unknown }).details;
if (!details || typeof details !== "object") return null;
const root = (details as { root?: unknown }).root;
return typeof root === "string" ? root : null;
}

export function shouldOpenReviewArtifact(event: ReviewArtifactEvent): boolean {
return (
(event.toolName === "Write" || event.toolName === "Edit") &&
event.isError !== true &&
toolResultRoot(event.result) === "workspace"
);
}

export function openWorkPanelTabState(
state: WorkPanelTabsState,
tab: WorkPanelTab,
Expand Down
6 changes: 1 addition & 5 deletions apps/desktop/src/stores/app-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ import {
import { settleStoppedAssistantMetrics } from "../lib/context-usage";
import { formatToolValue } from "../lib/tool-display";
import { withReviewChangeState } from "../lib/workspace-review";
import {
fileWorkPanelTab,
shouldOpenReviewArtifact,
toolWorkPanelTab,
} from "../lib/work-panel-tabs";
import { fileWorkPanelTab } from "../lib/work-panel-tabs";
import {
clearSessionPermissions,
enqueuePermission,
Expand Down
20 changes: 3 additions & 17 deletions apps/desktop/src/stores/slices/events-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ import {
mergePlanCheckpoint,
} from "../../lib/plan-mode-state";
import { formatToolValue } from "../../lib/tool-display";
import {
shouldOpenReviewArtifact,
toolWorkPanelTab,
} from "../../lib/work-panel-tabs";
import type { AppState } from "../app-state";
import type { SessionRuntime } from "../runtime/session-runtime";
import type { StoreAccess } from "./types";
Expand Down Expand Up @@ -352,7 +348,9 @@ export function createEventsSlice({
...(envelope.agentName ? { agentName: envelope.agentName } : {}),
});
} else if (event.type === "tool_end") {
const toolName = runtime.getToolStart(event.toolCallId)?.toolName;
// A tool result never opens or activates a work-panel tab: Review is a
// user-opened surface (panel toggle or New launcher), so an agent edit
// cannot reveal the panel even in its own session.
set((state) => {
const pendingPermissions = removePermissionForToolCall(
state.pendingPermissions,
Expand All @@ -369,18 +367,6 @@ export function createEventsSlice({
? {}
: { pendingPermissions, pendingAsks };
});
if (
shouldOpenReviewArtifact({
toolName,
isError: event.isError,
result: event.result,
})
) {
get().openWorkPanelTabForSession(
envelope.sessionId,
toolWorkPanelTab("review"),
);
}
}

if (event.type === "compaction_end" && event.ok && event.mark) {
Expand Down
10 changes: 6 additions & 4 deletions apps/desktop/test/bundled-plugins.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ test("the host no longer offers Files or Browser as built-in tools", () => {
assert.match(panelSource, /activeTab\?\.kind === "review"/);
});

test("Review still opens itself from workspace edit artifacts", () => {
// Removing the launcher entry must not remove the way Review appears at all.
test("Review opens only from an explicit user action", () => {
// The New launcher row and the viewport-fixed toggle are the only ways in,
// so a workspace edit can no longer reveal or activate Review by itself.
const storeSource = readStoreSourceSync();
assert.match(storeSource, /shouldOpenReviewArtifact\(\{/);
assert.match(storeSource, /toolWorkPanelTab\("review"\)/);
assert.doesNotMatch(storeSource, /shouldOpenReviewArtifact/);
assert.doesNotMatch(storeSource, /toolWorkPanelTab\("review"\)/);
assert.match(panelSource, /toolWorkPanelTab\("review"\)/);
});

test("Browser ships as an ordinary plugin over the public CDP API", () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/test/chat-review-entry.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ test("chat renders one message-owned card immediately after its tool row", () =>
storeSource,
/rollbackWorkspaceChange:[\s\S]*api\.workspaceReviewRollback[\s\S]*withReviewChangeState/,
);
assert.match(
assert.doesNotMatch(
eventsSource,
/if \(\s*shouldOpenReviewArtifact\([\s\S]*openWorkPanelTabForSession/,
/shouldOpenReviewArtifact|toolWorkPanelTab\("review"\)/,
);
assert.doesNotMatch(storeSource, /workspaceReviewSessions/);
});
Expand Down
21 changes: 0 additions & 21 deletions apps/desktop/test/work-panel-tabs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const {
pluginWorkPanelTab,
replaceWorkPanelTabState,
sanitizeWorkPanelTabsState,
shouldOpenReviewArtifact,
switchWorkPanelContextState,
toolWorkPanelTab,
} = await import("../src/lib/work-panel-tabs.ts");
Expand Down Expand Up @@ -140,26 +139,6 @@ test("only plugin views are launchable tools", () => {
assert.equal(isKnownWorkPanelTab(newWorkPanelTab()), true);
});

test("review artifacts are recognized independently of the visible session", () => {
const base = {
toolName: "Write",
isError: false,
result: { details: { root: "workspace" } },
};

assert.equal(shouldOpenReviewArtifact(base), true);
assert.equal(shouldOpenReviewArtifact({ ...base, toolName: "Edit" }), true);
assert.equal(shouldOpenReviewArtifact({ ...base, toolName: "Bash" }), false);
assert.equal(shouldOpenReviewArtifact({ ...base, isError: true }), false);
assert.equal(
shouldOpenReviewArtifact({
...base,
result: { details: { root: "scratch" } },
}),
false,
);
});

test("empty work panel context has no visible or retained resource state", () => {
assert.deepEqual(emptyWorkPanelContext(), {
open: false,
Expand Down
31 changes: 9 additions & 22 deletions apps/desktop/test/work-panel.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -441,30 +441,17 @@ test("built-in terminal is absent while the work panel keeps its other surfaces"
assert.doesNotMatch(transcriptSource, /openTerminal|terminalArtifact|chat\.openTerminal/);
});

test("workspace artifacts attach review to their originating session", () => {
const artifactIndex = storeSource.indexOf("shouldOpenReviewArtifact({");
const openReviewMatch = storeSource.match(
/get\(\)\.openWorkPanelTabForSession\(\s*envelope\.sessionId,\s*toolWorkPanelTab\("review"\),?\s*\)/,
);
const openReviewIndex = openReviewMatch?.index ?? -1;
const gateIndex = storeSource.indexOf(
"if (envelope.sessionId !== get().activeSessionId)",
);
assert.ok(artifactIndex > -1, "workspace artifact gate exists");
assert.ok(openReviewIndex > artifactIndex, "review artifact records its session tab");
assert.ok(gateIndex > -1, "cross-session gate exists");
assert.ok(
openReviewIndex < gateIndex,
"background artifacts must be recorded before the cross-session early-return",
);
assert.match(
storeSource,
/shouldOpenReviewArtifact\(\{[\s\S]*toolName,[\s\S]*isError:\s*event\.isError,[\s\S]*result:\s*event\.result/s,
);
test("tool results never open the Review tab on their own", () => {
// Review opens only from an explicit user action: the viewport toggle
// reveals the retained context and the `+` launcher lists its row. A
// successful Write/Edit may not record, activate, or reveal a tab for any
// session, visible or background.
assert.doesNotMatch(storeSource, /shouldOpenReviewArtifact/);
assert.doesNotMatch(
storeSource.match(/shouldOpenReviewArtifact\(\{[\s\S]*?\}\)/)?.[0] ?? "",
/activeSessionId|sessionId/,
storeSource,
/openWorkPanelTabForSession\([\s\S]{0,120}toolWorkPanelTab\("review"\)/,
);
assert.match(storeSource, /openWorkPanelTabForSession:/);
});

test("work panel context is retained by session instead of cleared on selection", () => {
Expand Down
7 changes: 4 additions & 3 deletions docs/spec/04-ux/01-ui-ia.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ destination, chat as the home surface, tools and permissions inline.
revealing it without creating a resource tab and collapsing it without
discarding one; the create trigger remains unavailable while the panel is
closed. Closing the final tab keeps the panel open and shows the New launcher.
A
successful active-session workspace Write/Edit artifact opens Review;
scratch, failed, and background-session writes never steal focus. The inner
No agent or tool result opens, activates, or resizes the panel: Review is
reached only through an explicit user action, so a successful workspace
Write/Edit leaves the panel exactly as the user left it and shows its
evidence as a transcript card instead. The inner
divider resizes the panel through the shared three-column budget; moving it
left takes space until MainChat reaches 450px, at which point the expanded
sidebar yields immediately, and moving it right gives space back. A manual
Expand Down
7 changes: 5 additions & 2 deletions docs/spec/04-ux/08-component-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1014,8 +1014,11 @@ entirely inside the plugin's isolated page:
- Trigger: file/URL references and BrowserPreview create/activate their
resource tab in the originating session's runtime context. BrowserPreview
events carry `sessionId`, and the renderer retains that session's preview
path/URL as its Browser resource. Successful workspace Write/Edit artifacts
create/activate Review in the originating session.
path/URL as its Browser resource. Review is never triggered by a tool
result: it opens only from the `+` launcher row or from the retained panel
context the viewport-fixed toggle and `Cmd/Ctrl + J` reveal, so a successful
workspace Write/Edit cannot open, activate, or resize the panel in any
session.
The viewport-fixed toggle and `Cmd/Ctrl + J` both toggle the active session's
retained panel context: they reveal the panel without creating a resource and
collapse the visible panel without deleting one. With no active session the
Expand Down
15 changes: 9 additions & 6 deletions docs/spec/04-ux/09-interaction-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,13 @@ may be retained while exactly one workspace supplies the visible shell context.
divider updates the renderer-owned panel target from 244px upward, capped by
the live three-column budget, while native window edges resize only the fixed
application window (ADR 0151).
- A successful workspace Write/Edit creates or activates Review in its
originating session. Failed and scratch writes do not. Background-session
artifacts update only their retained context and never open, activate, resize,
focus, or change the visible panel.
- No tool result creates or activates a work-panel tab. Review opens only from
an explicit user action — its `+` launcher row, or the retained context the
viewport-fixed toggle and `Cmd/Ctrl + J` reveal — so a successful workspace
Write/Edit never takes the panel away from what the user was reading. Failed
and scratch writes behave the same. Background-session events update only
their retained context and never open, activate, resize, focus, or change the
visible panel.
- Each successful workspace Write/Edit tool result carries one durable review
snapshot. Its compact InlineReviewCard is rendered in the same activity
disclosure, immediately after its tool row; it is never moved to the
Expand All @@ -486,8 +489,8 @@ may be retained while exactly one workspace supplies the visible shell context.
denied, and unstructured results do not render a card. A background
session's card remains with its own transcript and becomes visible only
after that session is selected; its event never renders in the currently
visible session. Successful workspace artifacts may still create or
activate the singleton Review tab.
visible session. A successful workspace artifact cannot create or activate
the singleton Review tab; it appears only after the user opens it.
- Each session retains `{open, tabs, activeTabId, browserResource}` in renderer
memory. Selecting another session swaps the visible context atomically and
switching back restores it; selecting a workspace without an active
Expand Down
6 changes: 4 additions & 2 deletions docs/spec/04-ux/10-workbuddy-benchmark-ux.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ writes results to workspace memory/config — a form disguised as a chat.
### 3.7 Artifacts view (from 我的文件)
Sessions produce files the user later can't find without scrolling the
transcript. **Adopted first step in D128**: clicking a file artifact creates a
path-keyed, closeable work-panel tab, and successful workspace Write/Edit
artifacts open Review. **Adopted in D179**: the transcript also places a
path-keyed, closeable work-panel tab; its second half — a successful workspace
Write/Edit opening Review by itself — was withdrawn in D451, and Review now
opens only from an explicit user action. **Adopted in D179**: the transcript
also places a
message-scoped review card directly after each successful file mutation; its
status, +/− totals, and expandable hunks stay attached to that tool row rather
than becoming a global footer entry. These renderer tabs and cards are
Expand Down
17 changes: 10 additions & 7 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -3249,7 +3249,9 @@ identify the platform validation still needed.
Repeat with failed, denied, and scratch writes.
- **Expected**: Each successful workspace Write/Edit creates one message-owned
review record and one adjacent keyboard-accessible card; the card is never a
bottom/global entry. Every review card, inline and in the Review tab, is
bottom/global entry. The completion opens nothing: the panel keeps whatever
the user left it showing, and Review appears only after the user opens it.
Every review card, inline and in the Review tab, is
collapsed by default and expands on demand. The Review tab lists A's
chronological recorded changes, independent of Git status, repository
presence, commit state, focus refresh,
Expand Down Expand Up @@ -4549,15 +4551,16 @@ identify the platform validation still needed.
request in A as well. 5) Open B explicitly, resolve only B's request, then
return to A and resolve A's request. 6) Rapidly select A then B while session
details load in opposite completion order. 7) While B is loading, resolve A's
Write/Edit request so its tool completion creates Review, then let B emit a
Write/Edit request so its tool completion records an inline review card, then
let B emit a
BrowserPreview artifact while A is visible. Switch back to each session.
- **Expected**: B's background events update only B's row and retained state;
they do not change A's active session/project/page, transcript, draft, scroll,
or keyboard focus, and no global modal appears. Opening B reveals only B's
inline card with its original countdown. Both requests remain independently
actionable, and resolving B does not clear A. The final rapid selection stays
on B even when A's older load finishes later. Only explicit notification or
session activation may navigate. A's post-approval Review is retained only in
session activation may navigate. A's post-approval review card is retained only in
A without a transient open/close flash in B; B's BrowserPreview carries B's
session identity, updates only B's retained Browser resource, and never opens,
navigates, focuses, or resizes A's panel. Explicitly returning to either
Expand Down Expand Up @@ -7159,8 +7162,8 @@ identify the platform validation still needed.
no Uninstall action.
2. Reveal the work panel and click `+` to create a New launcher tab. Confirm
its rows include Review and the plugin-contributed File Manager and Browser
views. Trigger an agent edit and confirm Review opens itself under Open
resources — it is an artifact surface, not a launcher entry.
views. Trigger an agent edit and confirm the panel does not open itself:
Review appears under Open resources only after the user selects its row.
3. Open the File Manager view. Confirm the tree lists the project, expands
directories lazily, and omits `node_modules`, `.git`, and `.env`. Switch
the view's top-left folder control to the project's second folder and
Expand Down Expand Up @@ -8479,8 +8482,8 @@ This test plan spec is accepted when:
action removes or changes the other card.
- Resolve A's Write/Edit permission and switch to B before completion. Expect no
transient Review panel in B and no panel/window flash; returning to A restores
A's resulting Review tab and prior panel selection, while B's tabs and Browser
resource remain unchanged.
A's prior panel selection with the inline review card in its transcript — the
edit opened no tab — while B's tabs and Browser resource remain unchanged.

### US-UI-69 Sidebar type balance (D144/D161)
- Open the expanded sidebar in light and dark themes at default and minimum
Expand Down
Loading
Loading