8.3 KiB
spec, created, status
| spec | created | status |
|---|---|---|
| docs/specs/ui/requirements/sidebar-view-creation.md | 2026-07-17 | completed |
Implementation Plan: Direct Sidebar View Creation
Workspace-scope continuation
This completed package records the original global preference implementation. Workspace sidebar task views owns its scoped persistence successor and updated browser fixtures. Historical validation results below remain unchanged.
Overview
Add a guarded optimistic createSidebarView() state action first, then connect it to desktop and mobile controls through one shared popover/rename controller. Finish with production-build Playwright journeys proving immediate creation, optional rename, persistence, blocked states, and mobile touch parity. Existing user-settings contracts remain unchanged.
Backend
No backend changes are planned. apps/backend/internal/user/service/service.go::applySidebarViews already validates the 50-view limit, nonblank names and IDs, and unique IDs; applySidebarViewState validates active-view membership. Existing settings storage, boot hydration, WebSocket updates, retries, rollback, and error toast remain the persistence path.
Frontend
Instant-create state contract
- In
apps/web/lib/state/slices/ui/sidebar-view-builtins.ts, keepDEFAULT_VIEWcanonical and add a factory that creates independent default view data plus a frontend 50-view limit aligned with backendmaxSidebarViews. - In
apps/web/lib/state/slices/ui/sidebar-view-actions.ts, addcreateSidebarView()tobuildSidebarBackendActions. ReusemakeId,mutateViews, andtoSidebarSettingsPayload; guard drafts and the limit, choose the lowest available automatic name, append canonical defaults, and activate the new view. - Add the action signature to
UISliceActionsinapps/web/lib/state/slices/ui/types.tsand forward it throughAppStateinapps/web/lib/state/store.ts. - Keep
saveSidebarDraftAs,duplicateSidebarView, and their persistence behavior unchanged.
Shared creation and rename flow
- Create
apps/web/components/task/sidebar-filter/use-sidebar-view-popover.tsto own popover-open state, a monotonic rename-request token,startNewView, and disabled reasons for an active draft or the 50-view limit. - Update
SidebarFilterPopoverinsidebar-filter-popover.tsxto accept the rename request, clear stale requests when the popover closes or the active view rolls back, and constrain its current22remwidth to the viewport. - Update
ViewHeaderRowinview-manager.tsxto enter rename mode for an external request, prefill from the newly active view, expose an accessible input name, and retain existing autofocus, Enter-save, Escape/Cancel, manual Rename, andSave as…behavior. Saving rename leaves the popover open; cancel never deletes the view.
Desktop and mobile entry points
- In
apps/web/components/app-sidebar/sections/tasks-view-picker.tsx, followWorkspacePickerContentfromapp-sidebar-workspace-picker.tsx: appendDropdownMenuSeparatorand anIconPlusNew viewitem with a stable test ID and keyboard-safeonSelect. - In
apps/web/components/task/sidebar-filter/sidebar-filter-bar.tsx, add a fixed labeledNew viewaction outsideSidebarViewChipsso touch users do not need to scroll or hover. Keep interactive hit areas at least 40×40 px without overlapping the chip or gear targets. - Adjust
apps/web/components/task/sidebar-filter/sidebar-view-chips.tsxonly if needed to preserve swipe scrolling, press-and-hold drag, and compact desktop dimensions after the mobile bar grows. - Use existing Radix/shadcn surfaces and transitions. Add no animation dependency or broad sidebar restyle.
Tests
- What: canonical creation, lowest-available naming, independent nested data, append/activate semantics, complete settings payload, draft/limit no-ops, queued-write rollback, and store isolation.
Files:
apps/web/lib/state/slices/ui/sidebar-view-actions.test.tsandapps/web/lib/state/slices/ui/ui-slice.test.tsHow: RED/GREEN Zustand unit tests aroundcreateSidebarView()with mockedupdateUserSettings. - What: external rename request, automatic-name prefill, focus/accessibility, Enter-save, Escape/Cancel retention, stale-request cleanup, and manual-rename regression.
Files:
apps/web/components/task/sidebar-filter/view-manager.test.tsxand a focused test besideuse-sidebar-view-popover.ts. How: Testing Library component/hook tests with mocked store actions. - What: separated desktop menu item, one create per selection, event-driven popover handoff, draft/limit reasons, and a fixed mobile action.
Files:
apps/web/e2e/tests/task/sidebar-filter.spec.ts,apps/web/e2e/tests/task/mobile-sidebar-views.spec.ts, and existingapps/web/components/app-sidebar/sections/tasks-section.test.tsxHow: production-build Playwright covers real Radix focus/layering and responsive layout; the existing section test guards the picker mount.
E2E Tests
- Scenario: create from desktop with no prior edit, observe immediate active canonical defaults and focused prefilled rename, then rename and reload.
File:
apps/web/e2e/tests/task/sidebar-filter.spec.tsWhat to verify: selection before rename, no clone of active state, persisted renamed view, and popover remaining open. - Scenario: cancel/close rename, create repeatedly, and attempt creation with a dirty draft or 50 views.
File:
apps/web/e2e/tests/task/sidebar-filter.spec.tsWhat to verify: automatic-name retention/sequence and disabled reasons without draft loss. - Scenario: create through the mobile task-switcher sheet, optionally rename/customize, reopen after reload, and inspect narrow layout.
File:
apps/web/e2e/tests/task/mobile-sidebar-views.spec.tsWhat to verify: touch-reachable fixed action, persistence, chip swipe behavior, minimum hit area, and no document-level horizontal overflow. - Extend
apps/web/e2e/pages/sidebar-filter-popover.tswith direct-create, automatic-name, and rename helpers; scope mobile locators to the sheet because the hidden desktop sidebar remains mounted.
Implementation Waves
Wave 1:
- task-01-instant-create-state — done
Wave 2:
- task-02-sidebar-new-view-ui — done; depends on task 01
Wave 3:
- task-03-sidebar-view-e2e — done; depends on task 02
Frontend files overlap and the E2E surface requires integrated behavior, so waves remain sequential.
Verification
cd apps
pnpm --filter @kandev/web test -- \
lib/state/slices/ui/sidebar-view-actions.test.ts \
components/task/sidebar-filter/use-sidebar-view-popover.test.tsx \
components/task/sidebar-filter/view-manager.test.tsx
cd apps/web
pnpm build
pnpm e2e:run --host --no-build --project chromium -- tests/task/sidebar-filter.spec.ts
pnpm e2e:run --host --no-build --project mobile-chrome -- tests/task/mobile-sidebar-views.spec.ts
cd ../..
make fmt
make typecheck test lint
Result
- Production build passed.
- Focused desktop Playwright passed 18/18; focused mobile Playwright passed 5/5.
- Web unit suite passed 5,113 tests across 632 files; backend retry, script tests, typecheck, and all linters passed.
- The repository-wide CLI suite retains an unrelated baseline failure:
.github/workflows/notify-docs.ymlpins pnpm10.29.3, whileapps/package.jsonpins9.15.9. Neither file is changed by this plan.
Risks and considerations
- The single sidebar draft slot makes creation while dirty destructive; both controller and store action must guard it.
- Never reuse nested references from
DEFAULT_VIEWwhen creating a view. - Create and immediate rename enqueue two ordered full-array settings writes; reuse the existing queue rather than adding timing delays.
- A create rollback can occur while rename UI is focused; the controller must clear stale intent when the created active ID disappears.
- Frontend limit metadata can drift from backend
maxSidebarViews; backend stays authoritative and failure rollback remains required. - Radix dropdown-to-popover focus transfer must be event-driven, not timeout-based.
- Hidden desktop and visible mobile controls share test IDs; mobile Playwright locators must stay sheet-scoped.
- Larger touch targets must not overlap or break
TouchSensorswipe/hold behavior.