+
+
+
+
-
+
{taskTitle ?? "Task details"}
diff --git a/apps/web/e2e/helpers/topbar-fixtures.ts b/apps/web/e2e/helpers/topbar-fixtures.ts
new file mode 100644
index 0000000000..add12de066
--- /dev/null
+++ b/apps/web/e2e/helpers/topbar-fixtures.ts
@@ -0,0 +1,2 @@
+export const LONG_TASK_TITLE =
+ 'Issue #1625: [Bug]: Config-session MCP advertises list_executors but backend returns "Unknown action: mcp.list_executors"';
diff --git a/apps/web/e2e/tests/layout/task-topbar-long-title.spec.ts b/apps/web/e2e/tests/layout/task-topbar-long-title.spec.ts
new file mode 100644
index 0000000000..7b92e6647f
--- /dev/null
+++ b/apps/web/e2e/tests/layout/task-topbar-long-title.spec.ts
@@ -0,0 +1,85 @@
+import { type Page } from "@playwright/test";
+import { test, expect } from "../../fixtures/test-base";
+import { LONG_TASK_TITLE } from "../../helpers/topbar-fixtures";
+import { SessionPage } from "../../pages/session-page";
+
+type DesktopTopbarMetrics = {
+ documentClientWidth: number;
+ documentScrollWidth: number;
+ stepperLeft: number;
+ titleClientWidth: number;
+ titleRight: number;
+ titleScrollWidth: number;
+ titleWidth: number;
+ toolsRight: number;
+ topbarRight: number;
+ topbarWidth: number;
+};
+
+async function readDesktopTopbarMetrics(page: Page): Promise {
+ return page.evaluate(() => {
+ const topbar = document.querySelector('[data-testid="task-topbar"]') as HTMLElement | null;
+ const title = topbar?.querySelector('[aria-current="page"]') as HTMLElement | null;
+ const stepper = topbar?.querySelector('[data-testid="workflow-stepper"]') as HTMLElement | null;
+ const tools = topbar?.querySelector('[aria-label="Task tools"]') as HTMLElement | null;
+ if (!topbar || !title || !stepper || !tools) return null;
+
+ const topbarRect = topbar.getBoundingClientRect();
+ const titleRect = title.getBoundingClientRect();
+ const stepperRect = stepper.getBoundingClientRect();
+ const toolsRect = tools.getBoundingClientRect();
+
+ return {
+ documentClientWidth: document.documentElement.clientWidth,
+ documentScrollWidth: document.documentElement.scrollWidth,
+ stepperLeft: stepperRect.left,
+ titleClientWidth: title.clientWidth,
+ titleRight: titleRect.right,
+ titleScrollWidth: title.scrollWidth,
+ titleWidth: titleRect.width,
+ toolsRight: toolsRect.right,
+ topbarRight: topbarRect.right,
+ topbarWidth: topbarRect.width,
+ };
+ });
+}
+
+test.describe("Task topbar long title layout", () => {
+ test("truncates long task titles without overlapping workflow or tool controls", async ({
+ testPage,
+ apiClient,
+ seedData,
+ }) => {
+ test.setTimeout(90_000);
+
+ const task = await apiClient.createTaskWithAgent(
+ seedData.workspaceId,
+ LONG_TASK_TITLE,
+ seedData.agentProfileId,
+ {
+ description: "/e2e:simple-message",
+ workflow_id: seedData.workflowId,
+ workflow_step_id: seedData.startStepId,
+ repository_ids: [seedData.repositoryId],
+ },
+ );
+
+ await testPage.goto(`/t/${task.id}`);
+ const session = new SessionPage(testPage);
+ await session.waitForLoad();
+
+ const title = testPage.locator('[data-testid="task-topbar"] [aria-current="page"]');
+ await expect(title).toHaveText(LONG_TASK_TITLE, { timeout: 10_000 });
+ await expect(testPage.getByTestId("layout-preset-trigger")).toBeVisible();
+
+ const metrics = await readDesktopTopbarMetrics(testPage);
+ expect(metrics).not.toBeNull();
+ if (!metrics) return;
+
+ expect(metrics.documentScrollWidth).toBeLessThanOrEqual(metrics.documentClientWidth + 1);
+ expect(metrics.titleScrollWidth).toBeGreaterThan(metrics.titleClientWidth + 8);
+ expect(metrics.titleWidth).toBeLessThanOrEqual(metrics.topbarWidth * 0.6);
+ expect(metrics.titleRight).toBeLessThanOrEqual(metrics.stepperLeft + 1);
+ expect(metrics.toolsRight).toBeLessThanOrEqual(metrics.topbarRight + 1);
+ });
+});
diff --git a/apps/web/e2e/tests/task/mobile-task-topbar-long-title.spec.ts b/apps/web/e2e/tests/task/mobile-task-topbar-long-title.spec.ts
new file mode 100644
index 0000000000..9318e17fe4
--- /dev/null
+++ b/apps/web/e2e/tests/task/mobile-task-topbar-long-title.spec.ts
@@ -0,0 +1,91 @@
+import { type Page } from "@playwright/test";
+import { test, expect } from "../../fixtures/test-base";
+import { LONG_TASK_TITLE } from "../../helpers/topbar-fixtures";
+import { SessionPage } from "../../pages/session-page";
+
+type MobileTopbarMetrics = {
+ actionsLeft: number;
+ actionsRight: number;
+ documentClientWidth: number;
+ documentScrollWidth: number;
+ headerLeft: number;
+ headerRight: number;
+ titleClientWidth: number;
+ titleRight: number;
+ titleScrollWidth: number;
+};
+
+async function readMobileTopbarMetrics(
+ page: Page,
+ taskTitle: string,
+): Promise {
+ return page.evaluate((titleText) => {
+ const header = Array.from(document.querySelectorAll("header")).find((node) =>
+ node.textContent?.includes(titleText),
+ ) as HTMLElement | undefined;
+ const title = Array.from(header?.querySelectorAll("span") ?? []).find(
+ (node) => node.textContent?.trim() === titleText,
+ ) as HTMLElement | undefined;
+ const actions = header?.querySelector('[data-testid="mobile-topbar-actions"]') as
+ | HTMLElement
+ | null
+ | undefined;
+ if (!header || !title || !actions) return null;
+
+ const headerRect = header.getBoundingClientRect();
+ const titleRect = title.getBoundingClientRect();
+ const actionsRect = actions.getBoundingClientRect();
+
+ return {
+ actionsLeft: actionsRect.left,
+ actionsRight: actionsRect.right,
+ documentClientWidth: document.documentElement.clientWidth,
+ documentScrollWidth: document.documentElement.scrollWidth,
+ headerLeft: headerRect.left,
+ headerRight: headerRect.right,
+ titleClientWidth: title.clientWidth,
+ titleRight: titleRect.right,
+ titleScrollWidth: title.scrollWidth,
+ };
+ }, taskTitle);
+}
+
+test.describe("Mobile task topbar long title layout", () => {
+ test("truncates long task titles without pushing mobile actions off-screen", async ({
+ testPage,
+ apiClient,
+ seedData,
+ }) => {
+ test.setTimeout(90_000);
+
+ const task = await apiClient.createTaskWithAgent(
+ seedData.workspaceId,
+ LONG_TASK_TITLE,
+ seedData.agentProfileId,
+ {
+ description: "/e2e:simple-message",
+ workflow_id: seedData.workflowId,
+ workflow_step_id: seedData.startStepId,
+ repository_ids: [seedData.repositoryId],
+ },
+ );
+
+ await testPage.goto(`/t/${task.id}`);
+ const session = new SessionPage(testPage);
+ await session.waitForLoad();
+
+ const header = testPage.locator("header").filter({ hasText: LONG_TASK_TITLE }).first();
+ await expect(header).toBeVisible({ timeout: 10_000 });
+ await expect(testPage.getByTestId("mobile-session-menu")).toBeVisible();
+
+ const metrics = await readMobileTopbarMetrics(testPage, LONG_TASK_TITLE);
+ expect(metrics).not.toBeNull();
+ if (!metrics) return;
+
+ expect(metrics.documentScrollWidth).toBeLessThanOrEqual(metrics.documentClientWidth + 1);
+ expect(metrics.titleScrollWidth).toBeGreaterThan(metrics.titleClientWidth + 8);
+ expect(metrics.titleRight).toBeLessThanOrEqual(metrics.actionsLeft + 1);
+ expect(metrics.headerLeft).toBeGreaterThanOrEqual(0);
+ expect(metrics.actionsRight).toBeLessThanOrEqual(metrics.headerRight + 1);
+ });
+});