mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 02:15:02 +00:00
fix(app): stop treating a covered New workspace composer as on screen
The screen passed isPaneFocused={true}, so with Settings pushed over it
the input still counted as revealed. focus() on a held composer resolved
on native with no visible cursor, and rejected with the wrong error on web.
The screen now reports its real route focus.
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
CHECKLIST,
|
||||
withComposerToolsExample,
|
||||
withPluginFocusingAComposer,
|
||||
withPluginHoldingNewWorkspaceComposer,
|
||||
withPluginWritingToAnotherWorkspace,
|
||||
} from "../support/helpers/plugin-composers-by-reference";
|
||||
|
||||
@@ -105,3 +106,13 @@ test("focus closes the compact Explorer panel covering the composer", async ({ p
|
||||
await author.expectComposerFocused();
|
||||
});
|
||||
});
|
||||
|
||||
test("focus rejects on a New workspace composer the user has left", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
await withPluginHoldingNewWorkspaceComposer(page, async (author) => {
|
||||
await author.openNewWorkspace();
|
||||
await author.expectHeldComposerFocusResult("focused");
|
||||
await author.openSettings();
|
||||
await author.expectHeldComposerFocusResult("The composer is not on screen.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { expect, test, type Page } from "@playwright/test";
|
||||
import { gotoAppShell } from "./app";
|
||||
import { openCommandCenter } from "./command-center";
|
||||
import { openAgentRoute } from "./mock-agent";
|
||||
import { connectNewWorkspaceDaemonClient } from "./new-workspace";
|
||||
import { connectNewWorkspaceDaemonClient, openGlobalNewWorkspaceComposer } from "./new-workspace";
|
||||
import { copyPluginExample, pluginRequirements } from "./plugin-fixture";
|
||||
import { seedWorkspace } from "./seed-client";
|
||||
import { getServerId } from "./server-id";
|
||||
@@ -260,7 +260,7 @@ export default function contribute(client) {
|
||||
kind: "action",
|
||||
async onPress() {
|
||||
for (const composer of client.composers({ agentId: ${targetId} })) {
|
||||
await composer.insertText("HELLO");
|
||||
composer.insertText("HELLO");
|
||||
await composer.focus();
|
||||
}
|
||||
},
|
||||
@@ -391,3 +391,73 @@ export default function contribute(client) {
|
||||
}
|
||||
|
||||
export { CHECKLIST };
|
||||
|
||||
type HeldComposerGlobal = typeof globalThis & {
|
||||
heldComposer?: { focus(): Promise<void> };
|
||||
};
|
||||
|
||||
export async function withPluginHoldingNewWorkspaceComposer(
|
||||
page: Page,
|
||||
run: (author: {
|
||||
openNewWorkspace(): Promise<void>;
|
||||
openSettings(): Promise<void>;
|
||||
expectHeldComposerFocusResult(result: string): Promise<void>;
|
||||
}) => Promise<void>,
|
||||
) {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "held-composer-", title: "Held composer" });
|
||||
const client = await connectNewWorkspaceDaemonClient({ ownProjects: false });
|
||||
const previous = await client.getDaemonConfig();
|
||||
const directory = await mkdtemp(path.join(tmpdir(), "paseo-held-composer-"));
|
||||
await writeFile(
|
||||
path.join(directory, "paseo-plugin.json"),
|
||||
JSON.stringify({ id: "held-composer", requirements: pluginRequirements }),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(directory, "index.client.tsx"),
|
||||
`import { useComposer } from "@getpaseo/plugin/client";
|
||||
|
||||
function Holder() {
|
||||
globalThis.heldComposer = useComposer();
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function contribute(client) {
|
||||
client.addComposerItem({ id: "holder", Component: Holder });
|
||||
return () => {};
|
||||
}`,
|
||||
);
|
||||
try {
|
||||
await client.patchDaemonConfig({ pluginsEnabled: true });
|
||||
await client.installDirectoryPlugin(directory);
|
||||
await page.setViewportSize(WIDE);
|
||||
await gotoAppShell(page);
|
||||
await run({
|
||||
openNewWorkspace: () =>
|
||||
test.step("open the New workspace composer", async () => {
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await page.waitForFunction(() => (globalThis as HeldComposerGlobal).heldComposer);
|
||||
}),
|
||||
openSettings: () =>
|
||||
test.step("push Settings over the New workspace screen", async () => {
|
||||
await page.getByTestId("sidebar-settings").filter({ visible: true }).click();
|
||||
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
||||
}),
|
||||
expectHeldComposerFocusResult: (result) =>
|
||||
test.step(`the held composer's focus() reports ${result}`, async () => {
|
||||
const actual = await page.evaluate(() =>
|
||||
(globalThis as HeldComposerGlobal).heldComposer?.focus().then(
|
||||
() => "focused",
|
||||
(error: Error) => error.message,
|
||||
),
|
||||
);
|
||||
expect(actual).toBe(result);
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
await client.removePlugin("held-composer");
|
||||
await client.patchDaemonConfig({ pluginsEnabled: previous.config.pluginsEnabled });
|
||||
await client.close();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
await workspace.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { encodeImages } from "@/utils/encode-images";
|
||||
import { getHostRuntimeStore } from "@/runtime/host-runtime";
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useIsFocused } from "expo-router/react-navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
@@ -909,6 +910,7 @@ export function NewWorkspaceScreen({
|
||||
const hostPickerAnchorRef = useRef<View | null>(null);
|
||||
const isDraftHandoffActive = useIsNewWorkspaceDraftHandoffActive({ draftId, selectedServerId });
|
||||
const isStillOnCreateScreen = useNewWorkspaceScreenPresence();
|
||||
const isScreenFocused = useIsFocused();
|
||||
|
||||
// Launch target: what the composer submits to (chat agent, or a terminal
|
||||
// profile). Mirrors useWorkspaceIsolation's pattern below: the derived
|
||||
@@ -1544,7 +1546,7 @@ export function NewWorkspaceScreen({
|
||||
submitLabel={terminalSubmitLabel}
|
||||
agentId={draftKey}
|
||||
serverId={selectedServerId}
|
||||
isPaneFocused={true}
|
||||
isPaneFocused={isScreenFocused}
|
||||
onSubmitMessage={handleSubmitTerminalLaunch}
|
||||
allowEmptySubmit={true}
|
||||
submitButtonAccessibilityLabel={t("newWorkspace.launch.submit")}
|
||||
@@ -1568,7 +1570,7 @@ export function NewWorkspaceScreen({
|
||||
editingSession={composerEditing.chat}
|
||||
agentId={draftKey}
|
||||
serverId={selectedServerId}
|
||||
isPaneFocused={true}
|
||||
isPaneFocused={isScreenFocused}
|
||||
onSubmitMessage={handleSubmitNewWorkspace}
|
||||
allowEmptySubmit={true}
|
||||
submitButtonAccessibilityLabel={t("newWorkspace.create")}
|
||||
|
||||
Reference in New Issue
Block a user