fix(web): keep repository tasks on worktree defaults (#2136)
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
---
|
||||
spec: docs/specs/tasks/task-create-executor-default.md
|
||||
created: 2026-08-01
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Implementation Plan: Task Create Executor Default
|
||||
|
||||
## Overview
|
||||
|
||||
Change the task-create selection order so source/workspace policy resolves the executor before a
|
||||
portable last-used profile is considered. Lock the behavior down first with focused Vitest
|
||||
regressions, then prove the backend-settings/cross-browser path through the existing Create Task
|
||||
Playwright flow. No backend, schema, API, or rendered-layout change is required.
|
||||
|
||||
## Confirmed root cause
|
||||
|
||||
`pickDefaultExecutorProfileId` accepts any eligible `lastUsedExecutorProfileId` before calling
|
||||
`pickDefaultExecutorId`. For an ordinary repository-backed task, a valid saved Local profile is
|
||||
therefore returned before the no-workspace-default Worktree fallback is resolved. The executor-ID
|
||||
effect also waits for any valid saved profile and then derives the executor from that profile,
|
||||
making the Local selection portable across browsers and updates by design.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Executor policy and profile restoration
|
||||
|
||||
- Update `apps/web/components/task-create-dialog-effects.ts` so
|
||||
`pickDefaultExecutorProfileId` first obtains the executor selected by `pickDefaultExecutorId`.
|
||||
- Restore `lastUsedExecutorProfileId` only when its flattened profile candidate belongs to that
|
||||
resolved executor.
|
||||
- Prefer the resolved executor's first profile when the saved profile belongs to another executor.
|
||||
Preserve the current first-eligible fallback if the resolved executor has no usable profile.
|
||||
- Update `shouldWaitForLastUsedExecutorProfile` so executor-ID auto-pick waits only for a saved
|
||||
profile compatible with the resolved executor. A saved Local profile must not suppress the
|
||||
Worktree executor-ID fallback for an ordinary repository-backed task.
|
||||
- Preserve the current task-source rules in `pickDefaultExecutorId`: explicit unmanaged local
|
||||
paths and repository-less tasks prefer direct Local; a valid workspace default wins; otherwise
|
||||
`DEFAULT_LOCAL_EXECUTOR_TYPE` (`worktree`) is preferred.
|
||||
- Keep task-create persistence and submit handlers unchanged. A manual selection still applies to
|
||||
the current task and is still recorded by the backend.
|
||||
|
||||
### Mobile design contract
|
||||
|
||||
- **Desktop and mobile outcome:** the existing Executor Profile selector shows the same
|
||||
policy-resolved profile for the same task source and workspace settings.
|
||||
- **Nearest shipped mobile exemplar:** the existing mobile Create Task dialog remains the
|
||||
composition and interaction baseline.
|
||||
- **Presentation and hierarchy:** no markup, overlay, scrolling, safe-area, focus, or touch target
|
||||
changes; only the shared selection state changes.
|
||||
- **Shared logic:** desktop and mobile already call the same task-create effects, so the fix stays
|
||||
in that shared hook.
|
||||
- **Mobile proof:** focused unit coverage is sufficient because this is state normalization inside
|
||||
the unchanged dialog. Existing mobile task-create E2E remains the layout/touch coverage; no new
|
||||
mobile-only interaction is introduced.
|
||||
|
||||
## Tests
|
||||
|
||||
- **What:** a saved Local profile cannot override the Worktree fallback for an ordinary
|
||||
repository-backed task with no workspace default.
|
||||
- **File:** `apps/web/components/task-create-dialog-effects-executor.test.ts`
|
||||
- **How:** render `useDefaultSelectionsEffect` with Local and Worktree executors, loaded backend
|
||||
settings, and `lastUsedExecutorProfileId` set to Local; assert Worktree profile and executor ID.
|
||||
- **What:** an explicit Local workspace default remains authoritative.
|
||||
- **File:** `apps/web/components/task-create-dialog-effects-executor.test.ts`
|
||||
- **How:** use the same executor set with `default_executor_id` set to Local and a saved Worktree
|
||||
profile; assert Local profile and executor ID.
|
||||
- **What:** source constraints remain authoritative for an explicit local path and repository-less
|
||||
task.
|
||||
- **File:** `apps/web/components/task-create-dialog-effects-executor.test.ts`
|
||||
- **How:** extend the existing local-path/no-repository cases with a saved Worktree profile and
|
||||
assert a direct Local profile.
|
||||
- **What:** a saved alternative Worktree profile is still restored when Worktree is the resolved
|
||||
executor, and missing-profile fallback behavior remains unchanged.
|
||||
- **File:** `apps/web/components/task-create-dialog-effects-executor.test.ts`
|
||||
- **How:** retain the existing restoration case and add/adjust table cases around resolved
|
||||
executor membership.
|
||||
|
||||
## E2E Tests
|
||||
|
||||
- **Scenario:** **GIVEN** backend user settings contain a last-used Local profile and the workspace
|
||||
has no executor default, **WHEN** Create Task opens for the seeded repository in a browser
|
||||
context, **THEN** the Executor Profile selector shows Worktree.
|
||||
- **File:** `apps/web/e2e/tests/task/create-task-executor-default.spec.ts`
|
||||
- **What to verify:** seed the Local profile through `saveUserSettings`, navigate through the
|
||||
regular Kanban UI, open the dialog, and assert the visible Worktree selection.
|
||||
- **Scenario:** **GIVEN** the workspace explicitly defaults to Local, **WHEN** Create Task opens for
|
||||
the seeded repository, **THEN** the selector shows Local even when the saved profile is Worktree.
|
||||
- **File:** `apps/web/e2e/tests/task/create-task-executor-default.spec.ts`
|
||||
- **What to verify:** set and later restore `default_executor_id`, assert the visible Local
|
||||
profile, and restore the backend last-used profile to Worktree so the worker-scoped fixture
|
||||
does not leak state.
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] [Task 01: Enforce executor-first profile selection](task-01-executor-first-selection.md)
|
||||
- [x] [Task 02: Prove portable settings behavior in Create Task](task-02-create-task-e2e.md)
|
||||
|
||||
Execution is sequential in the primary conversation. No subagent delegation is planned or
|
||||
authorized.
|
||||
|
||||
## Validation
|
||||
|
||||
Run from the repository root unless noted otherwise:
|
||||
|
||||
- `cd apps/web && pnpm test -- --run components/task-create-dialog-effects-executor.test.ts components/task-create-dialog-executor-wait.test.ts`
|
||||
- `cd apps/web && pnpm run typecheck`
|
||||
- `cd apps/web && pnpm e2e:run --project chromium tests/task/create-task-executor-default.spec.ts`
|
||||
|
||||
Completed validation: 14 focused Vitest tests passed, the web typecheck passed, targeted ESLint and
|
||||
Prettier checks passed, and the managed Chromium run passed both Create Task scenarios.
|
||||
|
||||
The managed E2E runner performs the required production build before Playwright. This worktree ran
|
||||
`cd apps && pnpm install --frozen-lockfile` before the recorded validation; fresh worktrees must run
|
||||
that prerequisite if frontend dependencies are missing.
|
||||
|
||||
## Risks
|
||||
|
||||
- The executor-ID and executor-profile effects run independently and settle through microtasks.
|
||||
Tests must assert the final paired selection, not only one setter call, so a transient mismatch
|
||||
cannot become a regression.
|
||||
- Existing E2E fixtures preserve task-create last-used fields on partial settings writes. New tests
|
||||
must restore a Worktree profile, and tests that set a workspace default must clear it in cleanup.
|
||||
- The single stored last-used profile cannot retain a preferred profile for multiple executor
|
||||
types; adding per-executor history remains explicitly out of scope.
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: "01-executor-first-selection"
|
||||
title: "Enforce executor-first profile selection"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/tasks/task-create-executor-default.md"
|
||||
---
|
||||
|
||||
# Task 01: Enforce executor-first profile selection
|
||||
|
||||
## Intent
|
||||
|
||||
Prevent a portable last-used profile from changing the executor selected by task-source and
|
||||
workspace policy while preserving intentional workspace and local-source defaults.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- An ordinary repository-backed task with no workspace default selects Worktree even when the
|
||||
backend last-used profile is Local.
|
||||
- Explicit Local workspace defaults, explicit unmanaged local paths, and repository-less tasks
|
||||
still select Local.
|
||||
- A saved profile is restored only within the resolved executor, with existing eligible fallback
|
||||
behavior preserved when that executor has no profile.
|
||||
|
||||
## TDD sequence
|
||||
|
||||
1. Add the Local-last-used/Worktree-default regression to
|
||||
`task-create-dialog-effects-executor.test.ts` and run it to confirm RED for the expected Local
|
||||
selection.
|
||||
2. Add the workspace-default and source-constraint cases.
|
||||
3. Change the smallest selection helpers/effects required in
|
||||
`task-create-dialog-effects.ts` and run the focused suite to GREEN.
|
||||
4. Refactor helper names/log metadata only where necessary to describe executor compatibility.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/components/task-create-dialog-effects.ts`
|
||||
- `apps/web/components/task-create-dialog-effects-executor.test.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential` — this task owns the shared selection behavior required by the E2E task.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec sections `What`, `Persistence guarantees`, and `Scenarios`.
|
||||
- Plan sections `Confirmed root cause` and `Executor policy and profile restoration`.
|
||||
- ADR 0028, ADR 0041, and ADR-2026-08-01-repository-task-executor-defaults.
|
||||
|
||||
## Verification
|
||||
|
||||
- `cd apps && pnpm --filter @kandev/web test -- components/task-create-dialog-effects-executor.test.ts`
|
||||
- `cd apps/web && pnpm run typecheck`
|
||||
|
||||
## Risks
|
||||
|
||||
- Independent effects can produce order-sensitive setter calls; assert the settled executor/profile
|
||||
pair through rerenders where needed.
|
||||
- Do not remove or rewrite backend last-used persistence as part of this task.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report the RED failure, final files changed, focused test/typecheck results, remaining risks, and
|
||||
update this task plus `plan.md` status in the same conversation.
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
id: "02-create-task-e2e"
|
||||
title: "Prove portable settings behavior in Create Task"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on: ["01-executor-first-selection"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/tasks/task-create-executor-default.md"
|
||||
---
|
||||
|
||||
# Task 02: Prove portable settings behavior in Create Task
|
||||
|
||||
## Intent
|
||||
|
||||
Exercise the real backend-settings-to-boot-state-to-dialog path so the isolation-first default is
|
||||
proven across the same boundary used by another browser or an upgraded frontend.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- With a backend-saved Local profile and no workspace executor default, the visible Create Task
|
||||
selector shows Worktree for the seeded repository.
|
||||
- With an explicit Local workspace default, the visible selector shows Local even when the saved
|
||||
profile is Worktree.
|
||||
- The test restores both workspace and task-create preference state so later worker-scoped tests do
|
||||
not inherit its executor choice.
|
||||
|
||||
## TDD sequence
|
||||
|
||||
1. Add the backend-saved Local-profile UI regression and run the focused Playwright grep to confirm
|
||||
RED against the current selection behavior if Task 01 is temporarily reverted or before its
|
||||
production change.
|
||||
2. Add the explicit workspace-default scenario and deterministic cleanup.
|
||||
3. Run both scenarios against the production build through the managed E2E runner.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/e2e/tests/task/create-task-executor-default.spec.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Task 01 must be complete.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential` — it validates Task 01 through shared task-create fixtures and production assets.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec scenarios for portable Local last-used state and explicit workspace defaults.
|
||||
- Existing `ApiClient.saveUserSettings`, `ApiClient.updateWorkspace`, `ApiClient.listExecutors`,
|
||||
`KanbanPage`, and `executor-profile-selector` patterns.
|
||||
- The fixture warning that partial task-create settings writes preserve omitted fields.
|
||||
|
||||
## Verification
|
||||
|
||||
- `cd apps/web && pnpm e2e:run --project chromium tests/task/create-task-executor-default.spec.ts`
|
||||
|
||||
## Mobile parity
|
||||
|
||||
No mobile-only E2E is added because the change modifies shared selection state inside the existing
|
||||
responsive dialog and does not alter composition, navigation, scrolling, touch targets, or pointer
|
||||
behavior. Existing mobile Create Task specs continue to cover reachability and layout.
|
||||
|
||||
## Risks
|
||||
|
||||
- Cleanup must restore `default_executor_id` and a non-empty Worktree
|
||||
`task_create_last_used.executor_profile_id`; omitting either can leak through the worker-scoped
|
||||
backend.
|
||||
- Assertions should target the selected profile text, not option ordering.
|
||||
|
||||
## Output contract
|
||||
|
||||
The focused managed Chromium run passed both scenarios (`2 passed`); each test restores the workspace
|
||||
default and backend task-create preference in `finally` cleanup, and no blockers remain.
|
||||
Reference in New Issue
Block a user