fix(web): keep configuration chat launcher visible (#3062)
This commit is contained in:
@@ -12,7 +12,6 @@ import { QuickChatSessionView } from "@/components/quick-chat/quick-chat-session
|
||||
import { isQuickChatSetupSessionId } from "@/lib/state/slices/ui/quick-chat-session";
|
||||
import { ConfigChatSetup } from "./config-chat-setup";
|
||||
import { useConfigChat } from "./use-config-chat";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function useConfigChatPanelStore() {
|
||||
return useAppStore(
|
||||
@@ -155,12 +154,7 @@ export const ConfigChatPanel = memo(function ConfigChatPanel({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
aria-hidden={panel.isOpen}
|
||||
tabIndex={panel.isOpen ? -1 : undefined}
|
||||
className={cn(
|
||||
"fixed bottom-[calc(1.5rem+var(--app-status-bar-height))] right-6 z-50 h-12 w-12 cursor-pointer rounded-full shadow-lg",
|
||||
panel.isOpen && "pointer-events-none opacity-0",
|
||||
)}
|
||||
className="fixed bottom-[calc(1.5rem+var(--app-status-bar-height))] right-6 z-50 h-12 w-12 cursor-pointer rounded-full shadow-lg"
|
||||
aria-label={t("common:configurationChat")}
|
||||
>
|
||||
<IconSparkles className="h-6 w-6" />
|
||||
|
||||
@@ -140,6 +140,19 @@ test.describe("Configuration Chat", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps the floating launcher visible and operable while open", async ({ testPage }) => {
|
||||
const popover = await openConfigChatFromSettings(testPage);
|
||||
const launcher = testPage.getByRole("button", { name: "Configuration Chat", exact: true });
|
||||
|
||||
await expect(launcher).toBeVisible();
|
||||
await expect(launcher).toBeEnabled();
|
||||
await expect(launcher).not.toHaveAttribute("aria-hidden", "true");
|
||||
await expect(launcher).not.toHaveAttribute("tabindex", "-1");
|
||||
|
||||
await launcher.click();
|
||||
await expect(popover).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("starts floating, expands the same session, restores, and continues", async ({
|
||||
testPage,
|
||||
}) => {
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
import { test, expect } from "../../fixtures/test-base";
|
||||
|
||||
test.describe("Configuration Chat on mobile", () => {
|
||||
test("keeps the floating launcher visible and operable while open", async ({ testPage }) => {
|
||||
await testPage.goto("/settings/agents");
|
||||
await testPage.getByRole("button", { name: "Configuration Chat" }).tap();
|
||||
|
||||
const popover = testPage.getByTestId("config-chat-popover");
|
||||
const launcher = testPage.getByRole("button", { name: "Configuration Chat", exact: true });
|
||||
await expect(popover).toBeVisible({ timeout: 10_000 });
|
||||
await expect(launcher).toBeVisible();
|
||||
await expect(launcher).toBeEnabled();
|
||||
await expect(launcher).not.toHaveAttribute("aria-hidden", "true");
|
||||
await expect(launcher).not.toHaveAttribute("tabindex", "-1");
|
||||
|
||||
const viewport = testPage.viewportSize();
|
||||
const launcherBox = await launcher.boundingBox();
|
||||
expect(viewport).not.toBeNull();
|
||||
expect(launcherBox).not.toBeNull();
|
||||
expect(launcherBox!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(launcherBox!.x + launcherBox!.width).toBeLessThanOrEqual(viewport!.width);
|
||||
expect(launcherBox!.y).toBeGreaterThanOrEqual(0);
|
||||
expect(launcherBox!.y + launcherBox!.height).toBeLessThanOrEqual(viewport!.height);
|
||||
|
||||
await launcher.tap();
|
||||
await expect(popover).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("starts floating, expands full-screen, and answers a clarification", async ({
|
||||
testPage,
|
||||
apiClient,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
created: 2026-08-26
|
||||
status: done
|
||||
requirements:
|
||||
- REQ-UI-QUICK-TERMINAL-001
|
||||
system_design:
|
||||
- ../../specs/ui/system-design/quick-terminal.md
|
||||
legacy_specs: []
|
||||
---
|
||||
|
||||
# Implementation Plan: Configuration Chat Launcher Visibility
|
||||
|
||||
## Overview
|
||||
|
||||
Keep the Configuration Chat floating launcher visible and operable while its Settings popover is
|
||||
open. The current component deliberately hides and disables the trigger in the open state even
|
||||
though the popover is already positioned above it. One focused TDD work order adds desktop and
|
||||
mobile regression coverage before removing that open-state suppression.
|
||||
|
||||
The UI system owns this repair because the defect affects the presentation contract of an existing
|
||||
launcher. Task-backed configuration-chat identity, persistence, and lifecycle remain unchanged.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Preserve the floating launcher below the open Configuration Chat panel on Settings routes.
|
||||
- Let pointer, keyboard, and touch activation of the visible launcher close the open panel through
|
||||
the existing popover state transition.
|
||||
- Prove the same launcher outcome in desktop Chromium and the mobile Chrome project.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Configuration-chat creation, persistence, agent-profile selection, or session restoration.
|
||||
- Popover dimensions, panel content, the header close action, or Quick Chat expansion behavior.
|
||||
- Settings floating-save placement and behavior.
|
||||
|
||||
## Technical approach
|
||||
|
||||
### Launcher state
|
||||
|
||||
Update `ConfigChatPanel` in
|
||||
`apps/web/components/config-chat/config-chat-panel.tsx` so the floating `PopoverTrigger` retains
|
||||
its normal accessibility, focus, pointer, and opacity state while `panel.isOpen` is true. Preserve
|
||||
the controlled `Popover`, its existing open-change handler, fixed launcher geometry, and tooltip
|
||||
suppression. Activating the retained trigger then closes the same popover without adding another
|
||||
state path.
|
||||
|
||||
### Responsive contract
|
||||
|
||||
Desktop and mobile keep the current fixed bottom-right entry point and top-aligned popover. The
|
||||
launcher is the primary thumb-reachable floating action on phones. The panel remains the only
|
||||
scroll surface. The change does not affect safe areas or breakpoints. The nearest mobile exemplar
|
||||
is `components/kanban/mobile-fab.tsx` for the persistent, touch-sized floating action. The existing
|
||||
Configuration Chat popover remains the source for the surface geometry.
|
||||
|
||||
## Tests
|
||||
|
||||
- `AC-UI-QUICK-TERMINAL-001.2`: extend
|
||||
`apps/web/e2e/tests/settings/config-chat-popover.spec.ts` to assert that the launcher remains
|
||||
visible and enabled after opening the popover, then closes the panel when activated again.
|
||||
|
||||
## E2E tests
|
||||
|
||||
- Desktop Chromium: run the focused Configuration Chat popover spec and prove launcher visibility,
|
||||
operability, and toggle-close behavior.
|
||||
- Mobile Chrome: extend `apps/web/e2e/tests/settings/mobile-configuration-chat.spec.ts` with the
|
||||
same touch path, including full launcher containment in the viewport after the panel opens.
|
||||
|
||||
## Work orders
|
||||
|
||||
- [x] [Task 01: Keep the Configuration Chat launcher visible](task-01-keep-launcher-visible.md)
|
||||
|
||||
## Verification results
|
||||
|
||||
Passed on 2026-08-26:
|
||||
|
||||
- `cd apps && pnpm install --frozen-lockfile`
|
||||
- `cd apps/web && pnpm run typecheck`
|
||||
- `cd apps/web && pnpm e2e:run e2e/tests/settings/config-chat-popover.spec.ts` (6 passed)
|
||||
- `cd apps/web && pnpm e2e:run --project mobile-chrome e2e/tests/settings/mobile-configuration-chat.spec.ts` (2 passed)
|
||||
|
||||
## Risks
|
||||
|
||||
- The visible trigger shares the popover's anchor position. Regression coverage must prove that it
|
||||
does not block panel controls or move outside a narrow viewport.
|
||||
- A tooltip must not open over the active panel when focus returns to or hovers the retained
|
||||
launcher.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: "01-keep-launcher-visible"
|
||||
title: "Keep the Configuration Chat launcher visible"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-UI-QUICK-TERMINAL-001
|
||||
acceptance_criteria:
|
||||
- AC-UI-QUICK-TERMINAL-001.2
|
||||
system_design:
|
||||
- ../../specs/ui/system-design/quick-terminal.md
|
||||
---
|
||||
|
||||
# Task 01: Keep the Configuration Chat launcher visible
|
||||
|
||||
## Summary
|
||||
|
||||
Add a failing desktop and mobile regression for the Settings Configuration Chat launcher, then
|
||||
remove the component's open-state visibility and interaction suppression. Keep the existing
|
||||
controlled popover and responsive geometry as the only open/close mechanism.
|
||||
|
||||
## In scope
|
||||
|
||||
- Add desktop E2E assertions that the launcher stays visible and enabled with the popover open and
|
||||
closes the panel when activated again.
|
||||
- Add the equivalent touch and viewport-containment assertions to the mobile Configuration Chat
|
||||
flow.
|
||||
- Remove the launcher's open-state `aria-hidden`, tab-order removal, pointer-event suppression, and
|
||||
zero-opacity styling while preserving tooltip suppression during the open state.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Changing Configuration Chat content, session behavior, or the header close action.
|
||||
- Repositioning or resizing the popover, floating-save host, or launcher.
|
||||
- Adding new user-facing copy or translation keys.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- The floating Configuration Chat launcher remains visible, enabled, keyboard-focusable, and
|
||||
touch-operable while its Settings popover is open.
|
||||
- Activating the retained launcher closes the popover through the existing `onOpenChange` path.
|
||||
- Focused desktop and mobile E2E tests pass without document horizontal overflow or launcher
|
||||
clipping.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps && pnpm install --frozen-lockfile
|
||||
cd apps/web && pnpm run typecheck
|
||||
cd apps/web && pnpm e2e:run e2e/tests/settings/config-chat-popover.spec.ts
|
||||
cd apps/web && pnpm e2e:run --project mobile-chrome e2e/tests/settings/mobile-configuration-chat.spec.ts
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/components/config-chat/config-chat-panel.tsx`
|
||||
- `apps/web/e2e/tests/settings/config-chat-popover.spec.ts`
|
||||
- `apps/web/e2e/tests/settings/mobile-configuration-chat.spec.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Risks
|
||||
|
||||
- Radix trigger focus and tooltip behavior can overlap the open popover if tooltip suppression is
|
||||
removed accidentally.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- `REQ-UI-QUICK-TERMINAL-001` and `AC-UI-QUICK-TERMINAL-001.2`.
|
||||
- `docs/specs/ui/system-design/quick-terminal.md` launcher and responsive behavior.
|
||||
- Existing Configuration Chat desktop and mobile Playwright specs.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented on 2026-08-26. The floating trigger no longer receives open-state `aria-hidden`,
|
||||
tab-order removal, pointer-event suppression, or zero-opacity styling. Tooltip suppression remains
|
||||
active while the popover is open. Desktop and mobile E2E coverage verifies visibility, enabled state,
|
||||
accessibility attributes, viewport containment on mobile, and toggle-close behavior.
|
||||
|
||||
Verification passed:
|
||||
|
||||
- `cd apps && pnpm install --frozen-lockfile`
|
||||
- `cd apps/web && pnpm run typecheck`
|
||||
- `cd apps/web && pnpm e2e:run e2e/tests/settings/config-chat-popover.spec.ts` (6 passed)
|
||||
- `cd apps/web && pnpm e2e:run --project mobile-chrome e2e/tests/settings/mobile-configuration-chat.spec.ts` (2 passed)
|
||||
@@ -2,7 +2,7 @@
|
||||
status: active
|
||||
system: ui
|
||||
created: 2026-08-03
|
||||
updated: 2026-08-06
|
||||
updated: 2026-08-26
|
||||
owners:
|
||||
- kandev
|
||||
---
|
||||
@@ -21,7 +21,7 @@ Quick Chat and Quick Terminal are both short-lived utilities reached from the sa
|
||||
#### Acceptance criteria
|
||||
|
||||
- **AC-UI-QUICK-TERMINAL-001.1:** Quick Chat is the single responsive dialog for ordinary chats, configuration chats, and host terminal tabs. Quick Terminal no longer opens a separate dialog.
|
||||
- **AC-UI-QUICK-TERMINAL-001.2:** Existing conversation launchers preserve their kind-specific behavior: generic Quick Chat shortcuts select an ordinary chat, configuration entry points select the workspace's configuration chat, and either opens its existing setup when no matching conversation exists.
|
||||
- **AC-UI-QUICK-TERMINAL-001.2:** Existing conversation launchers preserve their kind-specific behavior. A generic Quick Chat launcher selects an ordinary chat. A configuration entry point selects the workspace's configuration chat. Each launcher opens its setup when no matching conversation exists. On Settings routes, the Configuration Chat floating launcher remains visible and operable while its panel is open. Another activation closes the panel.
|
||||
- **AC-UI-QUICK-TERMINAL-001.3:** The existing Quick Terminal launchers use a reuse-or-create policy scoped to the active workspace: they open the most recently activated terminal tab when one exists, and create the first terminal tab otherwise.
|
||||
- **AC-UI-QUICK-TERMINAL-001.4:** The tab-strip plus button opens a creation menu grouped like the task-detail Dockview add menu: an **Agents** section with **New Agent**, a separator, and a **Terminals** section with **New Terminal**. Existing tabs remain directly selectable in the tab strip rather than being duplicated in the creation menu. Because the plus button sits at the leading edge of the tab strip, its menu opens toward the trailing edge (aligned to the button's start) so it does not overhang the workspace edge.
|
||||
- **AC-UI-QUICK-TERMINAL-001.5:** Choosing **New Agent** preserves the current ordinary/configuration setup flow. Choosing **New Terminal** always creates and activates a distinct host-shell terminal, even when another terminal exists.
|
||||
|
||||
@@ -4,7 +4,7 @@ system: ui
|
||||
requirements:
|
||||
- REQ-UI-QUICK-TERMINAL-001
|
||||
created: 2026-08-03
|
||||
updated: 2026-08-06
|
||||
updated: 2026-08-26
|
||||
owners:
|
||||
- kandev
|
||||
---
|
||||
@@ -36,6 +36,10 @@ them without losing work, and return to the most recent terminal without managin
|
||||
- Existing conversation launchers preserve their kind-specific behavior: generic Quick Chat
|
||||
shortcuts select an ordinary chat, configuration entry points select the workspace's
|
||||
configuration chat, and either opens its existing setup when no matching conversation exists.
|
||||
- `ConfigChatPanel` keeps its floating `PopoverTrigger` mounted, visible, and operable while the
|
||||
controlled popover is open. The existing top-aligned popover geometry leaves the trigger below
|
||||
the panel on desktop and phone viewports, and a second trigger activation follows the same close
|
||||
path as the panel header. The trigger tooltip stays suppressed while the panel is open.
|
||||
- The existing Quick Terminal launchers use a reuse-or-create policy scoped to the active workspace:
|
||||
they open the most recently activated terminal tab when one exists, and create the first terminal
|
||||
tab otherwise.
|
||||
@@ -257,6 +261,9 @@ checks, and Agents-page authorization behavior remain unchanged.
|
||||
- **GIVEN** a terminal and an ordinary chat both exist, **WHEN** the user alternates the Quick
|
||||
Terminal and generic Quick Chat launchers, **THEN** each launcher opens the most recently active
|
||||
matching tab without changing configuration-chat launcher behavior.
|
||||
- **GIVEN** Configuration Chat is open on a Settings route, **WHEN** the floating launcher renders
|
||||
below the panel, **THEN** it remains visible, keyboard-focusable, and touch-operable, and another
|
||||
activation closes the panel.
|
||||
- **GIVEN** an active terminal tab with sibling tabs, **WHEN** the user closes it, **THEN** only its
|
||||
PTY is stopped and the nearest remaining same-workspace tab becomes active.
|
||||
- **GIVEN** terminal tabs belong to two workspaces, **WHEN** the active workspace changes, **THEN**
|
||||
|
||||
Reference in New Issue
Block a user