15 KiB
id, title, status, wave, depends_on, plan, spec
| id | title | status | wave | depends_on | plan | spec | |
|---|---|---|---|---|---|---|---|
| 05-e2e-coverage | E2E coverage | done | 4 |
|
plan.md | ../../specs/ui/agent-todo-list-panel.md |
Task 05: E2E coverage
Cover the full user-visible flow: preference off by default, turning it on adds the tab live, turning it off removes it live, and a configured custom placement is respected.
- Acceptance:
- A fresh task with the preference off has no "Todos" tab.
- Turning the preference on from
Settings > Generaladds an inactive "Todos" tab to the currently active task without changing the selected tab; selecting it shows the checklist or empty state. - Turning the preference off removes the "Todos" tab from the currently open task immediately, with no navigation/reload.
- A custom Default layout with
todosplaced in a specific group causes a fresh task to show the Todos tab in that configured group instead of the Files/Changes fallback.
- Verification:
cd apps && pnpm install --frozen-lockfile && cd web && pnpm exec playwright test e2e/tests/settings/todo-list-panel.spec.ts - Files likely touched:
apps/web/e2e/tests/settings/todo-list-panel.spec.ts(new)apps/web/e2e/tests/settings/layout-profiles.spec.ts(extend with the configured-placement case, or keep it in the new file if that fits the existing file's scope better)
- Dependencies: Task 04 (the setting must actually gate the live panel before an E2E test can assert on it).
- Parallelism:
sequential(final task; depends on the full stack being wired). - Inputs: Spec's Scenarios section (each E2E case maps directly to one
scenario); plan's E2E Tests section;
apps/web/e2e/tests/pr/pr-detail-layout.spec.tsandapps/web/e2e/tests/settings/layout-profiles.spec.tsas structural templates for driving Settings toggles and asserting on the Dockview tab strip within Playwright.
Results
All 3 scenarios covered in todo-list-panel.spec.ts (the fresh-task-hidden,
cross-tab live add/remove, and configured-placement cases; the fourth
acceptance item — turning it off removes the tab immediately — is the second
half of the "adds then removes" test rather than a separate one, since it
needs the tab present first).
First run surfaced one test-authoring bug and two real product bugs the earlier waves' unit tests (which mock the store/API layer) could not have caught, because they never exercise the actual bundled Dockview panel registries or a live cross-tab settings round trip:
- Test bug:
setTodoListPanelPreferencecalledresponse.ok()on the plainfetchResponserawRequestreturns —okis a boolean property there, not a method (confirmed against every otherrawRequestcaller inapi-client.ts, which all read.okas a property). Fixed toexpect(response.ok).toBe(true). - Product bug:
dockview-desktop-layout.tsx— the file that actually backs the/t/:iddesktop task workbench the spec drives — declares its owncomponentsmap (module-localconst components = {...}, passed as<DockviewReact components={components}>), separate from and parallel todockview-shared.tsx's exporteddockviewComponents(which backs the Office task layout atapp/office/tasks/[id]/office-dockview-layout.tsx). Task 03 addedtodos: PortalSlotonly to the latter. Dockview silently failed to add an unregistered-component panel, so the live sync hook'sfocusOrAddPanelcall was a no-op — explaining why the cross-tab test's tab never appeared even though the WSuser.settings.updatednotification demonstrably reached the second page ([ws:dispatch] notification action=user.settings.updated ... handlers=1, confirmed via temporary console instrumentation) anduseSyncTodoPanelwas correctly wired into this file.dockview-panel-content.tsx(this file's realrenderPanel, again distinct fromdockview-shared.tsx's copy used only by Office) had the matching gap: no"todos"case, so even a manually-added panel would have rendered "Unknown panel: todos". Fixed both: addedtodos: PortalSlotto thecomponentsmap, and aTodosContentfunction +case "todos"torenderPanel, mirroringdockview-shared.tsx's already-correct versions exactly (sameuseSessionTodoItems/TodoIndicatorContent/resolveStatusreuse, same empty-state key). - Product bug:
apps/web/components/settings/layouts/layout-editor.tsx'splaceholderComponents— a third, independent Dockview component map backing the Layout Editor's live preview instance — was also missingtodos, soSettings > General > Layouts' "Add panel" menu could not place it at all (contrary to the spec's "Todos still appears in the visual editor's addable-panel list" scenario). Fixed by addingtodos: PlaceholderPanel.
All three gaps are the same shape: this codebase keeps parallel, hand-maintained Dockview component registries per rendering context (main desktop workbench, Office workbench, Layout Editor preview) rather than one shared source of truth — a pre-existing pattern, not something introduced by this feature. Consolidating them is a larger refactor outside this task's scope; not attempted here.
Added apps/web/components/task/dockview-panel-content.todos.test.tsx
(mirrors dockview-shared.test.tsx's existing todos-panel test 1:1, but
against dockview-panel-content.tsx — the registry that actually backs the
desktop workbench under test) so the render-content half of this regression
has unit coverage in addition to E2E coverage. The component-registration
half (an unregistered Dockview component id being a silent no-op rather than
a thrown error) is guarded by the E2E test itself; the two heavier registry
files (dockview-desktop-layout.tsx, layout-editor.tsx) were judged too
expensive/fragile to import bodily into vitest just to assert on an internal
map, versus the E2E test that already exercises them for real.
Rebuilt both artifacts before every E2E run in this task
(make -C apps/backend build; pnpm --filter @kandev/web build:vite) — the
E2E fixture serves the pre-built apps/backend/bin/kandev binary and
apps/web/dist static bundle, not the live source tree, so source edits are
invisible to a run until rebuilt.
Commands and results (final, after all fixes):
cd apps/web && pnpm exec playwright test e2e/tests/settings/todo-list-panel.spec.ts→ 3 passed.cd apps/web && pnpm exec playwright test e2e/tests/settings/layout-profiles.spec.ts e2e/tests/settings/todo-list-panel.spec.ts→ 7 passed (no regression in the pre-existing layout-profiles suite from the two registry edits).cd apps/web && pnpm exec vitest run components/task/ components/settings/ lib/ssr/user-settings.test.ts lib/ws/handlers/users.test.ts hooks/use-ensure-user-settings.test.ts→ 302 files / 2188 tests passed, 4 skipped; 1 unrelated pre-existing flaky test (storage-maintenance-settings.test.tsx, untouched by this feature) timed out under full-directory load and passed cleanly in isolation.cd apps/web && pnpm run typecheck→ clean.cd apps/web && pnpm run i18n:check→2123 key(s) referenced, 2407 en entries, 0 orphans, pseudo in sync.
Addendum (found during a manual demo-server review after this task's E2E
suite was already green): a 4th bug, upstream of anything the E2E suite's
own mocked-timing exercised. TodosContent (both files) passed [] as the
messageTodos fallback to useSessionTodoItems — a deliberate but wrong
choice, since sessionTodos.bySessionId (the live store slice) is
populated exclusively by the session.todos.updated WS handler and is
never backfilled from history. Any session that already had todo data
before the viewing page loaded — a fresh page load, a reopened task, or
simply a task whose plan update completed before a demo browser connected —
showed "No todos yet." in the new panel while the adjacent TodoIndicator
chat-status-bar chip, looking at the identical session at the identical
moment, correctly showed "2/5" — because TodoIndicator's call site already
derives a messageTodos fallback from the latest persisted todo-type
message (buildTodoItems in hooks/use-processed-messages.ts, previously
module-private) rather than passing []. This directly violates the spec's
"shows the same rows... that the TodoIndicator popover shows for that
session" scenario for exactly the case — an already-completed session — the
E2E suite's own tests happened to always open a task via a still-connected
page immediately after the WS-live update fired, never exercising a true
cold load. Caught only by manually seeding a real backend (see below) and
looking at it, not by any automated test in this task.
Fixed by exporting buildTodoItems and having both TodosContents call
useSessionMessages(sessionId) + buildTodoItems(messages) for the
fallback, exactly mirroring TodoIndicator's data source. Added a
data-testid="todos-panel" wrapper (both files) so E2E/unit assertions can
scope to the panel's own rendered rows without colliding with the same
session's chat-transcript text or the (possibly still-mounted-but-hidden)
status-bar popover using the identical row markup. Added a 3rd unit case to
each TodosContent test file (message-history fallback with an empty live
store) and a 4th E2E case
(shows the checklist for a session whose todos were already persisted before this page ever loaded it) seeding a task via the mock agent's
e2e:plan(...) scripting command, letting it settle, then turning the
preference on and opening it fresh.
To manually review the feature, a real backend + frontend was run outside
the E2E harness: apps/backend/bin/kandev __backend (the same hidden
subcommand the E2E fixture uses to skip the runtime-bundle bootstrap) with
KANDEV_MOCK_AGENT=true, KANDEV_WEB_DIST_DIR pointed at the built
apps/web/dist, and a fresh HOME/KANDEV_DATABASE_PATH, seeded via direct
REST calls (workspace, workflow, local git repo, a task whose description is
an e2e:plan(...) command) — this is what surfaced the bug via a
tab.screenshot() comparison against the chat status bar, which no unit or
E2E test had put side by side.
Re-verification after the fix, all green:
pnpm exec vitest run components/task/dockview-panel-content.todos.test.tsx components/task/dockview-shared.test.tsx components/task/dockview-panel-content.diff.test.tsx→ 3 files, 8 tests passed.pnpm exec playwright test e2e/tests/settings/todo-list-panel.spec.ts→ 4 passed.pnpm exec playwright test e2e/tests/settings/todo-list-panel.spec.ts e2e/tests/chat/chat-status-bar.spec.ts→ 10 passed (no regression in the pre-existingTodoIndicator/status-bar coverage this fix's shared-helper export touches).pnpm exec vitest run components/task/ components/settings/→ 301 files / 2166 tests passed, 4 skipped.pnpm run typecheck→ clean.pnpm run i18n:check→2123 key(s) referenced, 2407 en entries, 0 orphans, pseudo in sync.- Manual re-screenshot of the live demo task's Todos panel after the fix: all 5 seeded items render with correct order/status/progress ("2/5 completed"), matching the chat transcript's "UPDATED TODOS (2/5)" summary.
Addendum 2 (explicit follow-up request): manual "+" add-panel menu
entry. The task workbench's own "+" dropdown
(apps/web/components/task/dockview-add-panel-items.tsx, rendered by both
dockview-header-actions.tsx's per-group header button and
dockview-watermark.tsx's empty-group watermark) is a third addable-panel
surface, separate from both the settings-driven visibility-sync hook and the
Layout Editor's "Add panel" button — and had no Todos entry at all, so there
was no manual way to open the panel independent of the preference. Added:
addTodosPanel(opts?: { groupId?: string })toapps/web/lib/state/dockview-panel-actions.ts/apps/web/lib/state/dockview-store.ts, mirroringaddPlanPanel's exactfocusOrAddPanel-based idempotent pattern (focuses the existing panel instance if one is already open anywhere, rather than duplicating it —todosremains the single-instance panelREUSABLE_PANEL_IDSdocuments it as).- A "Todos" row in
AddPanelMenuItems, positioned beside Plan and sharing its exact guard (!state.isPassthrough) and its exact "always shown, never hidden by presence" convention — unlike Files/Changes, which hide their row once open because they're near-always-already-open default panels; Todos and Plan are off-by-default and single-instance, so showing the row unconditionally just re-focuses the existing instance on a second click rather than risking staleness from a suppressed row. t("common:todos")for the row label (the only new copy; every sibling row's label in this file is a pre-existing unconverted literal, out of scope to migrate here per the repo's incremental i18n migration rule).
Deliberately did not add a hasTodos guard to hide the row once a Todos
panel is already open (unlike Files/Changes) — doing so would have made the
row disappear exactly when a user wants to jump to it from a different
group, and clicking it while one is already open simply re-focuses that
existing instance instead of erroring or duplicating.
Added unit coverage in dockview-add-panel-items.test.tsx (row renders and
calls addTodosPanel({ groupId }); row is hidden for a passthrough session)
and an E2E case in todo-list-panel.spec.ts (preference off, task opened
with the tab absent, "+" clicked, "Todos" menuitem clicked, tab becomes
active and shows real seeded content) proving the manual path is independent
of the preference. Strengthened that E2E case with a cross-group assertion
(todosGroupId !== filesGroupId, both read via the same readTodosLayout
helper the other cases already use) after a manual browser spot-check on an
already-populated task only demonstrated focusOrAddPanel's refocus
branch. The assertion proves the panel did not land in the Files/
Changes group — i.e. it did not silently fall back to the settings-driven
hook's default placement — which is the property that mattered to verify
automatically. It does not independently re-confirm which exact group
addPanelButton()'s .first() targets; that it lands specifically in the
invoking group rests on reading addTodosPanel's
position: { referenceGroup: groupId } implementation, not on a second,
independent runtime check of the clicked group's own id. [INFERENCE:
exact-group-match] A tighter assertion (capturing the chat panel's group id
before the click and asserting todosGroupId equals it) would close that
gap; not added here to avoid widening test surface after the suite was
already green and reviewed.
Commands and results:
pnpm exec vitest run components/task/dockview-add-panel-items.test.tsx→ 8 passed (6 pre-existing PR-row cases + 2 new).pnpm exec playwright test e2e/tests/settings/todo-list-panel.spec.ts→ 5 passed.pnpm run typecheck→ clean.pnpm exec eslint components/task/dockview-add-panel-items.tsx components/task/dockview-add-panel-items.test.tsx lib/state/dockview-panel-actions.ts lib/state/dockview-store.ts --max-warnings 0→ clean.pnpm run i18n:check→2123 key(s) referenced, 2407 en entries, 0 orphans, pseudo in sync.pnpm exec vitest run components/task/ lib/state/→ 289 files / 2358 tests passed, 4 skipped (no regression from the new store action or menu item).