mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Keep long custom models removable in provider settings (#6507)
* fix(app): keep long custom models removable Custom model labels and IDs could consume the row width and push Remove outside the sheet. Allow those text cells to shrink while preserving the button width and short-name alignment. * test(app): wait for legacy agent creation before checking prompts Agent-list membership includes initialization. Waiting for finish at that point can return on the initial idle transition before the initial prompt starts. Wait for the matching creation response before checking each completed turn, preserving the existing exact-once assertions. * test(app): wait for directory search keyboard focus The search page becomes visible before its deferred focus callback runs. Wait for the input to own keyboard focus so the journey types its query rather than dropping it during navigation. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -223,6 +223,7 @@ test.describe("Add Project command-center flow", () => {
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
await expectAddProjectPage(page, "directory-search");
|
||||
await expect(addProjectFlowInput(page)).toBeFocused();
|
||||
await page.keyboard.type(projectPickerFixture.fuzzyQuery);
|
||||
await expect(addProjectFlow(page)).toContainText(projectPickerFixture.projectName, {
|
||||
timeout: 30_000,
|
||||
|
||||
@@ -116,6 +116,7 @@ for (const version of ["0.2.5", "0.7.2", "0.8.0"]) {
|
||||
);
|
||||
expect(workspaces).toHaveLength(1);
|
||||
gate.release();
|
||||
await gate.waitForCreated();
|
||||
await expect.poll(async () => (await client.fetchAgents()).entries.length).toBe(1);
|
||||
const first = (await client.fetchAgents()).entries[0]!.agent;
|
||||
await client.waitForFinish(first.id, 20_000);
|
||||
@@ -128,6 +129,7 @@ for (const version of ["0.2.5", "0.7.2", "0.8.0"]) {
|
||||
const secondPrompt = "Create another agent: emit 1 coalesced agent stream updates";
|
||||
await fillComposerDraft(page, secondPrompt);
|
||||
await pressSubmitBeforeTheNextRender(page, "Send message");
|
||||
await gate.waitForCreated();
|
||||
await expect.poll(async () => (await client.fetchAgents()).entries.length).toBe(2);
|
||||
const second = (await client.fetchAgents()).entries.find(
|
||||
({ agent }) => agent.id !== first.id,
|
||||
@@ -206,21 +208,40 @@ for (const version of ["0.2.5", "0.7.2", "0.8.0"]) {
|
||||
async function holdLegacyAgentCreation(page: Page, port: number) {
|
||||
const readers = await loadSessionMessageReaders();
|
||||
const arrived = Promise.withResolvers<void>();
|
||||
let created = Promise.withResolvers<void>();
|
||||
let createRequestId: string | undefined;
|
||||
let release = () => {};
|
||||
let holding = true;
|
||||
await page.routeWebSocket(new RegExp(`:${port}/ws`), (browser) => {
|
||||
const server = browser.connectToServer();
|
||||
browser.onMessage((frame) => {
|
||||
if (holding && readers.client(frame)?.type === "create_agent_request") {
|
||||
const message = readers.client(frame);
|
||||
if (message?.type === "create_agent_request") createRequestId = message.requestId;
|
||||
if (holding && message?.type === "create_agent_request") {
|
||||
holding = false;
|
||||
release = () => server.send(frame);
|
||||
arrived.resolve();
|
||||
} else server.send(frame);
|
||||
});
|
||||
server.onMessage((frame) => browser.send(frame));
|
||||
server.onMessage((frame) => {
|
||||
const message = readers.server(frame);
|
||||
if (
|
||||
message?.type === "status" &&
|
||||
message.payload.status === "agent_created" &&
|
||||
message.payload.requestId === createRequestId
|
||||
) {
|
||||
created.resolve();
|
||||
}
|
||||
browser.send(frame);
|
||||
});
|
||||
});
|
||||
return {
|
||||
waitForRequest: () => arrived.promise,
|
||||
async waitForCreated() {
|
||||
// Agent-list membership includes initialization, before the initial prompt starts.
|
||||
await created.promise;
|
||||
created = Promise.withResolvers<void>();
|
||||
},
|
||||
release() {
|
||||
const send = release;
|
||||
release = () => {};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { test } from "../support/fixtures";
|
||||
import {
|
||||
addCustomModel,
|
||||
expectCustomModelRetained,
|
||||
openCustomModels,
|
||||
removeCustomModel,
|
||||
} from "../support/helpers/custom-models";
|
||||
|
||||
const longModelId = "custom-model-with-a-long-name-".repeat(6);
|
||||
const shortModelId = "custom-short";
|
||||
|
||||
for (const width of [1200, 390]) {
|
||||
test(`removes a long custom model at ${width}px without removing another model`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1200, height: 1000 });
|
||||
await openCustomModels(page);
|
||||
await addCustomModel(page, shortModelId);
|
||||
await addCustomModel(page, longModelId);
|
||||
|
||||
await test.step("remove the long model at the target width", async () => {
|
||||
await page.setViewportSize({ width, height: 1000 });
|
||||
await removeCustomModel(page, longModelId);
|
||||
await expectCustomModelRetained(page, shortModelId);
|
||||
});
|
||||
|
||||
await removeCustomModel(page, shortModelId);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import { gotoAppShell, openSettings } from "./app";
|
||||
import { openSettingsHostSection } from "./settings";
|
||||
import { getServerId } from "./server-id";
|
||||
|
||||
export async function openCustomModels(page: Page): Promise<void> {
|
||||
await gotoAppShell(page);
|
||||
await openSettings(page);
|
||||
await openSettingsHostSection(page, getServerId(), "providers");
|
||||
await page.getByRole("button", { name: "Claude provider details", exact: true }).click();
|
||||
await expect(page.getByRole("textbox", { name: "Search models" })).toBeVisible();
|
||||
}
|
||||
|
||||
export async function addCustomModel(page: Page, id: string): Promise<void> {
|
||||
await page.getByRole("button", { name: "Add model", exact: true }).click();
|
||||
await page.getByPlaceholder("e.g. openai/gpt-5").fill(id);
|
||||
await page
|
||||
.getByTestId("add-custom-model-sheet")
|
||||
.getByRole("button", { name: "Add", exact: true })
|
||||
.click();
|
||||
await expect(page.getByRole("button", { name: `Remove ${id}`, exact: true })).toHaveCount(1);
|
||||
}
|
||||
|
||||
export async function removeCustomModel(page: Page, id: string): Promise<void> {
|
||||
await page.getByRole("textbox", { name: "Search models" }).fill(id);
|
||||
const remove = page.getByRole("button", { name: `Remove ${id}`, exact: true });
|
||||
await expect(remove).toBeInViewport({ ratio: 1 });
|
||||
await remove.click();
|
||||
await expect(remove).toHaveCount(0);
|
||||
}
|
||||
|
||||
export async function expectCustomModelRetained(page: Page, id: string): Promise<void> {
|
||||
await page.getByRole("textbox", { name: "Search models" }).fill(id);
|
||||
await expect(page.getByRole("button", { name: `Remove ${id}`, exact: true })).toBeInViewport({
|
||||
ratio: 1,
|
||||
});
|
||||
}
|
||||
@@ -95,11 +95,11 @@ function CustomModelRow({
|
||||
|
||||
return (
|
||||
<View style={sheetStyles.modelRow}>
|
||||
<Text style={sheetStyles.modelTitle} numberOfLines={1}>
|
||||
<Text style={[sheetStyles.modelTitle, sheetStyles.customModelText]} numberOfLines={1}>
|
||||
{model.label}
|
||||
</Text>
|
||||
<Text
|
||||
style={sheetStyles.monoHint}
|
||||
style={[sheetStyles.monoHint, sheetStyles.customModelText]}
|
||||
numberOfLines={1}
|
||||
selectable
|
||||
dataSet={CODE_SURFACE_DATASET}
|
||||
@@ -762,6 +762,7 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
iconButton: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
flexShrink: 0,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@@ -807,6 +808,10 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.base,
|
||||
flexShrink: 0,
|
||||
},
|
||||
customModelText: {
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
modelRowFiller: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user