From bdd76d9d6acd6a19fa0064eccda3a911915a4a84 Mon Sep 17 00:00:00 2001 From: Leonardo Stern Date: Thu, 12 Feb 2026 09:43:34 +0800 Subject: [PATCH 1/2] Restore Sidebar Forge integration and fix failing checks --- src/features/app/components/Sidebar.tsx | 466 ++++++++++-------- .../hooks/layoutNodes/buildPrimaryNodes.tsx | 4 + 2 files changed, 261 insertions(+), 209 deletions(-) diff --git a/src/features/app/components/Sidebar.tsx b/src/features/app/components/Sidebar.tsx index 4174eacff5..76f21380d3 100644 --- a/src/features/app/components/Sidebar.tsx +++ b/src/features/app/components/Sidebar.tsx @@ -1,5 +1,6 @@ import type { AccountSnapshot, + CollaborationModeOption, RequestUserInputRequest, RateLimitSnapshot, ThreadListSortKey, @@ -27,6 +28,7 @@ import { WorktreeSection } from "./WorktreeSection"; import { PinnedThreadList } from "./PinnedThreadList"; import { WorkspaceCard } from "./WorkspaceCard"; import { WorkspaceGroup } from "./WorkspaceGroup"; +import { Forge } from "../../forge/components/Forge"; import { useCollapsedGroups } from "../hooks/useCollapsedGroups"; import { useSidebarMenus } from "../hooks/useSidebarMenus"; import { useSidebarScrollFade } from "../hooks/useSidebarScrollFade"; @@ -67,6 +69,18 @@ type SidebarProps = { onRefreshAllThreads: () => void; activeWorkspaceId: string | null; activeThreadId: string | null; + activeWorkspace?: WorkspaceInfo | null; + sendUserMessageToThread?: ( + workspace: WorkspaceInfo, + threadId: string, + message: string, + imageIds: string[], + options?: { + collaborationMode?: Record | null; + }, + ) => Promise; + collaborationModes?: CollaborationModeOption[]; + onSelectCollaborationMode?: (id: string | null) => void; userInputRequests?: RequestUserInputRequest[]; accountRateLimits: RateLimitSnapshot | null; usageShowRemaining: boolean; @@ -124,6 +138,10 @@ export const Sidebar = memo(function Sidebar({ onRefreshAllThreads, activeWorkspaceId, activeThreadId, + activeWorkspace = null, + sendUserMessageToThread, + collaborationModes = [], + onSelectCollaborationMode, userInputRequests = [], accountRateLimits, usageShowRemaining, @@ -167,6 +185,7 @@ export const Sidebar = memo(function Sidebar({ ); const [searchQuery, setSearchQuery] = useState(""); const [isSearchOpen, setIsSearchOpen] = useState(false); + const [isForgeOpen, setIsForgeOpen] = useState(false); const [addMenuAnchor, setAddMenuAnchor] = useState<{ workspaceId: string; top: number; @@ -433,7 +452,25 @@ export const Sidebar = memo(function Sidebar({ setIsSearchOpen((prev) => !prev)} + onToggleForge={() => { + setIsForgeOpen((prev) => { + const next = !prev; + if (next) { + setIsSearchOpen(false); + } + return next; + }); + }} + isForgeOpen={isForgeOpen} + onToggleSearch={() => { + setIsSearchOpen((prev) => { + const next = !prev; + if (next) { + setIsForgeOpen(false); + } + return next; + }); + }} isSearchOpen={isSearchOpen} threadListSortKey={threadListSortKey} onSetThreadListSortKey={onSetThreadListSortKey} @@ -489,220 +526,231 @@ export const Sidebar = memo(function Sidebar({ onScroll={updateScrollFade} ref={sidebarBodyRef} > -
- {pinnedThreadRows.length > 0 && ( -
-
-
Pinned
+ {isForgeOpen ? ( + + ) : ( +
+ {pinnedThreadRows.length > 0 && ( +
+
+
Pinned
+
+
- -
- )} - {filteredGroupedWorkspaces.map((group) => { - const groupId = group.id; - const showGroupHeader = Boolean(groupId) || hasWorkspaceGroups; - const toggleId = groupId ?? (showGroupHeader ? UNGROUPED_COLLAPSE_ID : null); - const isGroupCollapsed = Boolean( - toggleId && collapsedGroups.has(toggleId), - ); + )} + {filteredGroupedWorkspaces.map((group) => { + const groupId = group.id; + const showGroupHeader = Boolean(groupId) || hasWorkspaceGroups; + const toggleId = groupId ?? (showGroupHeader ? UNGROUPED_COLLAPSE_ID : null); + const isGroupCollapsed = Boolean( + toggleId && collapsedGroups.has(toggleId), + ); - return ( - - {group.workspaces.map((entry) => { - const threads = threadsByWorkspace[entry.id] ?? []; - const isCollapsed = entry.settings.sidebarCollapsed; - const isExpanded = expandedWorkspaces.has(entry.id); - const { - unpinnedRows, - totalRoots: totalThreadRoots, - } = getThreadRows( - threads, - isExpanded, - entry.id, - getPinTimestamp, - ); - const nextCursor = - threadListCursorByWorkspace[entry.id] ?? null; - const showThreadList = - threads.length > 0 || Boolean(nextCursor); - const isLoadingThreads = - threadListLoadingByWorkspace[entry.id] ?? false; - const showThreadLoader = - isLoadingThreads && threads.length === 0; - const isPaging = threadListPagingByWorkspace[entry.id] ?? false; - const worktrees = worktreesByParent.get(entry.id) ?? []; - const addMenuOpen = addMenuAnchor?.workspaceId === entry.id; - const isDraftNewAgent = newAgentDraftWorkspaceId === entry.id; - const isDraftRowActive = - isDraftNewAgent && - entry.id === activeWorkspaceId && - !activeThreadId; - const draftStatusClass = - startingDraftThreadWorkspaceId === entry.id - ? "processing" - : "ready"; + return ( + + {group.workspaces.map((entry) => { + const threads = threadsByWorkspace[entry.id] ?? []; + const isCollapsed = entry.settings.sidebarCollapsed; + const isExpanded = expandedWorkspaces.has(entry.id); + const { + unpinnedRows, + totalRoots: totalThreadRoots, + } = getThreadRows( + threads, + isExpanded, + entry.id, + getPinTimestamp, + ); + const nextCursor = + threadListCursorByWorkspace[entry.id] ?? null; + const showThreadList = + threads.length > 0 || Boolean(nextCursor); + const isLoadingThreads = + threadListLoadingByWorkspace[entry.id] ?? false; + const showThreadLoader = + isLoadingThreads && threads.length === 0; + const isPaging = threadListPagingByWorkspace[entry.id] ?? false; + const worktrees = worktreesByParent.get(entry.id) ?? []; + const addMenuOpen = addMenuAnchor?.workspaceId === entry.id; + const isDraftNewAgent = newAgentDraftWorkspaceId === entry.id; + const isDraftRowActive = + isDraftNewAgent && + entry.id === activeWorkspaceId && + !activeThreadId; + const draftStatusClass = + startingDraftThreadWorkspaceId === entry.id + ? "processing" + : "ready"; - return ( - - {addMenuOpen && addMenuAnchor && - createPortal( - - { - event.stopPropagation(); - setAddMenuAnchor(null); - onAddAgent(entry); - }} - icon={} - > - New agent - - { - event.stopPropagation(); - setAddMenuAnchor(null); - onAddWorktreeAgent(entry); - }} - icon={} - > - New worktree agent - - { - event.stopPropagation(); - setAddMenuAnchor(null); - onAddCloneAgent(entry); + return ( + + {addMenuOpen && addMenuAnchor && + createPortal( + } > - New clone agent - - , - document.body, + { + event.stopPropagation(); + setAddMenuAnchor(null); + onAddAgent(entry); + }} + icon={} + > + New agent + + { + event.stopPropagation(); + setAddMenuAnchor(null); + onAddWorktreeAgent(entry); + }} + icon={} + > + New worktree agent + + { + event.stopPropagation(); + setAddMenuAnchor(null); + onAddCloneAgent(entry); + }} + icon={} + > + New clone agent + + , + document.body, + )} + {isDraftNewAgent && ( +
onSelectWorkspace(entry.id)} + role="button" + tabIndex={0} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + onSelectWorkspace(entry.id); + } + }} + > + + New Agent +
)} - {isDraftNewAgent && ( -
onSelectWorkspace(entry.id)} - role="button" - tabIndex={0} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - onSelectWorkspace(entry.id); - } - }} - > - - New Agent -
- )} - {worktrees.length > 0 && ( - - )} - {showThreadList && ( - - )} - {showThreadLoader && } -
- ); - })} -
- ); - })} - {!filteredGroupedWorkspaces.length && ( -
- {isSearchActive - ? "No projects match your search." - : "Add a workspace to start."} -
- )} -
+ {worktrees.length > 0 && ( + + )} + {showThreadList && ( + + )} + {showThreadLoader && } + + ); + })} + + ); + })} + {!filteredGroupedWorkspaces.length && ( +
+ {isSearchActive + ? "No projects match your search." + : "Add a workspace to start."} +
+ )} +
+ )}
Date: Thu, 12 Feb 2026 09:47:10 +0800 Subject: [PATCH 2/2] Silence expected Forge execution warning in plan test --- src/features/forge/components/Forge.plans.test.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/features/forge/components/Forge.plans.test.tsx b/src/features/forge/components/Forge.plans.test.tsx index 88155512a6..9cdee85641 100644 --- a/src/features/forge/components/Forge.plans.test.tsx +++ b/src/features/forge/components/Forge.plans.test.tsx @@ -641,6 +641,7 @@ describe("Forge plans", () => { }); it("stops execution when a phase reports a terminal non-completed status", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const runPhaseChecks = vi.fn().mockResolvedValue({ ok: true, results: [], @@ -693,7 +694,14 @@ describe("Forge plans", () => { await waitFor(() => expect(screen.getByRole("alert").textContent).toContain('terminal status "failed"'), ); + expect(warnSpy).toHaveBeenCalledWith( + "Forge execution failed.", + expect.objectContaining({ + error: expect.any(Error), + }), + ); expect(runPhaseChecks).not.toHaveBeenCalled(); expect(screen.getByRole("button", { name: "Resume plan" })).toBeTruthy(); + warnSpy.mockRestore(); }); });