Skip to content

Commit e949786

Browse files
committed
fix goal pill workspace navigation
1 parent 23440dd commit e949786

2 files changed

Lines changed: 77 additions & 19 deletions

File tree

src/browser/components/AgentListItem/AgentListItem.test.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "../../../../tests/ui/dom";
22

33
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
4-
import { cleanup, render, within } from "@testing-library/react";
4+
import { cleanup, fireEvent, render, within } from "@testing-library/react";
55
import type { ReactNode } from "react";
66
import { installDom } from "../../../../tests/ui/dom";
77
import type * as ReactDndModuleType from "react-dnd";
@@ -17,9 +17,12 @@ import type * as RuntimeStatusStoreModuleType from "@/browser/stores/RuntimeStat
1717
import type * as WorkspaceStoreModule from "@/browser/stores/WorkspaceStore";
1818
import * as TooltipModule from "../Tooltip/Tooltip";
1919
import * as WorkspaceStatusIndicatorModule from "../WorkspaceStatusIndicator/WorkspaceStatusIndicator";
20+
import { parseRightSidebarLayoutState } from "@/browser/utils/rightSidebarLayout";
21+
import { getRightSidebarLayoutKey } from "@/common/constants/storage";
2022
import type { AgentRowRenderMeta } from "@/browser/utils/ui/workspaceFiltering";
2123
import type { StreamAbortReasonSnapshot } from "@/common/types/stream";
2224
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
25+
import type { WorkspaceSelection } from "./AgentListItem";
2326
import type { AgentListItem as AgentListItemComponent } from "./AgentListItem";
2427

2528
let AgentListItem!: typeof AgentListItemComponent;
@@ -225,6 +228,7 @@ function renderWorkspaceItem(
225228
subAgentConnectorLayout?: "default" | "task-group-member";
226229
completedChildrenExpanded?: boolean;
227230
onToggleCompletedChildren?: (workspaceId: string) => void;
231+
onSelectWorkspace?: (selection: WorkspaceSelection) => void;
228232
} = {}
229233
) {
230234
const metadata = options.metadata ?? createMetadata();
@@ -240,7 +244,7 @@ function renderWorkspaceItem(
240244
subAgentConnectorLayout={options.subAgentConnectorLayout}
241245
completedChildrenExpanded={options.completedChildrenExpanded}
242246
onToggleCompletedChildren={options.onToggleCompletedChildren}
243-
onSelectWorkspace={() => undefined}
247+
onSelectWorkspace={options.onSelectWorkspace ?? (() => undefined)}
244248
onForkWorkspace={() => Promise.resolve()}
245249
onArchiveWorkspace={() => Promise.resolve()}
246250
onCancelCreation={() => Promise.resolve()}
@@ -311,6 +315,47 @@ describe("AgentListItem", () => {
311315
expect(pill.getAttribute("aria-label")).toBe("Goal budget limited, $5.25 of $5.00 spent");
312316
});
313317

318+
test("goal pill selects the workspace and persists the Goal tab before navigation", () => {
319+
mockWorkspaceHeartbeatsEnabled = true;
320+
mockWorkspaceSidebarState = createWorkspaceSidebarState({
321+
goal: {
322+
goalId: "22222222-2222-4222-8222-222222222222",
323+
status: "active",
324+
objective: "Open the goal tab",
325+
budgetCents: 100_000,
326+
costCents: 5_294,
327+
turnsUsed: 2,
328+
turnCap: null,
329+
startedAtMs: Date.now(),
330+
},
331+
});
332+
const onSelectWorkspace = mock((_: WorkspaceSelection) => undefined);
333+
334+
const { row, metadata } = renderWorkspaceItem({ onSelectWorkspace });
335+
const pill = within(row).getByTestId(`workspace-goal-pill-${TEST_WORKSPACE_ID}`);
336+
337+
fireEvent.click(pill);
338+
339+
expect(onSelectWorkspace).toHaveBeenCalledWith({
340+
projectPath: metadata.projectPath,
341+
projectName: metadata.projectName,
342+
namedWorkspacePath: metadata.namedWorkspacePath,
343+
workspaceId: metadata.id,
344+
});
345+
const persistedLayout = window.localStorage.getItem(getRightSidebarLayoutKey(metadata.id));
346+
if (persistedLayout === null) {
347+
throw new Error("expected target workspace right sidebar layout to be persisted");
348+
}
349+
const rawLayout: unknown = JSON.parse(persistedLayout);
350+
const layout = parseRightSidebarLayoutState(rawLayout, "costs");
351+
expect(layout.root.type).toBe("tabset");
352+
if (layout.root.type !== "tabset") {
353+
throw new Error("expected default right sidebar layout to be a tabset");
354+
}
355+
expect(layout.root.activeTab).toBe("goal");
356+
expect(layout.root.tabs).toContain("goal");
357+
});
358+
314359
test("renders a heartbeat icon directly in the leading slot for seen rows when the heartbeat experiment is enabled", () => {
315360
mockWorkspaceHeartbeatsEnabled = true;
316361

src/browser/components/AgentListItem/AgentListItem.tsx

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { useTitleEdit } from "@/browser/contexts/WorkspaceTitleEditContext";
22
import { updatePersistedState } from "@/browser/hooks/usePersistedState";
3+
import {
4+
getDefaultRightSidebarLayoutState,
5+
parseRightSidebarLayoutState,
6+
selectOrAddTab,
7+
type RightSidebarLayoutState,
8+
} from "@/browser/utils/rightSidebarLayout";
39
import { useContextMenuPosition } from "@/browser/hooks/useContextMenuPosition";
410
import { useExperimentValue } from "@/browser/hooks/useExperiments";
511
import {
@@ -20,9 +26,8 @@ import {
2026
normalizeTaskGroupLabel,
2127
} from "@/common/utils/tools/taskGroups";
2228
import { EXPERIMENT_IDS } from "@/common/constants/experiments";
23-
import { CUSTOM_EVENTS, createCustomEvent } from "@/common/constants/events";
2429
import { isDevcontainerRuntime } from "@/common/types/runtime";
25-
import { getWorkspaceLastReadKey } from "@/common/constants/storage";
30+
import { getRightSidebarLayoutKey, getWorkspaceLastReadKey } from "@/common/constants/storage";
2631
import type { GoalSnapshot } from "@/common/types/goal";
2732
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
2833
import React, { useState, useEffect, useRef, useCallback } from "react";
@@ -446,6 +451,16 @@ function getGoalPillText(goal: GoalSnapshot): string {
446451
return `Target ${formatGoalCents(goal.costCents)}`;
447452
}
448453

454+
function persistGoalTabSelection(workspaceId: string): void {
455+
const defaultLayout = getDefaultRightSidebarLayoutState("costs");
456+
457+
updatePersistedState<RightSidebarLayoutState>(
458+
getRightSidebarLayoutKey(workspaceId),
459+
(rawLayout) => selectOrAddTab(parseRightSidebarLayoutState(rawLayout, "costs"), "goal"),
460+
defaultLayout
461+
);
462+
}
463+
449464
// ─────────────────────────────────────────────────────────────────────────────
450465
// Regular Workspace Item (persisted workspace)
451466
// ─────────────────────────────────────────────────────────────────────────────
@@ -700,6 +715,13 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
700715

701716
const paddingLeft = getSidebarItemPaddingLeft(depth);
702717

718+
const workspaceSelection: WorkspaceSelection = {
719+
projectPath,
720+
projectName,
721+
namedWorkspacePath,
722+
workspaceId,
723+
};
724+
703725
// Drag handle for moving workspace between sections
704726
const [{ isDragging }, drag, dragPreview] = useDrag(
705727
() => ({
@@ -748,12 +770,7 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
748770
onClick={() => {
749771
if (isDisabled) return;
750772
if (ctxMenu.suppressClickIfLongPress()) return;
751-
onSelectWorkspace({
752-
projectPath,
753-
projectName,
754-
namedWorkspacePath,
755-
workspaceId,
756-
});
773+
onSelectWorkspace(workspaceSelection);
757774
}}
758775
onDoubleClick={(event) => {
759776
if (isDisabled || isEditing) {
@@ -797,12 +814,7 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
797814
}
798815
if (e.key === "Enter" || e.key === " ") {
799816
e.preventDefault();
800-
onSelectWorkspace({
801-
projectPath,
802-
projectName,
803-
namedWorkspacePath,
804-
workspaceId,
805-
});
817+
onSelectWorkspace(workspaceSelection);
806818
}
807819
}}
808820
onContextMenu={ctxMenu.onContextMenu}
@@ -1089,9 +1101,10 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
10891101
data-testid={`workspace-goal-pill-${workspaceId}`}
10901102
onClick={(event) => {
10911103
event.stopPropagation();
1092-
window.dispatchEvent(
1093-
createCustomEvent(CUSTOM_EVENTS.OPEN_GOAL_TAB, { workspaceId })
1094-
);
1104+
// Persist the Goal tab before selecting so the target workspace opens
1105+
// directly there even when its RightSidebar is not mounted yet.
1106+
persistGoalTabSelection(workspaceId);
1107+
onSelectWorkspace(workspaceSelection);
10951108
}}
10961109
onKeyDown={stopKeyboardPropagation}
10971110
>

0 commit comments

Comments
 (0)