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.
|
||||
Reference in New Issue
Block a user