Files
kandev/apps/web/e2e/helpers/clarification.ts
T
José Almeida 5efeae4ece feat: allow multiline custom answers in clarification overlay (#1566)
* feat: allow multiline custom answers in clarification overlay

Swap the single-line custom-answer input for an auto-growing textarea so users
can type multi-line replies to ask_user_question. Enter submits, Shift+Enter
inserts a newline; the box grows to ~6 lines then scrolls. Adds unit and e2e
coverage.

* fix: address review feedback on multiline clarification input

- Always preventDefault on desktop plain Enter so an empty draft can't leak a
  stray newline before the trim guard (Claude/CodeRabbit).
- On coarse-pointer devices let Enter insert a newline and submit via the
  existing Submit button; hide the keyboard hints on touch (mobile parity).
- Preserve newlines in the resolved clarification history message via
  whitespace-pre-wrap + items-start.
- Strengthen e2e to guard against fused lines; add Ctrl+Enter and touch tests.

* fix: touch submit button and IME guard for clarification input

- Guard against submitting on the Enter that confirms an IME candidate
  (e.nativeEvent.isComposing) on CJK keyboards.
- Add an inline Send button on coarse-pointer devices so single-question custom
  answers (which have no overlay Submit button) have a touch-reachable send
  path. Fixes the regression from switching touch Enter to newline.

* refactor: extract clarification custom-input controls

- Pull the Enter/⇧↵ hints and touch Send button into a CustomInputControls
  subcomponent to shrink ClarificationCustomInput.
- Compute the trimmed draft once and reuse it across the keydown handler and the
  Send button (disabled state, click, styling) so the conditions can't drift.
- Add a test asserting the touch Send button is disabled while isSubmitting.

* fix: ignore auto-repeat Enter in clarification custom input

A held Enter key emits auto-repeat keydown events; guard the handler with
e.repeat so it can't fire onSubmit more than once. Adds a regression test.

* fix: suppress newline on repeat Enter and add mobile clarification e2e

- Repeat Enter on desktop now calls preventDefault before bailing, so a held key
  can't leak newlines into the current or next question's textarea after the
  carousel advances; it still never re-fires onSubmit/finalize.
- Add a mobile-chrome (Pixel 5) e2e covering the touch path: Enter inserts a
  newline and the inline Send button submits the multiline answer.

* test: consolidate clarification e2e seed helper

Extract the shared seed → navigate → waitForLoad flow into
e2e/helpers/clarification.ts and delegate from the clarification, resize, and
mobile specs instead of duplicating it in each.
2026-07-01 21:36:58 +01:00

52 lines
1.6 KiB
TypeScript

import { type Page } from "@playwright/test";
import type { SeedData } from "../fixtures/test-base";
import type { ApiClient } from "./api-client";
import { SessionPage } from "../pages/session-page";
type SeedOptions = {
/** Mock-agent scenario slug, e.g. "clarification" or "simple-message". */
scenario: string;
/**
* Wait for the chat to go idle after load. Leave false for blocking
* clarification scenarios where the agent parks on the MCP call and the idle
* input never appears until the question is answered.
*/
waitForIdle?: boolean;
};
/**
* Create a task + session running a mock-agent scenario, navigate to it, and
* return a ready SessionPage. Shared by the clarification specs (overlay,
* resize, and mobile) so the seed → navigate → waitForLoad flow lives in one
* place.
*/
export async function seedClarificationSession(
testPage: Page,
apiClient: ApiClient,
seedData: SeedData,
title: string,
{ scenario, waitForIdle = false }: SeedOptions,
): Promise<SessionPage> {
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
title,
seedData.agentProfileId,
{
description: `/e2e:${scenario}`,
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();
if (waitForIdle) await session.waitForChatIdle();
return session;
}