diff --git a/apps/web/src/routes/initiatives/index.test.tsx b/apps/web/src/routes/initiatives/index.test.tsx
new file mode 100644
index 0000000..8905cf5
--- /dev/null
+++ b/apps/web/src/routes/initiatives/index.test.tsx
@@ -0,0 +1,104 @@
+// @vitest-environment happy-dom
+import "@testing-library/jest-dom/vitest";
+import { render, screen, fireEvent } from "@testing-library/react";
+import { vi, describe, it, expect, beforeEach } from "vitest";
+
+// ── Mocks ────────────────────────────────────────────────────────────────────
+
+vi.mock("@tanstack/react-router", () => ({
+ createFileRoute: () => () => ({}),
+ useNavigate: () => vi.fn(),
+}));
+
+vi.mock("@/lib/trpc", () => ({
+ trpc: {
+ listProjects: { useQuery: () => ({ data: [] }) },
+ useUtils: () => ({}),
+ },
+}));
+
+vi.mock("@/hooks", () => ({
+ useLiveUpdates: vi.fn(),
+ INITIATIVE_LIST_RULES: {},
+}));
+
+vi.mock("@/components/InitiativeList", () => ({
+ InitiativeList: () =>
,
+}));
+
+vi.mock("@/components/CreateInitiativeDialog", () => ({
+ CreateInitiativeDialog: () => null,
+}));
+
+// ── Import after mocks ───────────────────────────────────────────────────────
+
+import { DashboardPage } from "@/routes/initiatives/index";
+
+// ── Helpers ──────────────────────────────────────────────────────────────────
+
+function renderPage() {
+ return render();
+}
+
+// ── Tests ────────────────────────────────────────────────────────────────────
+
+describe("DashboardPage — statusFilter default and sessionStorage", () => {
+ beforeEach(() => {
+ sessionStorage.clear();
+ vi.clearAllMocks();
+ });
+
+ it("defaults to 'active' when sessionStorage is empty", () => {
+ renderPage();
+ const select = screen.getByRole("combobox", { name: /status/i }) as HTMLSelectElement;
+ expect(select.value).toBe("active");
+ });
+
+ it("displays the 'Active' option as selected on first render", () => {
+ renderPage();
+ const select = screen.getByDisplayValue("Active") as HTMLSelectElement;
+ expect(select.value).toBe("active");
+ });
+
+ it("reads 'completed' from sessionStorage as initial value", () => {
+ sessionStorage.setItem("initiatives.statusFilter", "completed");
+ renderPage();
+ const select = screen.getByDisplayValue("Completed") as HTMLSelectElement;
+ expect(select.value).toBe("completed");
+ });
+
+ it("reads 'archived' from sessionStorage as initial value", () => {
+ sessionStorage.setItem("initiatives.statusFilter", "archived");
+ renderPage();
+ const select = screen.getByDisplayValue("Archived") as HTMLSelectElement;
+ expect(select.value).toBe("archived");
+ });
+
+ it("falls back to 'active' when sessionStorage contains an invalid value", () => {
+ sessionStorage.setItem("initiatives.statusFilter", "bogus");
+ renderPage();
+ const select = screen.getByDisplayValue("Active") as HTMLSelectElement;
+ expect(select.value).toBe("active");
+ });
+
+ it("writes the new value to sessionStorage when the filter changes", () => {
+ renderPage();
+ const select = screen.getByDisplayValue("Active");
+ fireEvent.change(select, { target: { value: "all" } });
+ expect(sessionStorage.getItem("initiatives.statusFilter")).toBe("all");
+ });
+
+ it("updates the select's displayed value after the filter changes", () => {
+ renderPage();
+ const select = screen.getByDisplayValue("Active");
+ fireEvent.change(select, { target: { value: "completed" } });
+ expect((select as HTMLSelectElement).value).toBe("completed");
+ });
+
+ it("writes 'archived' to sessionStorage when filter changes to archived", () => {
+ renderPage();
+ const select = screen.getByDisplayValue("Active");
+ fireEvent.change(select, { target: { value: "archived" } });
+ expect(sessionStorage.getItem("initiatives.statusFilter")).toBe("archived");
+ });
+});
diff --git a/apps/web/src/routes/initiatives/index.tsx b/apps/web/src/routes/initiatives/index.tsx
index 140b7c3..0b6923f 100644
--- a/apps/web/src/routes/initiatives/index.tsx
+++ b/apps/web/src/routes/initiatives/index.tsx
@@ -21,11 +21,35 @@ const filterOptions: { value: StatusFilter; label: string }[] = [
{ value: "archived", label: "Archived" },
];
-function DashboardPage() {
+export function DashboardPage() {
const navigate = useNavigate();
- const [statusFilter, setStatusFilter] = useState("all");
+ const [statusFilter, setStatusFilter] = useState(() => {
+ try {
+ const stored = sessionStorage.getItem("initiatives.statusFilter");
+ if (
+ stored === "all" ||
+ stored === "active" ||
+ stored === "completed" ||
+ stored === "archived"
+ ) {
+ return stored;
+ }
+ } catch {
+ // sessionStorage unavailable (SSR, private-browsing restriction, etc.)
+ }
+ return "active";
+ });
const [projectFilter, setProjectFilter] = useState("all");
const [createDialogOpen, setCreateDialogOpen] = useState(false);
+
+ const handleStatusFilterChange = (value: StatusFilter) => {
+ try {
+ sessionStorage.setItem("initiatives.statusFilter", value);
+ } catch {
+ // ignore
+ }
+ setStatusFilter(value);
+ };
const projectsQuery = trpc.listProjects.useQuery();
// Single SSE stream for live updates
@@ -55,9 +79,10 @@ function DashboardPage() {
))}