@@ -122,6 +137,10 @@ function CompactSourceMetrics({
);
}
+function metricsHeight(size: NonNullable
) {
+ return size === "sm" ? "h-7" : "h-8";
+}
+
function SourceBadge({ source, updatedAt }: { source: SystemMetricsSource; updatedAt?: string }) {
const isHost = source.kind === "backend";
const label = isHost ? "Host" : "Executor";
diff --git a/apps/web/components/task/task-top-bar.tsx b/apps/web/components/task/task-top-bar.tsx
index bb532dca1f..5be0fcce0e 100644
--- a/apps/web/components/task/task-top-bar.tsx
+++ b/apps/web/components/task/task-top-bar.tsx
@@ -365,7 +365,7 @@ function TopBarRight({
}) {
return (
-
+
{isArchived && (
diff --git a/apps/web/e2e/tests/kanban/mobile-kanban.spec.ts b/apps/web/e2e/tests/kanban/mobile-kanban.spec.ts
index 99c04896cc..10baf1e882 100644
--- a/apps/web/e2e/tests/kanban/mobile-kanban.spec.ts
+++ b/apps/web/e2e/tests/kanban/mobile-kanban.spec.ts
@@ -3,6 +3,29 @@ import { MobileKanbanPage } from "../../pages/mobile-kanban-page";
import { missingGitHealth } from "./health-fixtures";
test.describe("Mobile kanban view", () => {
+ test.afterEach(async ({ apiClient }) => {
+ await apiClient.rawRequest("PATCH", "/api/v1/user/settings", {
+ system_metrics_display: { show_in_topbar: false },
+ });
+ });
+
+ test("metrics match the height of the mobile topbar actions", async ({ testPage, apiClient }) => {
+ await apiClient.rawRequest("PATCH", "/api/v1/user/settings", {
+ system_metrics_display: { show_in_topbar: true },
+ });
+ const mobile = new MobileKanbanPage(testPage);
+ await mobile.goto();
+
+ const metrics = testPage.getByTestId("mobile-topbar-metrics");
+ await expect(metrics).toBeVisible();
+ await expect(mobile.mobileSearchToggle).toBeVisible();
+ const metricsBox = await metrics.boundingBox();
+ const actionBox = await mobile.mobileSearchToggle.boundingBox();
+ if (!metricsBox || !actionBox) throw new Error("topbar action has no bounding box");
+
+ expect(metricsBox.height).toBe(actionBox.height);
+ });
+
test("renders mobile layout with column tabs and swipeable columns", async ({
testPage,
apiClient,
diff --git a/apps/web/e2e/tests/layout/topbar-alignment.spec.ts b/apps/web/e2e/tests/layout/topbar-alignment.spec.ts
index 3d61950261..f0e1a71b9e 100644
--- a/apps/web/e2e/tests/layout/topbar-alignment.spec.ts
+++ b/apps/web/e2e/tests/layout/topbar-alignment.spec.ts
@@ -2,6 +2,7 @@ import { type Page } from "@playwright/test";
import { test, expect } from "../../fixtures/test-base";
import type { SeedData } from "../../fixtures/test-base";
import type { ApiClient } from "../../helpers/api-client";
+import { KanbanPage } from "../../pages/kanban-page";
import { SessionPage } from "../../pages/session-page";
/**
@@ -16,9 +17,27 @@ async function bottomOf(page: Page, testId: string): Promise {
return box.y + box.height;
}
+async function heightOf(page: Page, testId: string): Promise {
+ const box = await page.getByTestId(testId).boundingBox();
+ if (!box) throw new Error(`no bounding box for ${testId}`);
+ return box.height;
+}
+
+async function enableTopbarMetrics(apiClient: ApiClient): Promise {
+ await apiClient.rawRequest("PATCH", "/api/v1/user/settings", {
+ system_metrics_display: { show_in_topbar: true },
+ });
+}
+
test.describe("Sidebar header / top bar alignment", () => {
test.describe.configure({ retries: 1 });
+ test.afterEach(async ({ apiClient }) => {
+ await apiClient.rawRequest("PATCH", "/api/v1/user/settings", {
+ system_metrics_display: { show_in_topbar: false },
+ });
+ });
+
test("task top bar bottom edge aligns with the sidebar header", async ({
testPage,
apiClient,
@@ -65,4 +84,49 @@ test.describe("Sidebar header / top bar alignment", () => {
// Allow 1px for sub-pixel rounding of the shared border line.
expect(Math.abs((headerBottom as number) - topbarBottom)).toBeLessThanOrEqual(1);
});
+
+ test("task metrics match the height of the task action controls", async ({
+ testPage,
+ apiClient,
+ seedData,
+ }) => {
+ await enableTopbarMetrics(apiClient);
+ const task = await apiClient.createTaskWithAgent(
+ seedData.workspaceId,
+ "Metrics Alignment Task",
+ seedData.agentProfileId,
+ {
+ description: "/e2e:simple-message",
+ 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();
+
+ await expect(testPage.getByTestId("topbar-metrics")).toBeVisible();
+ await expect(testPage.getByTestId("layout-preset-trigger")).toBeVisible();
+ expect(await heightOf(testPage, "topbar-metrics")).toBe(
+ await heightOf(testPage, "layout-preset-trigger"),
+ );
+ });
+
+ test("Kanban metrics match the height of the Kanban action controls", async ({
+ testPage,
+ apiClient,
+ }) => {
+ await enableTopbarMetrics(apiClient);
+ const kanban = new KanbanPage(testPage);
+ await kanban.goto();
+
+ await expect(testPage.getByTestId("topbar-metrics")).toBeVisible();
+ await expect(testPage.getByTestId("view-toggle-kanban")).toBeVisible();
+ expect(await heightOf(testPage, "topbar-metrics")).toBe(
+ await heightOf(testPage, "view-toggle-kanban"),
+ );
+ });
});