Files
kandev/docs/plans/task-create-workflow-memory/plan.md
T
2026-08-23 17:34:52 +00:00

230 lines
12 KiB
Markdown

---
spec: docs/specs/tasks/requirements/task-create-workflow-memory.md
created: 2026-08-08
status: done
---
# Implementation Plan: Task Create Workflow Memory
## Overview
Extend the existing backend-owned task-create preference with a targeted
per-workspace workflow map, then make the shared dialog resolver prefer that
map over unlocked board/list context. Backend contract and merge semantics land
first, frontend mapping and resolution follow, and a production-build E2E test
proves persistence across workspace switches and conflicting filters.
## Confirmed root cause
`TaskCreateLastUsed` and every wire/store mapper omit workflow, while
`resetTaskForm` initializes `selectedWorkflowId` only from the caller's
`workflowId`. The selector's `lastUsedWorkflowId` is component-local and only
sorts menu choices. Consequently, a dialog opened with `workflowId=null` has no
durable workflow default when multiple workflows exist, and a dialog opened
under a filter inherits that filter rather than the workflow that created the
previous task.
## Backend
### Per-workspace preference contract
- Add `WorkflowIDsByWorkspace map[string]string` with JSON key
`workflow_ids_by_workspace` to
`apps/backend/internal/user/models/models.go:TaskCreateLastUsed`.
- Keep the field inside the existing `users.settings.task_create_last_used`
JSON object; no table or column migration is required.
- Treat a non-empty workflow map as a meaningful task-create patch in
`apps/backend/internal/user/service/service.go` and preserve it through DTO,
HTTP response, WebSocket event, and boot-state mapping.
- Extend `apps/backend/internal/backendapp/boot_state_routes.go` so the boot
payload exposes the complete map and marks the task-create state synced when
the map is the only populated preference.
### Targeted SQLite and PostgreSQL merge
- Extend `makeTaskCreateLastUsedJSONSetArgs` in
`apps/backend/internal/user/store/sqlite.go` to update one nested
`workflow_ids_by_workspace.<workspace_id>` entry at a time.
- Extend `applyPostgresTaskCreateLastUsedPatch` with equivalent parameterized
`jsonb_set` paths. Do not serialize and replace the whole map: alternating or
concurrent task creation in two workspaces must preserve both entries.
- Preserve the existing targeted-update behavior for repository, branch, agent
profile, and executor profile, including broad user-settings writes racing
with task creation.
### Record the effective workflow
- Extend `buildTaskCreateLastUsedPatch` in
`apps/backend/internal/task/handlers/task_http_handlers.go` to add
`body.WorkflowID` under `body.WorkspaceID` after successful creation.
- Update the WebSocket task-create bridge in
`apps/backend/internal/task/handlers/task_ws_handlers.go` to pass
`req.WorkspaceID` and `req.WorkflowID` into the same recorder path.
- Preserve the current warning-only behavior when preference recording fails;
successful task creation is not rolled back.
## Frontend
### Wire mapping and queued overlay
- Add `workflow_ids_by_workspace?: Record<string, string>` to
`TaskCreateLastUsedApi` and `workflowIdsByWorkspace: Record<string, string>`
to the settings store state.
- Update `apps/web/lib/ssr/user-settings.ts`, boot/HTTP/WS mapping tests, and
default settings so absent maps normalize to `{}` and existing installations
remain compatible.
- Extend the task-create queued overlay in
`apps/web/components/task-create-dialog-handlers.ts` to derive the entry from
submitted `workspace_id` and `workflow_id`. Merge queued workflow entries
into, rather than replace, the authoritative map and clear the overlay only
after every queued entry matches backend settings.
- Update `apps/web/components/state-provider.tsx` equality logic for map
contents so settings publication clears the overlay deterministically.
### Workflow default resolution
- Add a pure resolver in
`apps/web/components/task-create-dialog-defaults.ts` for the confirmed
precedence: locked workflow; current-dialog manual choice; valid visible
per-workspace last-used workflow; valid visible unlocked context; sole
visible workflow; otherwise no selection.
- Keep unlocked board/list `workflowId` as fallback context rather than seeding
it as an authoritative form selection. Preserve explicit locked flows by
passing the existing `lockedFields.workflow` signal through form reset and
late-value synchronization.
- Thread `taskCreateLastUsed.workflowIdsByWorkspace[workspaceId]` and user
settings readiness through `useTaskCreateDialogData` / `useDialogComputed`.
Validate every candidate against the visible workflows in the current
workspace before returning `effectiveWorkflowId`.
- Make workflow-step loading and workflow-agent override effects consume the
resolved effective workflow so a restored default receives the same steps
and agent lock as a manual selection.
- Remove the misleading component-local `lastUsedWorkflowId` sorting state;
durable task-create recency now comes from backend settings and option order
remains workflow `sort_order`.
### Mobile design contract
- **Desktop and mobile outcome:** standard Create Task restores the same
workspace-specific workflow regardless of viewport or current filter.
- **Nearest shipped mobile exemplar:** the existing Kanban FAB → Create Task
dialog remains the entry point and presentation baseline.
- **Presentation:** no overlay, navigation, hierarchy, scroll owner, safe-area,
focus, or touch-target changes. Exactly-one-workflow suppression remains
shared across viewports.
- **Shared logic:** wire mapping, queued overlay, and default resolution stay in
shared state/hooks; no mobile-specific preference is introduced.
- **Mobile proof:** focused unit tests cover the shared resolver and the
existing mobile Kanban workflow-context E2E continues to cover dialog
reachability. No new mobile-only Playwright scenario is required because the
change is state normalization inside unchanged composition.
## Tests
- **What:** HTTP and WebSocket task creation record the effective workflow under
the correct workspace key.
- **File:** `apps/backend/internal/task/handlers/task_http_handlers_test.go`
(the existing file covers both HTTP and WebSocket task-create handlers).
- **How:** extend recorder-capture tests for both transports and assert the
workspace-to-workflow entry alongside existing profile/repository fields.
- **What:** targeted task-create preference writes preserve workflow entries for
other workspaces and all existing last-used fields on SQLite and PostgreSQL
query paths.
- **File:** `apps/backend/internal/user/store/sqlite_test.go`.
- **How:** real SQLite repository test plus PostgreSQL query-builder
assertions using two workspace keys and partial patches.
- **What:** service, DTO, event, and boot payload treat workflow-only history as
loaded task-create state.
- **File:** `apps/backend/internal/user/service/service_test.go` and
`apps/backend/internal/backendapp/boot_state_routes_test.go`.
- **How:** record a workflow-only patch and assert the published/boot map and
`synced` state.
- **What:** the frontend mapper and queued overlay preserve multiple workspace
entries and clear only after backend convergence.
- **File:** `apps/web/lib/ssr/user-settings.test.ts`,
`apps/web/hooks/use-ensure-user-settings.test.ts`,
`apps/web/components/state-provider.test.tsx`, and
`apps/web/components/task-create-dialog-handlers.test.ts`.
- **How:** map missing and populated wire values, merge two workspace entries,
and exercise stale/fresh settings publication.
- **What:** workflow resolution prefers per-workspace last-used state over an
unlocked conflicting filter, while manual and locked selections win and
invalid history falls through.
- **File:** `apps/web/components/task-create-dialog-defaults.test.ts` and
focused dialog state/effect tests.
- **How:** table-driven pure resolver cases plus hook coverage proving steps
and workflow-agent overrides follow the effective workflow.
- **What:** one visible workflow remains implicit with no selector.
- **File:** existing
`apps/web/components/task-create-dialog-form-body.test.tsx` and
`apps/web/e2e/tests/task/create-task.spec.ts` regressions.
- **How:** retain the current unit and E2E assertions unchanged unless wiring
changes require fixture updates.
## E2E Tests
- **Scenario:** **GIVEN** workspace A remembers Dev while its board is filtered
to PR Review, **WHEN** standard Create Task opens, **THEN** Dev is selected.
- **File:** `apps/web/e2e/tests/task/create-task.spec.ts`.
- **What to verify:** create the remembered task through the real HTTP API,
persist the conflicting filter, open the dialog through the regular UI,
and assert the visible workflow trigger text.
- **Scenario:** **GIVEN** workspace A remembers Dev and workspace B remembers
Support, **WHEN** the user opens standard Create Task in each workspace,
**THEN** each workspace restores its own workflow.
- **File:** `apps/web/e2e/tests/task/create-task.spec.ts`.
- **What to verify:** seed both histories through successful task creation,
navigate between workspaces, and assert each dialog independently after a
page reload so the Go boot payload and frontend mapper are exercised.
- **Scenario:** **GIVEN** a single visible workflow, **WHEN** Create Task opens,
**THEN** no workflow selector appears.
- **File:** existing `apps/web/e2e/tests/task/create-task.spec.ts` scenario.
- **What to verify:** keep the existing hidden-workflow-plus-one-visible
regression green.
## Verification Results
All planned checks pass:
- Backend: `cd apps/backend && go test ./internal/user/store ./internal/user/service ./internal/task/handlers ./internal/backendapp` — all four packages passed.
- Frontend: `cd apps/web && pnpm test -- --run lib/ssr/user-settings.test.ts lib/ws/handlers/users.test.ts hooks/use-ensure-user-settings.test.ts components/state-provider.test.tsx components/task-create-dialog-handlers.test.ts components/task-create-dialog-defaults.test.ts components/task-create-dialog-state.test.ts components/task-create-dialog-effects.test.ts components/task-create-dialog-form-body.test.tsx` — 9 files, 158 tests passed.
- Frontend typecheck: `cd apps/web && pnpm run typecheck` — passed.
- E2E focused production-build run: `cd apps/web && pnpm e2e:run tests/task/create-task.spec.ts -- --grep "remembered workflow"` — 2 tests passed.
- E2E selector/regression run: `cd apps/web && pnpm e2e:run --no-build tests/task/create-task.spec.ts -- --grep "single visible workflow|remembered workflow"` — 3 tests passed.
- E2E full task-create spec: `cd apps/web && pnpm e2e:run --no-build tests/task/create-task.spec.ts` — 15 tests passed in 39 seconds.
- Managed E2E runners exited cleanly and removed their isolated `/tmp/kandev-e2e-*` worker directories; no failure artifacts were produced.
## Implementation Waves And Parallel Candidates
Execution is sequential in the primary conversation because the frontend wire
contract depends on the backend field and the E2E scenario depends on both.
No task is marked parallel-safe and no subagent delegation is authorized.
Wave 1:
- [x] [Task 01: Persist per-workspace workflow memory](task-01-backend-workflow-memory.md)
Wave 2:
- [x] [Task 02: Restore the remembered workflow](task-02-frontend-workflow-resolution.md)
Wave 3:
- [x] [Task 03: Prove workflow memory through Create Task](task-03-create-task-e2e.md)
## Risks And Out Of Scope
- Nested JSON path construction must remain parameterized and safe for both
SQLite and PostgreSQL; replacing the complete map would reintroduce lost
updates across workspaces.
- Unlocked caller context currently doubles as form state. Separating fallback
context from manual/locked selection must preserve edit and Improve Kandev
locked flows.
- User settings can arrive after the dialog opens. The resolver must not
overwrite a manual selection made in the current open cycle when settings
settle.
- Workflow deletion does not eagerly prune history; validation ignores stale
IDs and the next successful creation overwrites that workspace entry.
- Changing the board/list filter, dialog composition, workflow ordering, and
cancelled-dialog persistence remain out of scope.