diff --git a/apps/examples/desktop-app/webview/components/agenda-ui-hidden.test.tsx b/apps/examples/desktop-app/webview/components/agenda-ui-hidden.test.tsx new file mode 100644 index 0000000000..28afd186a1 --- /dev/null +++ b/apps/examples/desktop-app/webview/components/agenda-ui-hidden.test.tsx @@ -0,0 +1,131 @@ +// @vitest-environment jsdom + +// Covers the shipped state of the Agenda feature: with AGENDA_UI_ENABLED +// false (the real flag value), the sidebar Agenda toggle and the welcome +// quick actions stay hidden and no agenda commands are issued. The +// feature-flag mock in agent-sidebar.test.tsx and welcome-chat.test.tsx +// forces the flag on to keep exercising the dormant UI. + +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AgentSidebar } from "@/components/agent-sidebar"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { WelcomeScreen } from "@/components/views/chat/welcome-chat"; +import { WorkspaceProvider } from "@/contexts/workspace-context"; +import type { UseSessionHistoryResult } from "@/hooks/use-session-history"; + +const desktopMocks = vi.hoisted(() => ({ + invoke: vi.fn(), + listAgendaTasks: vi.fn(), + getAgendaAutomationPolicy: vi.fn(), + subscribe: vi.fn(() => () => undefined), + subscribeTransportState: vi.fn(() => () => undefined), +})); +vi.mock("@/lib/desktop-client", () => ({ desktopClient: desktopMocks })); + +let container: HTMLDivElement; +let root: Root; + +beforeEach(() => { + Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + desktopMocks.invoke.mockRejectedValue(new Error("not available in test")); + Object.defineProperty(window, "matchMedia", { + configurable: true, + value: vi.fn(() => ({ + matches: false, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + })), + }); + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(async () => { + await act(async () => root.unmount()); + container.remove(); + vi.restoreAllMocks(); +}); + +function makeSessionHistory(): UseSessionHistoryResult { + return { + deleteThread: vi.fn(), + forkThread: vi.fn(), + hasLoadedHistory: true, + isLoadingMore: false, + loadOlderSessions: vi.fn(), + loadMoreSessions: vi.fn(), + mayHaveMoreSessions: false, + openThread: vi.fn(), + pendingAction: null, + renameThread: vi.fn(), + threads: [], + unreadSessionIds: new Set(), + } as unknown as UseSessionHistoryResult; +} + +describe("Agenda UI hidden by default", () => { + it("renders the sidebar without the Agenda toggle and issues no agenda commands", async () => { + await act(async () => { + root.render( + + + , + ); + await Promise.resolve(); + }); + + expect(container.querySelector('[aria-label="Show Agenda"]')).toBeNull(); + expect( + container.querySelector('[aria-label="New Session"]'), + ).not.toBeNull(); + expect(container.querySelector('[aria-label="Agenda"]')).toBeNull(); + expect(desktopMocks.listAgendaTasks).not.toHaveBeenCalled(); + expect(desktopMocks.getAgendaAutomationPolicy).not.toHaveBeenCalled(); + }); + + it("renders the welcome screen without agenda quick actions or agenda fetches", async () => { + await act(async () => { + root.render( + ["/projects/project-1"]), + refreshWorkspaces: vi.fn(async () => undefined), + switchWorkspace: vi.fn(async () => true), + pickWorkspaceDirectory: vi.fn(async () => null), + selectChat: vi.fn(async () => true), + }} + > + ({ + current: "main", + branches: ["main"], + }))} + onSwitchGitBranch={vi.fn(async () => true)} + /> + , + ); + await Promise.resolve(); + }); + + expect(container.querySelector("[data-welcome-hero]")).not.toBeNull(); + expect(desktopMocks.listAgendaTasks).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/examples/desktop-app/webview/components/agent-sidebar.test.tsx b/apps/examples/desktop-app/webview/components/agent-sidebar.test.tsx index bd883244d1..42272901a3 100644 --- a/apps/examples/desktop-app/webview/components/agent-sidebar.test.tsx +++ b/apps/examples/desktop-app/webview/components/agent-sidebar.test.tsx @@ -30,6 +30,10 @@ const desktopMocks = vi.hoisted(() => ({ })); const { invoke } = desktopMocks; vi.mock("@/lib/desktop-client", () => ({ desktopClient: desktopMocks })); +// The Agenda UI ships hidden for now; these tests force the flag on so they +// keep guarding the dormant feature. agenda-ui-hidden.test.tsx covers the +// shipped (hidden) state. +vi.mock("@/lib/feature-flags", () => ({ AGENDA_UI_ENABLED: true })); let container: HTMLDivElement; let root: Root; diff --git a/apps/examples/desktop-app/webview/components/agent-sidebar.tsx b/apps/examples/desktop-app/webview/components/agent-sidebar.tsx index 98644cb5d4..4f2ce8dab5 100644 --- a/apps/examples/desktop-app/webview/components/agent-sidebar.tsx +++ b/apps/examples/desktop-app/webview/components/agent-sidebar.tsx @@ -114,6 +114,7 @@ import { productNameForVersion, } from "@/lib/app-channel"; import { desktopClient } from "@/lib/desktop-client"; +import { AGENDA_UI_ENABLED } from "@/lib/feature-flags"; import { readModelSelectionStorageFromWindow } from "@/lib/model-selection"; import { ALL_SESSION_SOURCES, @@ -326,9 +327,11 @@ export function AgentSidebar({ workspaceRoot: agendaWorkspaceRoot, limit: 200, }, - view !== "settings", + AGENDA_UI_ENABLED && view !== "settings", + ); + const agendaAutomation = useAgendaAutomation( + AGENDA_UI_ENABLED && view !== "settings", ); - const agendaAutomation = useAgendaAutomation(view !== "settings"); useEffect(() => { if (view === "settings") { @@ -795,31 +798,37 @@ export function AgentSidebar({ {!isCollapsed ? (
- + {AGENDA_UI_ENABLED ? ( + + ) : null}
- ) : sidebarContent === "agenda" ? ( + ) : AGENDA_UI_ENABLED && sidebarContent === "agenda" ? ( ({ subscribeTransportState: vi.fn(() => () => undefined), }, })); +// The Agenda UI ships hidden for now; these tests force the flag on so they +// keep guarding the dormant feature. agenda-ui-hidden.test.tsx covers the +// shipped (hidden) state. +vi.mock("@/lib/feature-flags", () => ({ AGENDA_UI_ENABLED: true })); let container: HTMLDivElement; let root: Root; diff --git a/apps/examples/desktop-app/webview/components/views/chat/welcome-chat.tsx b/apps/examples/desktop-app/webview/components/views/chat/welcome-chat.tsx index c1ca94dd6c..18a98b5dc1 100644 --- a/apps/examples/desktop-app/webview/components/views/chat/welcome-chat.tsx +++ b/apps/examples/desktop-app/webview/components/views/chat/welcome-chat.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { AgendaTaskReviewDialog } from "@/components/agenda-task-review-dialog"; import { useWorkspace } from "@/contexts/workspace-context"; import { isAgendaTaskExpired, useAgendaTasks } from "@/hooks/use-agenda-tasks"; +import { AGENDA_UI_ENABLED } from "@/lib/feature-flags"; import { cn } from "@/lib/utils"; import { SessionContent } from "./session-content"; import { WelcomeHero } from "./welcome-hero"; @@ -49,7 +50,7 @@ export function WelcomeScreen({ statuses: ["pending_approval", "approved", "in_progress", "failed"], limit: 8, }, - active && workspaceRoot.trim().length > 0, + AGENDA_UI_ENABLED && active && workspaceRoot.trim().length > 0, ); const [runningTaskId, setRunningTaskId] = useState(null); const [reviewTask, setReviewTask] = useState(null); @@ -168,7 +169,7 @@ export function WelcomeScreen({ {active ? composer : {composer}} - {active ? ( + {active && AGENDA_UI_ENABLED ? ( <>