diff --git a/apps/web/components/task/tab-rename-input.tsx b/apps/web/components/task/tab-rename-input.tsx index 3c41ec331b..51413cc5cc 100644 --- a/apps/web/components/task/tab-rename-input.tsx +++ b/apps/web/components/task/tab-rename-input.tsx @@ -40,6 +40,7 @@ export function TabRenameInput({ className="flex h-full items-center gap-1 px-2" // Stop the click from selecting the tab while we type. onClick={(e) => e.stopPropagation()} + onDoubleClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} > {seqBadge != null && ( diff --git a/apps/web/e2e/tests/session/session-tab-rename.spec.ts b/apps/web/e2e/tests/session/session-tab-rename.spec.ts new file mode 100644 index 0000000000..f6b358c0a2 --- /dev/null +++ b/apps/web/e2e/tests/session/session-tab-rename.spec.ts @@ -0,0 +1,66 @@ +import { test, expect } from "../../fixtures/test-base"; +import { SessionPage } from "../../pages/session-page"; + +test.describe("Session tab rename", () => { + // @covers AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.1 through .3 + test("keeps rename-input double-clicks out of the maximize handler", async ({ + testPage, + apiClient, + seedData, + }) => { + const task = await apiClient.createTaskWithAgent( + seedData.workspaceId, + "Agent Tab Rename Double-click", + seedData.agentProfileId, + { + description: "/e2e:simple-message", + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + repository_ids: [seedData.repositoryId], + }, + ); + if (!task.session_id) throw new Error("createTaskWithAgent did not return a session_id"); + + await testPage.goto(`/t/${task.id}`); + const session = new SessionPage(testPage); + await session.waitForLoad(); + await session.waitForChatIdle({ timeout: 30_000 }); + + const tab = session.sessionTabBySessionId(task.session_id); + await expect(tab).toBeVisible(); + const header = testPage.locator( + `.dv-tabs-and-actions-container:has([data-testid="session-tab-${task.session_id}"])`, + ); + const maximizeButton = header.getByTestId("dockview-maximize-btn"); + await expect(maximizeButton.locator(".tabler-icon-arrows-maximize")).toBeVisible(); + + await tab.click({ button: "right" }); + await session.contextMenuItem("Rename").click(); + const input = testPage.getByTestId("session-tab-rename-input"); + await input.click(); + await expect(input).toBeFocused(); + await input.fill("implementation"); + await input.dblclick(); + await expect(maximizeButton.locator(".tabler-icon-arrows-maximize")).toBeVisible(); + const selection = await input.evaluate((element: HTMLInputElement) => ({ + start: element.selectionStart, + end: element.selectionEnd, + length: element.value.length, + })); + expect(selection).toEqual({ start: 0, end: 14, length: 14 }); + + await testPage.keyboard.press("Escape"); + await expect(input).not.toBeVisible(); + await tab.dblclick(); + await expect(maximizeButton.locator(".tabler-icon-arrows-minimize")).toBeVisible(); + + await tab.click({ button: "right" }); + await session.contextMenuItem("Rename").click(); + await input.click(); + await expect(input).toBeFocused(); + await input.fill("implementation"); + await input.dblclick(); + await expect(maximizeButton.locator(".tabler-icon-arrows-minimize")).toBeVisible(); + await testPage.keyboard.press("Escape"); + }); +}); diff --git a/docs/plans/agent-tab-rename-double-click/plan.md b/docs/plans/agent-tab-rename-double-click/plan.md new file mode 100644 index 0000000000..d14c096bef --- /dev/null +++ b/docs/plans/agent-tab-rename-double-click/plan.md @@ -0,0 +1,95 @@ +--- +created: 2026-08-31 +status: done +requirements: + - REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002 +system_design: + - ../../specs/ui/system-design/task-agent-tab-reconciliation.md +legacy_specs: [] +--- + +# Implementation Plan: Isolate Agent Tab Rename Double-clicks + +## Overview + +Keep text-selection double-clicks inside the shared tab rename editor so they +cannot reach the surrounding Agent-tab maximize handler. One work order adds a +browser regression first, applies the shared event-boundary correction, and +runs the focused frontend checks. + +## Scope + +### In scope + +- Preserve native double-click text selection in an active Agent-tab rename + input. +- Prevent that double-click from maximizing or restoring the Dockview group. +- Preserve tab-level double-click maximize behavior outside rename mode. +- Apply the isolation through the shared task tab rename editor. + +### Out of scope + +- Session-name persistence, validation, commit, cancel, or error behavior. +- Dockview maximize state management or layout persistence. +- Phone and tablet session-control composition. +- Quick Chat tab rename behavior, which uses a different editor. + +## Confirmed root cause + +`SessionTab` mounts `TabRenameInput` inside a `ContextMenuTrigger` whose +`onDoubleClick` calls `useTabMaximizeOnDoubleClick`. The editor stops bubbling +`mousedown` and `click`, but browsers emit a separate bubbling `dblclick` event. +That event reaches the parent maximize handler, which prevents the default text +selection and toggles the Dockview group. + +## Technical approach + +- Add a focused Chromium Playwright scenario in + `apps/web/e2e/tests/session/session-tab-rename.spec.ts`. Enter Agent-tab rename + mode from the context menu, double-click the input, assert native selection, + and assert that the group remains unmaximized. +- Update `TabRenameInput` in + `apps/web/components/task/tab-rename-input.tsx` to stop propagation of the + shared editor's `dblclick` event without calling `preventDefault`. +- Leave `SessionTab` and `useTabMaximizeOnDoubleClick` unchanged so ordinary + tab-surface double-clicks retain their current maximize-or-restore behavior. + +## Tests + +- `AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.2`: the Playwright regression + verifies selected input text and unchanged maximize state after an editor + double-click. +- `AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.3`: the same scenario verifies the + existing tab-surface maximize path remains available outside the editor. + +## E2E tests + +- Chromium: + `apps/web/e2e/tests/session/session-tab-rename.spec.ts` covers the affected + fine-pointer Dockview workflow. +- No mobile E2E change is required because phone and tablet task surfaces do + not mount desktop Dockview Agent tabs or their maximize gesture. + +## Work orders + +- [x] [Task 01: Isolate Rename Input Double-clicks](task-01-isolate-rename-input-double-clicks.md) + +## Verification results + +Completed on 2026-08-31. + +- The Chromium regression failed before the fix because the rename-input + double-click removed the group's maximize icon by maximizing the group. +- The rebuilt production E2E bundle passed the focused scenario after the + shared editor stopped the bubbling `dblclick` event. +- Web typecheck, focused ESLint, and `git diff --check` passed. +- No mobile test changed because phone and tablet do not mount the affected + Dockview Agent-tab surface. + +## Risks + +- Calling `preventDefault` at the editor boundary would fix maximize while also + breaking the text-selection behavior being protected. The correction must + stop propagation only. +- Testing only the two individual click events would miss the separately + dispatched `dblclick` that causes the regression. diff --git a/docs/plans/agent-tab-rename-double-click/task-01-isolate-rename-input-double-clicks.md b/docs/plans/agent-tab-rename-double-click/task-01-isolate-rename-input-double-clicks.md new file mode 100644 index 0000000000..4696b24c68 --- /dev/null +++ b/docs/plans/agent-tab-rename-double-click/task-01-isolate-rename-input-double-clicks.md @@ -0,0 +1,101 @@ +--- +id: "01-isolate-rename-input-double-clicks" +title: "Isolate Rename Input Double-clicks" +status: done +wave: 1 +depends_on: [] +plan: "plan.md" +requirements: + - REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002 +acceptance_criteria: + - AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.1 + - AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.2 + - AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.3 +system_design: + - ../../specs/ui/system-design/task-agent-tab-reconciliation.md +--- + +# Task 01: Isolate Rename Input Double-clicks + +## Summary + +Add a failing desktop browser regression for the Agent-tab rename interaction, +then make the shared task tab rename editor contain double-clicks without +disabling native input selection. Preserve the existing maximize behavior for +double-clicks outside rename mode. + +## In scope + +- Add the focused Agent-tab rename Playwright regression. +- Stop bubbling `dblclick` events at `TabRenameInput`. +- Verify native selection and unchanged Dockview maximize state. + +## Out of scope + +- Rename persistence and backend session APIs. +- Dockview layout or maximize-state refactors. +- Quick Chat rename behavior. +- Phone and tablet UI changes. + +## Acceptance + +- The new Playwright scenario fails on the current code because an editor + double-click maximizes the Agent-tab group. +- Double-clicking the active rename input selects text without changing the + Dockview group's maximize state. +- Double-clicking the non-editing Agent-tab surface still toggles maximize. + +## Verification + +```bash +pnpm e2e:run tests/session/session-tab-rename.spec.ts +pnpm run typecheck +pnpm exec eslint components/task/tab-rename-input.tsx e2e/tests/session/session-tab-rename.spec.ts +git diff --check +``` + +Run the first three commands from `apps/web`. Run `git diff --check` from the +repository root. + +## Files likely touched + +- `apps/web/e2e/tests/session/session-tab-rename.spec.ts` +- `apps/web/components/task/tab-rename-input.tsx` + +## Dependencies + +None. + +## Risks + +- Preventing the default `dblclick` action would suppress native input text + selection. +- A selector that observes a different Dockview group could allow the browser + regression to pass while the Agent group still toggles. + +## Parallelism + +`sequential` + +## Inputs + +- `REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002` +- `AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.1` through `.3` +- `docs/specs/ui/system-design/task-agent-tab-reconciliation.md` +- `apps/web/components/task/session-tab.tsx` +- `apps/web/components/task/tab-rename-input.tsx` +- `apps/web/components/task/use-tab-maximize.ts` + +## Results + +Completed on 2026-08-31. + +- RED: `pnpm e2e:run --no-build tests/session/session-tab-rename.spec.ts` + failed on the expected maximize-state assertion after an editor + double-click. +- GREEN: `pnpm e2e:run tests/session/session-tab-rename.spec.ts` rebuilt the + production frontend and passed 1 Chromium test. +- `pnpm run typecheck` passed. +- `pnpm exec eslint components/task/tab-rename-input.tsx e2e/tests/session/session-tab-rename.spec.ts` + passed. +- `git diff --check` passed. diff --git a/docs/specs/ui/requirements/task-agent-tab-reconciliation.md b/docs/specs/ui/requirements/task-agent-tab-reconciliation.md index facefeec03..ea91cb1dd9 100644 --- a/docs/specs/ui/requirements/task-agent-tab-reconciliation.md +++ b/docs/specs/ui/requirements/task-agent-tab-reconciliation.md @@ -43,13 +43,36 @@ task workbench becomes usable. shall continue to project the same task session membership through their existing session controls without mounting desktop workbench tabs. +### REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002: Agent Tab Rename Isolation + +**Intent:** Keep inline Agent-tab name editing independent from the desktop +workbench commands attached to the surrounding tab. + +#### Acceptance criteria + +- **AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.1:** When a fine-pointer user + chooses **Rename** for an Agent tab, the tab shall show a focused inline input + with its current name selected. +- **AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.2:** While the inline rename input + is active, double-clicking its text shall preserve native text selection and + shall not maximize or restore the surrounding Dockview group. +- **AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.3:** Outside rename mode, + double-clicking an eligible desktop workbench tab shall retain the existing + maximize-or-restore behavior. +- **AC-UI-TASK-AGENT-TAB-RECONCILIATION-002.4:** Phone and tablet session + controls shall retain their existing rename and navigation behavior without + exposing desktop Dockview maximize gestures. + ## Out of scope - Changing session creation, lifecycle, ordering, or backend APIs. - Changing saved desktop layout geometry or the active-panel rules for valid non-Agent panels. +- Changing session-name persistence or the rename commit, cancel, and failure + behavior. - Redesigning phone or tablet task navigation and session controls. ## Implementation plan - [Command-center task focus repair](../../../plans/command-center-task-focus/plan.md) +- [Agent tab rename double-click isolation](../../../plans/agent-tab-rename-double-click/plan.md) diff --git a/docs/specs/ui/system-design/task-agent-tab-reconciliation.md b/docs/specs/ui/system-design/task-agent-tab-reconciliation.md index 49c5582322..e33279c9e2 100644 --- a/docs/specs/ui/system-design/task-agent-tab-reconciliation.md +++ b/docs/specs/ui/system-design/task-agent-tab-reconciliation.md @@ -3,6 +3,7 @@ status: current system: ui requirements: - REQ-UI-TASK-AGENT-TAB-RECONCILIATION-001 + - REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002 --- # Task Agent Tab Reconciliation System Design @@ -19,6 +20,7 @@ the desktop workbench. | Requirement | Design section | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `REQ-UI-TASK-AGENT-TAB-RECONCILIATION-001` | [Components and responsibilities](#components-and-responsibilities), [Control flow](#control-flow), [Responsive behavior](#responsive-behavior) | +| `REQ-UI-TASK-AGENT-TAB-RECONCILIATION-002` | [Inline rename event boundary](#inline-rename-event-boundary), [Responsive behavior](#responsive-behavior), [Verification design](#verification-design) | ## Components and responsibilities @@ -34,6 +36,27 @@ the desktop workbench. - `runAutoSessionTabEffect` reads the current application state, removes stale Agent panels, ensures the active session panel, and adds current sibling session panels without activating them. +- `SessionTab` owns the fine-pointer Agent-tab context menu and attaches the + shared Dockview maximize handler to the tab surface. +- `TabRenameInput` owns the inline editor's pointer and keyboard event boundary. + It is shared with task terminal-tab rename mode. +- `useTabMaximizeOnDoubleClick` owns maximize-or-restore behavior outside an + active inline editor. + +## Inline rename event boundary + +`SessionTab` renders `TabRenameInput` inside the same `ContextMenuTrigger` that +receives tab-level double-clicks. Browsers dispatch `dblclick` as a separate +bubbling event after the individual click events. Stopping only `mousedown` and +`click` therefore does not isolate the editor from the tab-level maximize +handler. + +The shared rename editor stops propagation of `mousedown`, `click`, and +`dblclick`. Its `dblclick` boundary does not prevent the default browser action, +so native input text selection remains available. The parent tab maximize +handler remains unchanged and continues to own double-clicks that originate +outside the editor. This boundary also protects the shared task terminal rename +editor without changing terminal naming behavior. ## Data and contracts @@ -69,7 +92,8 @@ the effect reads the latest session list. Desktop uses Dockview Agent tabs. Phone and tablet layouts do not mount the desktop Dockview workbench. Their existing session controls continue to read the same application-store membership. No mobile composition or touch target -changes. +changes. Inline rename event isolation applies only where the desktop Dockview +tab and editor are mounted. ## Verification design @@ -77,4 +101,8 @@ A React hook regression test hydrates a multi-session task while the Dockview API is null. The test publishes the API later and verifies reconciliation. Existing pure reconciliation tests continue to cover active-tab and sibling behavior. A Playwright scenario opens a multi-session task from Cmd+K and -verifies that every Agent tab appears without a reload. +verifies that every Agent tab appears without a reload. A focused desktop +Playwright regression enters Agent-tab rename mode, double-clicks the input, +and verifies that native text selection remains active while the Dockview group +stays unmaximized. Mobile coverage is unchanged because phone and tablet do not +mount the affected Dockview tab surface.