From c00942740165ad0854444932fc0d39b6837048f3 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 26 Apr 2026 22:07:43 -0700 Subject: [PATCH] fix(FN-2577): preserve Todos access and TodoView interaction behavior - Keep the view overflow trigger available when Todos is supported so the Todos view remains reachable - Update TodoView list rows to use an explicit select button and align active-state assertions with the new structure - Restore keyboard focus styling and mobile action visibility in TodoView CSS for accessibility and usability - Harden CLI extension test cleanup with retry logic for transient ENOTEMPTY/EBUSY tempdir removal errors --- packages/cli/src/__tests__/extension.test.ts | 25 +++++++++++++- packages/dashboard/app/components/Header.tsx | 5 ++- .../dashboard/app/components/TodoView.css | 27 +++++++++++++-- .../dashboard/app/components/TodoView.tsx | 33 +++++++------------ .../app/components/__tests__/Header.test.tsx | 14 +++++--- .../components/__tests__/TodoView.test.tsx | 3 +- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/__tests__/extension.test.ts b/packages/cli/src/__tests__/extension.test.ts index bd8e3fade..f96e48101 100644 --- a/packages/cli/src/__tests__/extension.test.ts +++ b/packages/cli/src/__tests__/extension.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; +import { setTimeout as delay } from "node:timers/promises"; // Each test spins up a fresh temp workspace, mounts the full extension API, // registers tools, and exercises them through real TaskStore/MissionStore @@ -79,6 +80,28 @@ function makeCtx(cwd: string) { return { cwd } as any; } +async function removeDirWithRetries(path: string) { + const maxAttempts = 4; + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + await rm(path, { recursive: true, force: true }); + return; + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code !== "ENOTEMPTY" && code !== "EBUSY") { + throw error; + } + + if (attempt === maxAttempts) { + throw error; + } + + await delay(25 * attempt); + } + } +} + // ── Tests ────────────────────────────────────────────────────────── // Skipped: 39 tests × ~1-4s each (~62s total) exercise every fn pi tool @@ -102,7 +125,7 @@ describe.skip("fn pi extension", () => { }); afterEach(async () => { - await rm(tmpDir, { recursive: true, force: true }); + await removeDirWithRetries(tmpDir); }); describe("registration", () => { diff --git a/packages/dashboard/app/components/Header.tsx b/packages/dashboard/app/components/Header.tsx index ae01aaa2b..fda0feee3 100644 --- a/packages/dashboard/app/components/Header.tsx +++ b/packages/dashboard/app/components/Header.tsx @@ -301,14 +301,17 @@ export function Header({ }, [overflowScripts]); const hasViewOverflowItems = useMemo(() => { + const hasTodosView = Boolean(onChangeView); + return !!( + hasTodosView || experimentalFeatures?.insights || experimentalFeatures?.roadmap || showSkillsTab || experimentalFeatures?.memoryView || experimentalFeatures?.devServerView ); - }, [experimentalFeatures, showSkillsTab]); + }, [experimentalFeatures, onChangeView, showSkillsTab]); // Keep ref in sync with state useEffect(() => { diff --git a/packages/dashboard/app/components/TodoView.css b/packages/dashboard/app/components/TodoView.css index ff494db6a..0af20992c 100644 --- a/packages/dashboard/app/components/TodoView.css +++ b/packages/dashboard/app/components/TodoView.css @@ -85,7 +85,7 @@ align-items: center; padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); - cursor: pointer; + gap: var(--space-sm); color: var(--text-muted); transition: background var(--transition-fast), color var(--transition-fast); @@ -96,8 +96,20 @@ color: var(--text); } -.todo-list-item:focus-visible { +.todo-list-select-btn { + flex: 1; + min-width: 0; + border: 0; + background: none; + color: inherit; + text-align: left; + cursor: pointer; + padding: 0; +} + +.todo-list-select-btn:focus-visible { outline: none; + border-radius: var(--radius-sm); box-shadow: var(--focus-ring); } @@ -206,6 +218,12 @@ text-align: left; } +.todo-item-text:focus-visible { + outline: none; + border-radius: var(--radius-sm); + box-shadow: var(--focus-ring-strong); +} + .todo-item-text--completed { text-decoration: line-through; color: var(--text-muted); @@ -302,6 +320,7 @@ } .todo-list-item, + .todo-list-select-btn, .todo-add-list-btn, .todo-icon-btn, .todo-item, @@ -317,4 +336,8 @@ .todo-add-item-row .btn { width: 100%; } + + .todo-item-actions { + opacity: 1; + } } diff --git a/packages/dashboard/app/components/TodoView.tsx b/packages/dashboard/app/components/TodoView.tsx index ac825bb9d..a81245a16 100644 --- a/packages/dashboard/app/components/TodoView.tsx +++ b/packages/dashboard/app/components/TodoView.tsx @@ -278,15 +278,6 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
setSelectedListId(list.id)} - role="button" - tabIndex={0} - onKeyDown={(event) => { - if (event.key === "Enter") { - setSelectedListId(list.id); - } - }} - data-testid={`todo-list-${list.id}`} > {isEditing ? ( <> @@ -303,14 +294,12 @@ export function TodoView({ projectId, addToast }: TodoViewProps) { } }} autoFocus - onClick={(event) => event.stopPropagation()} data-testid={`rename-list-input-${list.id}`} />