feat(KB-659): add agents view toggle to view selector

- Add agents view option to view selector in Header component

- Update App.tsx to handle agents view routing

- Add tests for view selector toggle behavior
This commit is contained in:
gsxdsm
2026-04-01 06:59:58 -07:00
parent aa1043bfdd
commit 1ee99b8729
3 changed files with 59 additions and 6 deletions

View File

@@ -21,6 +21,7 @@ import { ScheduledTasksModal } from "./components/ScheduledTasksModal";
import { ActivityLogModal } from "./components/ActivityLogModal";
import { WorkflowStepManager } from "./components/WorkflowStepManager";
import { AgentListModal } from "./components/AgentListModal";
import { AgentsView } from "./components/AgentsView";
import { useTasks } from "./hooks/useTasks";
import { ToastProvider, useToast } from "./hooks/useToast";
import { useTheme } from "./hooks/useTheme";
@@ -49,11 +50,11 @@ function AppInner() {
const [autoMerge, setAutoMerge] = useState(true);
const [globalPaused, setGlobalPaused] = useState(false);
const [enginePaused, setEnginePaused] = useState(false);
const [view, setView] = useState<"board" | "list">(() => {
const [view, setView] = useState<"board" | "list" | "agents">(() => {
// Initialize from localStorage if available
if (typeof window !== "undefined") {
const saved = localStorage.getItem("kb-dashboard-view");
if (saved === "list" || saved === "board") {
if (saved === "list" || saved === "board" || saved === "agents") {
return saved;
}
}
@@ -113,7 +114,7 @@ function AppInner() {
localStorage.setItem("kb-dashboard-view", view);
}, [view]);
const handleChangeView = useCallback((newView: "board" | "list") => {
const handleChangeView = useCallback((newView: "board" | "list" | "agents") => {
setView(newView);
}, []);
@@ -276,7 +277,9 @@ function AppInner() {
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
/>
{view === "board" ? (
{view === "agents" ? (
<AgentsView addToast={addToast} />
) : view === "board" ? (
<Board
tasks={tasks}
maxConcurrent={maxConcurrent}

View File

@@ -34,8 +34,8 @@ interface HeaderProps {
enginePaused?: boolean;
onToggleGlobalPause?: () => void;
onToggleEnginePause?: () => void;
view?: "board" | "list";
onChangeView?: (view: "board" | "list") => void;
view?: "board" | "list" | "agents";
onChangeView?: (view: "board" | "list" | "agents") => void;
searchQuery?: string;
onSearchChange?: (query: string) => void;
}
@@ -238,6 +238,15 @@ export function Header({
>
<List size={16} />
</button>
<button
className={`view-toggle-btn${view === "agents" ? " active" : ""}`}
onClick={() => onChangeView("agents")}
title="Agents view"
aria-label="Agents view"
aria-pressed={view === "agents"}
>
<Bot size={16} />
</button>
</div>
)}

View File

@@ -210,6 +210,47 @@ describe("Header", () => {
expect(boardBtn.getAttribute("aria-pressed")).toBe("false");
});
// ── Agents View Toggle ──────────────────────────────────────────
it("renders agents view button in view toggle when onChangeView is provided", () => {
const onChangeView = vi.fn();
render(<Header view="board" onChangeView={onChangeView} />);
const agentsBtn = screen.getByTitle("Agents view");
expect(agentsBtn).toBeDefined();
});
it("calls onChangeView with 'agents' when agents view button is clicked", () => {
const onChangeView = vi.fn();
render(<Header view="board" onChangeView={onChangeView} />);
const agentsBtn = screen.getByTitle("Agents view");
fireEvent.click(agentsBtn);
expect(onChangeView).toHaveBeenCalledWith("agents");
});
it("marks agents view button as active when view is 'agents'", () => {
const onChangeView = vi.fn();
render(<Header view="agents" onChangeView={onChangeView} />);
const agentsBtn = screen.getByTitle("Agents view");
expect(agentsBtn.className).toContain("active");
expect(agentsBtn.getAttribute("aria-pressed")).toBe("true");
});
it("does not mark agents view button as active when view is 'board'", () => {
const onChangeView = vi.fn();
render(<Header view="board" onChangeView={onChangeView} />);
const agentsBtn = screen.getByTitle("Agents view");
expect(agentsBtn.className).not.toContain("active");
expect(agentsBtn.getAttribute("aria-pressed")).toBe("false");
});
it("does not mark board view button as active when view is 'agents'", () => {
const onChangeView = vi.fn();
render(<Header view="agents" onChangeView={onChangeView} />);
const boardBtn = screen.getByTitle("Board view");
expect(boardBtn.className).not.toContain("active");
expect(boardBtn.getAttribute("aria-pressed")).toBe("false");
});
// ── Terminal Button ─────────────────────────────────────────────
it("renders terminal button with correct title", () => {