diff --git a/packages/dashboard/app/components/__tests__/AppModals.test.tsx b/packages/dashboard/app/components/__tests__/AppModals.test.tsx
index ba44e44821..335abeb76e 100644
--- a/packages/dashboard/app/components/__tests__/AppModals.test.tsx
+++ b/packages/dashboard/app/components/__tests__/AppModals.test.tsx
@@ -51,14 +51,6 @@ vi.mock("../FileBrowserModal", () => ({
FileBrowserModal: () => null,
}));
-const mockTodoModalProps = vi.fn();
-vi.mock("../TodoModal", () => ({
- TodoModal: (props: any) => {
- mockTodoModalProps(props);
- return null;
- },
-}));
-
vi.mock("../UsageIndicator", () => ({
UsageIndicator: () => null,
}));
@@ -169,7 +161,6 @@ describe("AppModals", () => {
terminalInitialCommandGeneration: 0,
scriptsOpen: false,
filesOpen: false,
- todosOpen: false,
fileBrowserWorkspace: "project",
fileBrowserInitialFile: null,
usageOpen: false,
@@ -206,8 +197,6 @@ describe("AppModals", () => {
runScript: vi.fn(),
openFiles: vi.fn(),
closeFiles: vi.fn(),
- openTodos: vi.fn(),
- closeTodos: vi.fn(),
setFileWorkspace: vi.fn(),
openUsage: vi.fn(),
closeUsage: vi.fn(),
@@ -246,7 +235,6 @@ describe("AppModals", () => {
mockModelOnboardingModalProps.mockClear();
mockActivityLogModalProps.mockClear();
mockSettingsModalProps.mockClear();
- mockTodoModalProps.mockClear();
});
it("renders without crashing", () => {
@@ -270,50 +258,6 @@ describe("AppModals", () => {
expect(document.body).toBeDefined();
});
- it("renders TodoModal when todosOpen is true", () => {
- render(
-
- );
-
- expect(mockTodoModalProps).toHaveBeenCalledTimes(1);
- });
-
- it("does not render TodoModal when todosOpen is false", () => {
- render(
-
- );
-
- expect(mockTodoModalProps).not.toHaveBeenCalled();
- });
-
it("passes the live board task snapshot into the open detail modal while preserving prompt data", async () => {
const manager = {
...mockModalManager,
diff --git a/packages/dashboard/app/components/__tests__/Header.test.tsx b/packages/dashboard/app/components/__tests__/Header.test.tsx
index f47827c2f5..55ba44aca3 100644
--- a/packages/dashboard/app/components/__tests__/Header.test.tsx
+++ b/packages/dashboard/app/components/__tests__/Header.test.tsx
@@ -251,7 +251,7 @@ describe("Header", () => {
});
it("shows the Todos entry in view overflow when todos are enabled", () => {
- renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: true });
+ renderHeader({ onChangeView: noop, todosEnabled: true });
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
expect(screen.getByTestId("view-overflow-todos")).toBeInTheDocument();
});
@@ -559,7 +559,7 @@ describe("Header", () => {
describe("todos navigation", () => {
for (const tier of ["desktop", "tablet"] as const) {
it(`shows Todos only in More views and Mailbox only top-level on ${tier}`, () => {
- renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: true }, tier);
+ renderHeader({ onChangeView: noop, todosEnabled: true }, tier);
expect(screen.queryByTestId("todos-toggle-btn")).toBeNull();
expect(screen.getByTitle("Mailbox view")).toBeInTheDocument();
@@ -572,17 +572,25 @@ describe("Header", () => {
}
it("does not show Todos entry in More views when disabled", () => {
- renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: false }, "desktop");
+ renderHeader({ onChangeView: noop, todosEnabled: false }, "desktop");
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
expect(screen.queryByTestId("view-overflow-todos")).toBeNull();
});
- it("calls onOpenTodos from More views", () => {
- const onOpenTodos = vi.fn();
- renderHeader({ onChangeView: noop, onOpenTodos, todosEnabled: true }, "desktop");
- fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
- fireEvent.click(screen.getByTestId("view-overflow-todos"));
- expect(onOpenTodos).toHaveBeenCalled();
+ it("routes to todos from More views and marks active state", () => {
+ const onChangeView = vi.fn();
+ renderHeader({ onChangeView, view: "todos", todosEnabled: true }, "desktop");
+
+ const trigger = screen.getByTestId("view-toggle-overflow-trigger");
+ expect(trigger.className).toContain("active");
+ fireEvent.click(trigger);
+
+ const todosItem = screen.getByTestId("view-overflow-todos");
+ expect(todosItem.className).toContain("active");
+ fireEvent.click(todosItem);
+
+ expect(onChangeView).toHaveBeenCalledWith("todos");
+ expect(screen.queryByTestId("view-overflow-todos")).toBeNull();
});
});
diff --git a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx
index 7002c9928d..ad81a7b01b 100644
--- a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx
+++ b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx
@@ -401,10 +401,9 @@ describe("LeftSidebarNav", () => {
expect(window.localStorage.getItem("fusion:left-sidebar-width")).toBe("336");
});
- it("routes clicks to view changes, todos callback, and settings callback", () => {
- const onOpenTodos = vi.fn();
+ it("routes clicks to view changes, todos view, and settings callback", () => {
const onOpenSettings = vi.fn();
- const { onChangeView } = renderSidebar({ todosEnabled: true, onOpenTodos, onOpenSettings });
+ const { onChangeView } = renderSidebar({ todosEnabled: true, onOpenSettings });
fireEvent.click(screen.getByTestId("sidebar-nav-list"));
expect(onChangeView).toHaveBeenCalledWith("list");
@@ -413,7 +412,7 @@ describe("LeftSidebarNav", () => {
expect(onChangeView).toHaveBeenCalledWith("plugin:fusion-plugin-overflow:overflow-view");
fireEvent.click(screen.getByTestId("sidebar-nav-todos"));
- expect(onOpenTodos).toHaveBeenCalledOnce();
+ expect(onChangeView).toHaveBeenCalledWith("todos");
fireEvent.click(screen.getByTestId("sidebar-nav-settings"));
expect(onOpenSettings).toHaveBeenCalledOnce();
diff --git a/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx b/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx
index 6f9e5e37a2..4533d4bda2 100644
--- a/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx
+++ b/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx
@@ -222,12 +222,11 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-more-item-plugin-fusion-plugin-spacing-check-wide")).toBeDefined();
});
- it("keeps Todos in the mobile More sheet when todoView is enabled", () => {
- const onOpenTodos = vi.fn();
+ it("keeps Todos in the mobile More sheet and routes to the todos view", () => {
+ const props = createDefaultProps();
render(
,
);
@@ -235,7 +234,7 @@ describe("MobileNavBar", () => {
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
fireEvent.click(screen.getByTestId("mobile-more-item-todos"));
- expect(onOpenTodos).toHaveBeenCalled();
+ expect(props.onChangeView).toHaveBeenCalledWith("todos");
});
it("Mailbox is a primary tab and is not duplicated in the More sheet", () => {
@@ -251,7 +250,6 @@ describe("MobileNavBar", () => {
render(
,
);
@@ -262,6 +260,18 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-more-item-todos")).toBeInTheDocument();
});
+ it("marks the mobile More tab active for the todos view", () => {
+ render(
+
,
+ );
+
+ expect(screen.getByTestId("mobile-nav-tab-more")).toHaveClass("mobile-nav-tab--active");
+ });
+
it("shows secrets in More and routes to secrets view", () => {
const props = createDefaultProps();
render(
);
diff --git a/packages/dashboard/app/components/__tests__/TodoModal.test.tsx b/packages/dashboard/app/components/__tests__/TodoModal.test.tsx
deleted file mode 100644
index 932d10b2c9..0000000000
--- a/packages/dashboard/app/components/__tests__/TodoModal.test.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-import { TodoModal } from "../TodoModal";
-
-const mockTodoView = vi.fn();
-const mockUseMobileKeyboard = vi.fn();
-const mockUseViewportMode = vi.fn();
-
-vi.mock("../TodoView", () => ({
- TodoView: (props: unknown) => {
- mockTodoView(props);
- return
Todo content
;
- },
-}));
-
-vi.mock("../../hooks/useMobileKeyboard", () => ({
- useMobileKeyboard: (...args: unknown[]) => mockUseMobileKeyboard(...args),
-}));
-
-vi.mock("../../hooks/useViewportMode", () => ({
- MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
- getViewportMode: () => mockUseViewportMode(),
- isMobileViewport: () => mockUseViewportMode() === "mobile",
- useViewportMode: (...args: unknown[]) => mockUseViewportMode(...args),
-}));
-
-describe("TodoModal", () => {
- const onClose = vi.fn();
- const addToast = vi.fn();
-
- beforeEach(() => {
- vi.clearAllMocks();
- mockUseViewportMode.mockReturnValue("desktop");
- mockUseMobileKeyboard.mockReturnValue({
- keyboardOverlap: 0,
- viewportHeight: null,
- viewportOffsetTop: 0,
- keyboardOpen: false,
- });
- });
-
- it("renders modal dialog semantics and header content", () => {
- render(
);
-
- expect(screen.getByRole("dialog")).toHaveAttribute("aria-modal", "true");
- expect(screen.getByRole("heading", { name: "Todos" })).toBeInTheDocument();
- expect(screen.getByText("Manage reusable todo lists for your project.")).toBeInTheDocument();
- });
-
- it("closes on Escape", () => {
- render(
);
- fireEvent.keyDown(document, { key: "Escape" });
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- it("closes on overlay backdrop click", () => {
- render(
);
- const overlay = screen.getByRole("dialog");
- fireEvent.mouseDown(overlay);
- fireEvent.mouseUp(overlay);
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- it("closes from close button", () => {
- render(
);
- fireEvent.click(screen.getByRole("button", { name: "Close" }));
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- it("passes expected props through the lazy-loaded TodoView", async () => {
- const onPlanningMode = vi.fn();
- render(
-
,
- );
-
- expect(await screen.findByTestId("todo-view-content")).toBeInTheDocument();
- await waitFor(() => {
- expect(mockTodoView).toHaveBeenCalledWith(
- expect.objectContaining({
- projectId: "proj-1",
- addToast,
- onPlanningMode,
- onClose,
- mobileKeyboardActive: false,
- }),
- );
- });
- });
-
- describe("mobile keyboard behavior", () => {
- it("applies CSS variables when keyboard is open on mobile", () => {
- mockUseViewportMode.mockReturnValue("mobile");
- mockUseMobileKeyboard.mockReturnValue({
- keyboardOverlap: 250,
- viewportHeight: 450,
- viewportOffsetTop: 40,
- keyboardOpen: true,
- });
-
- render(
);
- const modal = screen.getByRole("dialog").querySelector(".modal.todo-modal");
- expect(modal).toBeTruthy();
-
- const style = (modal as HTMLElement).style;
- expect(style.getPropertyValue("--keyboard-overlap")).toBe("250px");
- expect(style.getPropertyValue("--vv-offset-top")).toBe("40px");
- expect(style.getPropertyValue("--vv-height")).toBe("450px");
- expect(mockTodoView).toHaveBeenCalledWith(expect.objectContaining({ mobileKeyboardActive: true }));
- });
-
- it("does not apply keyboard CSS variables when keyboard is closed", () => {
- mockUseViewportMode.mockReturnValue("mobile");
- mockUseMobileKeyboard.mockReturnValue({
- keyboardOverlap: 0,
- viewportHeight: null,
- viewportOffsetTop: 0,
- keyboardOpen: false,
- });
-
- render(
);
- const modal = screen.getByRole("dialog").querySelector(".modal.todo-modal");
- expect(modal).toBeTruthy();
-
- const style = (modal as HTMLElement).style;
- expect(style.getPropertyValue("--keyboard-overlap")).toBe("");
- expect(style.getPropertyValue("--vv-offset-top")).toBe("");
- expect(style.getPropertyValue("--vv-height")).toBe("");
- expect(mockTodoView).toHaveBeenCalledWith(expect.objectContaining({ mobileKeyboardActive: false }));
- });
- });
-});
diff --git a/packages/dashboard/app/components/__tests__/TodoView.mobile-css.test.ts b/packages/dashboard/app/components/__tests__/TodoView.mobile-css.test.ts
index 6aa3c05493..cd186a9043 100644
--- a/packages/dashboard/app/components/__tests__/TodoView.mobile-css.test.ts
+++ b/packages/dashboard/app/components/__tests__/TodoView.mobile-css.test.ts
@@ -16,11 +16,4 @@ describe("TodoView action row CSS contract", () => {
expect(css).toMatch(/\.todo-item-actions\s*\{[^}]*margin-left:\s*calc\(var\(--space-lg\) \+ var\(--space-sm\)\);/);
expect(css).toMatch(/@media \(max-width:\s*768px\)[^{]*\{[\s\S]*\.todo-item-actions\s*\{[^}]*opacity:\s*1;[^}]*\}/);
});
-
- it("applies keyboard-active mobile layout containment rules", () => {
- const css = loadAllAppCss();
-
- expect(css).toMatch(/@media \(max-width:\s*768px\)[^{]*\{[\s\S]*\.todo-view--mobile-keyboard-active \.todo-view-layout\s*\{[^}]*height:\s*100%;[^}]*\}/);
- expect(css).toMatch(/@media \(max-width:\s*768px\)[^{]*\{[\s\S]*\.todo-view--mobile-keyboard-active \.todo-view-main\s*\{[^}]*overscroll-behavior:\s*contain;[^}]*\}/);
- });
});
diff --git a/packages/dashboard/app/components/__tests__/TodoView.test.tsx b/packages/dashboard/app/components/__tests__/TodoView.test.tsx
index f831c51270..fce458306b 100644
--- a/packages/dashboard/app/components/__tests__/TodoView.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TodoView.test.tsx
@@ -77,17 +77,18 @@ describe("TodoView", () => {
mockUseTodoLists.mockReturnValue(createMockTodoLists());
});
+ it("renders the docked view header", () => {
+ render(
);
+ expect(screen.getByRole("heading", { level: 2, name: "Todos" })).toBeInTheDocument();
+ expect(screen.getByText("Manage reusable todo lists for your project.")).toBeInTheDocument();
+ });
+
it("renders sidebar with list names", () => {
render(
);
expect(screen.getByTestId("todo-list-list-1")).toHaveTextContent("My List");
expect(screen.getByTestId("todo-list-list-2")).toHaveTextContent("Work Tasks");
});
- it("applies keyboard-active root class when mobileKeyboardActive is true", () => {
- render(
);
- expect(screen.getByTestId("todo-view-root")).toHaveClass("todo-view--mobile-keyboard-active");
- });
-
it("renders only items for the selected list", () => {
render(
);
expect(screen.getByText("Buy groceries")).toBeInTheDocument();
diff --git a/packages/dashboard/app/components/__tests__/auto-merge-toggle-blank.mobile-integration.test.tsx b/packages/dashboard/app/components/__tests__/auto-merge-toggle-blank.mobile-integration.test.tsx
index 11b290b0b7..c34663ed1d 100644
--- a/packages/dashboard/app/components/__tests__/auto-merge-toggle-blank.mobile-integration.test.tsx
+++ b/packages/dashboard/app/components/__tests__/auto-merge-toggle-blank.mobile-integration.test.tsx
@@ -325,7 +325,6 @@ function AppShellMobileHarness({ tasks }: { tasks: Task[] }) {
onOpenScripts={vi.fn()}
onToggleTerminal={vi.fn()}
onOpenFiles={vi.fn()}
- onOpenTodos={vi.fn()}
onOpenGitHubImport={vi.fn()}
onOpenPlanning={vi.fn()}
onResumePlanning={vi.fn()}
diff --git a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx
index 67307ab4f8..d13b18d083 100644
--- a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx
+++ b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx
@@ -25,7 +25,7 @@ const defaultSettings: Settings = {
worktreeInitCommand: "",
testCommand: "",
buildCommand: "",
- experimentalFeatures: { insights: true, roadmap: true, skillsView: true, agentsView: true, evalsView: true },
+ experimentalFeatures: { insights: true, roadmap: true, skillsView: true, agentsView: true, evalsView: true, todoView: true },
};
const mockSubscribeSse = vi.fn((..._args: any[]) => vi.fn());
@@ -85,6 +85,7 @@ const mockUseTasks = vi.fn(() => ({
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
+ refreshTasks: vi.fn(),
}));
vi.mock("../../hooks/useTasks", () => ({
@@ -358,6 +359,7 @@ describe("Navigation history integration", () => {
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
+ refreshTasks: vi.fn(),
}));
mockProjectsState.projects = [];
mockProjectsState.loading = false;
@@ -489,6 +491,29 @@ describe("Navigation history integration", () => {
expect((window.history.pushState as any).mock.calls.length).toBeGreaterThan(pushCallsBefore);
});
+ it("pushes history entry and reverts when switching to todos from overflow", async () => {
+ localStorage.setItem("kb-dashboard-view-mode", "project");
+ const taskViewStorageKey = scopedKey("kb-dashboard-task-view", DEFAULT_PROJECT_ID);
+ localStorage.setItem(taskViewStorageKey, "board");
+
+ await renderAppAndWait();
+
+ const pushCallsBefore = (window.history.pushState as any).mock.calls.length;
+ fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
+ fireEvent.click(screen.getByTestId("view-overflow-todos"));
+
+ await waitFor(() => {
+ expect(screen.getByTestId("todo-view")).toBeTruthy();
+ });
+ expect((window.history.pushState as any).mock.calls.length).toBeGreaterThan(pushCallsBefore);
+
+ dispatchPopState({ navIndex: 0 });
+ await waitFor(() => {
+ expect(screen.queryByTestId("todo-view")).toBeNull();
+ expect(screen.getByTestId("board-view")).toBeTruthy();
+ });
+ });
+
// 4. Desktop: popstate reverts view changes
it("reverts view change on popstate in desktop mode", async () => {
localStorage.setItem("kb-dashboard-view-mode", "project");
@@ -529,6 +554,7 @@ describe("Navigation history integration", () => {
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
+ refreshTasks: vi.fn(),
}));
await renderMobileAppAndWait();
@@ -561,6 +587,7 @@ describe("Navigation history integration", () => {
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
+ refreshTasks: vi.fn(),
}));
await renderMobileAppAndWait();
diff --git a/packages/dashboard/app/hooks/useMobileScrollLock.ts b/packages/dashboard/app/hooks/useMobileScrollLock.ts
index bbe961134e..91e7bc7679 100644
--- a/packages/dashboard/app/hooks/useMobileScrollLock.ts
+++ b/packages/dashboard/app/hooks/useMobileScrollLock.ts
@@ -46,7 +46,7 @@ export function isIOS(): boolean {
* area aligned with the layout viewport.
*
* Reference counting matters because multiple overlays can be open at once
- * (e.g. a confirm dialog over a TodoModal) — only the outermost lock should
+ * (e.g. a confirm dialog over another modal) — only the outermost lock should
* actually mutate styles, so an inner unmount doesn't release the lock for
* an outer overlay that is still open.
*/
diff --git a/packages/dashboard/app/hooks/useModalManager.ts b/packages/dashboard/app/hooks/useModalManager.ts
index 4c9973000e..2c3e7b5ec8 100644
--- a/packages/dashboard/app/hooks/useModalManager.ts
+++ b/packages/dashboard/app/hooks/useModalManager.ts
@@ -52,7 +52,6 @@ export interface ModalManager {
terminalInitialCommand: string | undefined;
terminalInitialCommandGeneration: number;
filesOpen: boolean;
- todosOpen: boolean;
fileBrowserWorkspace: string;
fileBrowserInitialFile: string | null;
activityLogOpen: boolean;
@@ -114,8 +113,6 @@ export interface ModalManager {
openFiles: (workspace?: string, initialFile?: string | null) => void;
closeFiles: () => void;
- openTodos: () => void;
- closeTodos: () => void;
setFileWorkspace: (workspace: string) => void;
openActivityLog: () => void;
@@ -184,7 +181,6 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
const [terminalInitialCommand, setTerminalInitialCommand] = useState
(undefined);
const [terminalInitialCommandGeneration, setTerminalInitialCommandGeneration] = useState(0);
const [filesOpen, setFilesOpen] = useState(false);
- const [todosOpen, setTodosOpen] = useState(false);
const [fileBrowserWorkspace, setFileBrowserWorkspace] = useState("project");
const [fileBrowserInitialFile, setFileBrowserInitialFile] = useState(null);
const [activityLogOpen, setActivityLogOpen] = useState(false);
@@ -207,7 +203,6 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
isSubtaskOpen ||
terminalOpen ||
filesOpen ||
- todosOpen ||
activityLogOpen ||
gitManagerOpen ||
workflowEditorOpen ||
@@ -376,8 +371,6 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
setFilesOpen(false);
setFileBrowserInitialFile(null);
}, []);
- const openTodos = useCallback(() => setTodosOpen(true), []);
- const closeTodos = useCallback(() => setTodosOpen(false), []);
const setFileWorkspace = useCallback((workspace: string) => {
if (typeof workspace === "string" && workspace) {
setFileBrowserWorkspace(workspace);
@@ -468,7 +461,6 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
terminalInitialCommand,
terminalInitialCommandGeneration,
filesOpen,
- todosOpen,
fileBrowserWorkspace,
fileBrowserInitialFile,
activityLogOpen,
@@ -511,8 +503,6 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
closeTerminal,
openFiles,
closeFiles,
- openTodos,
- closeTodos,
setFileWorkspace,
openActivityLog,
closeActivityLog,
diff --git a/packages/dashboard/app/hooks/useViewState.ts b/packages/dashboard/app/hooks/useViewState.ts
index 415d04dc9d..122574a47b 100644
--- a/packages/dashboard/app/hooks/useViewState.ts
+++ b/packages/dashboard/app/hooks/useViewState.ts
@@ -5,7 +5,7 @@ import { getScopedItem, setScopedItem } from "../utils/projectStorage";
import { getPluginViewId, isPluginViewId, isPluginViewRegistered } from "../plugins/pluginViewRegistry";
export type ViewMode = "overview" | "project";
-export type BuiltInTaskView = "board" | "list" | "graph" | "agents" | "missions" | "chat" | "documents" | "research" | "evals" | "goalsView" | "skills" | "mailbox" | "insights" | "memory" | "command-center" | "secrets" | "devserver" | "dev-server" | "stash-recovery" | "pull-requests";
+export type BuiltInTaskView = "board" | "list" | "graph" | "agents" | "missions" | "chat" | "documents" | "research" | "evals" | "goalsView" | "todos" | "skills" | "mailbox" | "insights" | "memory" | "command-center" | "secrets" | "devserver" | "dev-server" | "stash-recovery" | "pull-requests";
export type PluginTaskView = `plugin:${string}:${string}`;
export type TaskView = BuiltInTaskView | PluginTaskView;
@@ -20,6 +20,11 @@ const BUILT_IN_TASK_VIEWS: readonly BuiltInTaskView[] = [
"research",
"evals",
"goalsView",
+ /*
+ FNXC:ViewState 2026-06-21-09:14:
+ FN-6829 promotes project Todos from modal-only state into the persisted built-in task-view registry so dashboard navigation can dock it in the right content area.
+ */
+ "todos",
"skills",
"mailbox",