fix(web): keep repository tasks on worktree defaults (#2136)

This commit is contained in:
Carlos Florêncio
2026-08-01 20:34:23 +01:00
committed by GitHub
parent 1e353fbbbc
commit b29ee5e6d2
13 changed files with 627 additions and 16 deletions
@@ -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.