--- spec: docs/specs/ui/requirements/command-panel-sidebar-task-reveal.md created: 2026-08-05 status: implemented --- # Implementation Plan: Command-panel Sidebar Task Reveal ## Overview Add a bounded DOM-navigation helper for rendered task rows, invoke it when Cmd+K opens a task, and prove the behavior against an actually overflowing sidebar. The helper follows the retryable target navigation pattern already used by `apps/web/lib/review/navigation.ts`, while scoping lookup to a visible task-sidebar scroll viewport so the CSS-hidden desktop rail is never selected on mobile. A command-panel selection is queued until the canonical route and matching active task render, and each new selection immediately invalidates the previous latest-wins generation. ## Confirmed root cause `useCommandPanelHandlers.handleTaskSelect` in `apps/web/components/command-panel.tsx` only closes the palette and pushes `linkToTask(task.id)`. `TaskSidebarScrollArea` in `apps/web/components/task/task-sidebar-scroll-area.tsx` only tracks its bottom-scroll cue; no code connects command-panel selection to the nested Radix scroll viewport. Consequently, route and active-task state change while an off-screen sidebar row remains outside the visible list. ## Frontend ### Sidebar target navigation - Add `apps/web/lib/sidebar/task-navigation.ts` with a stable task-row DOM attribute/selector and a bounded `requestAnimationFrame` retry helper. It resolves the row only inside a rendered, visible `task-sidebar-scroll` viewport and calls `scrollIntoView({ block: "nearest", inline: "nearest" })`. A missing or hidden target resolves as a no-op rather than affecting navigation. A newer reveal request supersedes and terminates an older retry loop before it can scroll a stale row, including while the newer route is waiting behind a navigation guard. - Add a row-specific task marker to the interactive row in `apps/web/components/task/task-item.tsx`. Keep the existing test ID and active-task accessibility attributes intact. - Update command-panel task selection in `apps/web/hooks/use-command-panel-task-navigation.ts` to queue the task ID alongside the existing canonical task navigation, then start the sidebar reveal only after the matching task route and active-task state render. Do not move focus or change active sidebar view/collapse state. ### Mobile design contract Desktop outcome: Cmd+K opens the selected task and the rendered active row becomes visible inside the existing task-list scroll owner. Phone entry point and presentation remain the existing direct task route plus the optional `SessionTaskSwitcherSheet` inset drawer; the command-panel action does not open that drawer. `apps/web/components/task/mobile/session-task-switcher-sheet.tsx` is the nearest shipped exemplar and contributes the rule that phone task navigation owns a separate, safe-area-aware internal scroller. Shared task search and route logic remain unchanged; the reveal helper selects only a layout-visible desktop scroll viewport. No new touch target, viewport-bound surface, or safe-area behavior is introduced. ## Tests - **What:** selector escaping, above/below viewport behavior, immediate and delayed row discovery, minimum-distance scroll options, visible-sidebar scoping, latest-request supersession, and bounded failure. - **File:** `apps/web/lib/sidebar/task-navigation.test.ts`. - **How:** Vitest/jsdom with explicit layout visibility and queued animation-frame callbacks. - **Regression order:** first add the overflowing-sidebar browser scenario and run it against the current code to confirm the selected route changes while the target row stays outside the sidebar viewport; then implement the helper and rerun to green. ## E2E tests - **Scenario:** Given enough desktop tasks to overflow the sidebar, when Cmd+K selects a task whose row is confirmed outside the nested viewport, then the URL, active-row state, and row/container bounding boxes prove the row was revealed. - **Scenario:** Given a dirty settings page that delays task-route activation beyond the initial reveal retry budget, when the user confirms the guarded navigation, then the active row is revealed after route activation. - **File:** `apps/web/e2e/tests/task/sidebar-scroll-preservation.spec.ts`. - **What to verify:** precondition overflow and off-screen target, command-panel selection through the UI, canonical `/t/:id` navigation, `aria-current`, containment within `task-sidebar-scroll`, and no document-scroll movement. - **Scenario:** Given a phone viewport, when Cmd+K selects a task, then direct task navigation still succeeds without revealing the hidden desktop rail. - **File:** `apps/web/e2e/tests/task/mobile-command-panel-task-navigation.spec.ts`. - **What to verify:** canonical task URL and usable mobile task surface; the desktop sidebar remains non-visible and document horizontal overflow is absent. ## Verification results - Dependency setup: `cd apps && pnpm install --frozen-lockfile` passed. - RED regressions: the supersession unit test failed before generation cancellation, and the delayed-blocker desktop E2E failed before post-navigation reveal queuing; both passed after the implementation. - Unit: `cd apps/web && pnpm exec vitest run lib/sidebar/task-navigation.test.ts` — 1 file, 9 tests passed. - Related unit consumers: `cd apps/web && pnpm exec vitest run lib/sidebar/task-navigation.test.ts hooks/use-command-panel-task-navigation.test.ts components/command-panel-content-search.test.tsx` — 3 files, 24 tests passed. - Typecheck: `cd apps/web && pnpm exec tsc --noEmit` passed. - Desktop focused GREEN: `cd apps/web && pnpm e2e:run --host --no-build tests/task/sidebar-scroll-preservation.spec.ts --grep "after a delayed settings navigation blocker|starts above the sidebar viewport"` — 2 tests passed. - Desktop full regression: `cd apps/web && pnpm e2e:run --host --no-build tests/task/sidebar-scroll-preservation.spec.ts` — 8 tests passed. - Mobile GREEN: `cd apps/web && pnpm e2e:run --host --no-build --project mobile-chrome tests/task/mobile-command-panel-task-navigation.spec.ts` — 1 test passed. - Build: `cd apps && pnpm --filter @kandev/web build:vite` passed. - Formatting and lint: focused Prettier check and ESLint with `--max-warnings 0` passed. - Repository checks: `git diff --check` passed. Managed E2E build/test artifacts were disposable and are not part of the change. ## Implementation waves - [x] [Task 01 — reveal command-selected sidebar task](task-01-reveal-command-selected-task.md) — done Execution is sequential in the primary conversation. No subagents are authorized. ## Risks and boundaries - The active sidebar view may intentionally omit or collapse the task. The helper must time out without mutating persisted user preferences. - Desktop and mobile sidebar DOM can coexist because the desktop rail is hidden with responsive CSS. Visibility scoping is required to avoid treating hidden markup as the destination. - `content-visibility: auto` is used for long task lists. The E2E assertion must inspect the row's actual bounding box after reveal rather than assuming DOM presence proves visibility.