fix(app): return to chat after fullscreen submission (#6987)

This commit is contained in:
Mohamed Boudra
2026-10-15 14:55:19 +07:00
committed by GitHub
parent 12066431ad
commit 74282dac12
3 changed files with 70 additions and 12 deletions
@@ -170,7 +170,8 @@ test("composer growth keeps a bottom-pinned chat at the bottom", async ({ page }
async function pausePageClock(page: Page): Promise<void> {
const now = Date.now();
await page.clock.install({ time: now });
await page.clock.pauseAt(now);
// The installed clock advances between protocol calls; pause at a future time.
await page.clock.pauseAt(now + 60_000);
}
/** The send error shares the footer with Send, so whatever keeps Send visible keeps it visible. */
@@ -206,7 +207,7 @@ test("compact long drafts stop growing before the header", async ({ page }) => {
}
});
test("compact fullscreen editing preserves the live draft and selection and stays open after send", async ({
test("compact fullscreen editing preserves the live draft and selection and returns to chat after send", async ({
page,
}) => {
const agent = await seedMockAgentWorkspace({
@@ -260,7 +261,7 @@ test("compact fullscreen editing preserves the live draft and selection and stay
await sendComposerWithButton(page);
await expect(composerLocator(page)).toHaveValue("");
});
await test.step("sending inside fullscreen clears the editor and keeps fullscreen open", async () => {
await test.step("sending inside fullscreen returns to chat and clears the editor", async () => {
await writeCompactDraft(page, "First line\nSecond line\nThird line");
await enterComposerFullscreen(page);
await writeCompactDraft(page, "Short draft");
@@ -274,10 +275,12 @@ test("compact fullscreen editing preserves the live draft and selection and stay
await resizeComposerViewport(page, "portrait");
await sendComposerWithButton(page);
await expect(composerLocator(page)).toHaveValue("");
await expectComposerFullscreenControls(page);
await expect(page.getByRole("button", { name: "Send message", exact: true })).toBeDisabled();
await expectWorkspaceHeaderVisible(page);
await expect(page.getByTestId("composer-fullscreen")).toHaveCount(0);
});
await test.step("typing and sending keep working after leaving fullscreen again", async () => {
await writeCompactDraft(page, "First line\nSecond line\nThird line");
await enterComposerFullscreen(page);
await writeCompactDraft(page, "Typed inside fullscreen");
await expect(page.getByRole("button", { name: "Send message", exact: true })).toBeEnabled();
await exitComposerFullscreen(page);
@@ -339,16 +342,16 @@ test("fullscreen submit rejection retains the editable draft and supports retry"
await writeCompactDraft(page, draft);
await enterComposerFullscreen(page);
await sendComposerWithButton(page);
await expect(page.getByTestId("composer-fullscreen").getByRole("alert")).toHaveText(
"Requested mock prompt rejection",
);
await expectWorkspaceHeaderVisible(page);
await expect(page.getByRole("alert")).toHaveText("Requested mock prompt rejection");
await enterComposerFullscreen(page);
await expectFullscreenErrorAboveSend(page);
await expect(composerLocator(page)).toHaveValue(draft);
await expect(composerLocator(page)).toBeEditable();
await expect(page.getByRole("button", { name: "Send message", exact: true })).toBeEnabled();
await sendComposerWithButton(page);
await expect(composerLocator(page)).toHaveValue("");
await expectComposerFullscreenControls(page);
await expectWorkspaceHeaderVisible(page);
} finally {
await agent.cleanup();
}
@@ -440,8 +443,7 @@ test("fullscreen Send keeps the configured queue behavior while an agent runs",
await expectComposerFullscreenControls(page);
await queueComposerWithButton(page);
await expect(composerLocator(page)).toHaveValue("");
await expectComposerFullscreenControls(page);
await exitComposerFullscreen(page);
await expectWorkspaceHeaderVisible(page);
await expectQueuedMessageButton(page);
} finally {
await agent.cleanup();
@@ -1,7 +1,14 @@
import {
composerLocator,
enterComposerFullscreen,
exitComposerFullscreen,
resizeComposerViewport,
} from "../support/helpers/composer";
import { expect, test } from "../support/fixtures";
import { gotoAppShell } from "../support/helpers/app";
import { getE2EDaemonPort } from "../support/helpers/daemon-port";
import {
delayBrowserWorkspaceCreatedResponse,
expectNewWorkspaceDraft,
expectNewWorkspaceProjectSelected,
fillNewWorkspaceDraft,
@@ -134,3 +141,45 @@ test.describe("New workspace composer draft", () => {
}
});
});
test("fullscreen creation returns inline and keeps submission locked when reopened", async ({
page,
}) => {
const project = await seedWorkspace({ repoPrefix: "fullscreen-submit-pending-" });
const delay = await delayBrowserWorkspaceCreatedResponse(page);
try {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await openNewWorkspaceComposer(page, project);
await resizeComposerViewport(page, "portrait");
await fillNewWorkspaceDraft(page, DRAFT);
await enterComposerFullscreen(page);
await page.getByRole("button", { name: "Create", exact: true }).click();
await delay.waitForCreateRequest();
await test.step("submission returns inline with the original draft and spinner", async () => {
await expect(page.getByTestId("composer-fullscreen")).toHaveCount(0);
await expect(composerLocator(page)).toHaveValue(DRAFT);
await expect(composerLocator(page)).not.toBeEditable();
await expect(page.getByRole("button", { name: "Create", exact: true })).toBeDisabled();
await expect(page.getByRole("progressbar")).toBeVisible();
});
await test.step("fullscreen can reopen while pending without unlocking or duplicating submission", async () => {
await enterComposerFullscreen(page);
await expect(page.getByTestId("composer-fullscreen")).toBeVisible();
await expect(composerLocator(page)).toHaveValue(DRAFT);
await expect(composerLocator(page)).not.toBeEditable();
await expect(page.getByRole("button", { name: "Create", exact: true })).toBeDisabled();
await expect(page.getByRole("progressbar")).toBeVisible();
await exitComposerFullscreen(page);
});
delay.release();
await page.waitForURL((url) => url.pathname.includes("/workspace/"));
await expect(composerLocator(page)).toHaveValue("");
await expect(composerLocator(page)).toBeEditable();
expect(delay.agentRequests).toHaveLength(1);
} finally {
delay.release();
await project.cleanup();
}
});
+8 -1
View File
@@ -942,6 +942,7 @@ interface SendMessageContext {
onSubmit: (payload: MessagePayload) => void;
onMinimizeHeight: () => void;
preserveHeightOnSubmit: boolean;
exitFullscreen: () => void;
}
function sendMessageImpl(ctx: SendMessageContext): void {
@@ -954,6 +955,7 @@ function sendMessageImpl(ctx: SendMessageContext): void {
) {
return;
}
ctx.exitFullscreen();
ctx.onSubmit({
text: trimmed,
attachments: ctx.attachments,
@@ -974,12 +976,14 @@ interface QueueMessageContext {
onQueue: ((payload: MessagePayload) => void) | undefined;
replaceText: (text: string) => void;
onMinimizeHeight: () => void;
exitFullscreen: () => void;
}
function queueMessageImpl(ctx: QueueMessageContext): void {
if (!ctx.onQueue) return;
const trimmed = ctx.value.trim();
if (!trimmed && ctx.attachments.length === 0) return;
ctx.exitFullscreen();
ctx.onQueue({ text: trimmed, attachments: ctx.attachments, cwd: ctx.cwd });
ctx.replaceText("");
ctx.onMinimizeHeight();
@@ -1640,6 +1644,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
onSubmit,
onMinimizeHeight: minimizeInputHeight,
preserveHeightOnSubmit,
exitFullscreen,
});
}, [
allowEmptySubmit,
@@ -1651,6 +1656,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
minimizeInputHeight,
preserveHeightOnSubmit,
updateLiveTextPresence,
exitFullscreen,
]);
const handleQueueMessage = useCallback(
@@ -1662,8 +1668,9 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
onQueue,
replaceText,
onMinimizeHeight: minimizeInputHeight,
exitFullscreen,
}),
[attachments, cwd, onQueue, replaceText, minimizeInputHeight],
[attachments, cwd, onQueue, replaceText, minimizeInputHeight, exitFullscreen],
);
const handleDefaultSendAction = useCallback(() => {