import { expect, test, type Locator, type Page, type Route } from "@playwright/test"; import { createAuthenticationStack } from "./fixtures/auth-stack.mjs"; test.describe.configure({ mode: "serial" }); test.use({ viewport: { width: 1910, height: 911 } }); let stack: Awaited>; const databaseId = "11111111-1111-4111-8111-111111111111"; const database = { id: databaseId, workspaceId: "psd-clinical", workspaceName: "Policlinico San Donato", workspaceAvailable: true, workspaceRevision: { commit: "a".repeat(40), blob: "b".repeat(40) }, workspaceEvidence: { sourceType: "filesystem", state: "materialized_current_revision" }, runtimeBinding: { transport: "postgres_direct", configurationState: "ready", sessionTransportSupported: true, }, configured: true, engine: "postgres", databaseName: "warehouse", schema: "datawarehouse", version: 3, createdAt: "2026-08-27T08:00:00Z", updatedAt: "2026-08-27T09:00:00Z", binding: { transport: "postgres_direct", host: "db.internal", port: 5432, username: "reader", }, connectionStatus: "reachable", testedVersion: 3, secrets: { password: false, apiKey: false, sshPrivateKey: false, sshPrivateKeyPassphrase: false, sshKnownHosts: false, tlsCa: false, }, }; const unconfiguredDatabase = { ...database, id: undefined, workspaceId: "research-lab", workspaceName: "Research laboratory", configured: false, databaseName: "research", schema: "analytics", version: 0, createdAt: "", updatedAt: "", binding: { transport: "postgres_direct", port: 5432 }, connectionStatus: "untested", testedVersion: undefined, }; const table = { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", databaseId, name: "patients", sourceComment: "Patients imported from the clinical source", description: null, generatedDescription: null, version: 1, createdAt: "2026-08-27T08:00:00Z", updatedAt: "2026-08-27T09:00:00Z", }; const descriptionRun = { id: "77777777-7777-4777-8777-777777777777", databaseId, scope: "selected_columns", modelId: "glm-53", language: "it", status: "completed", total: 5, processed: 5, generated: 4, nonGeneratable: 1, failed: 0, inputTokens: 1200, cacheReadTokens: 80, outputTokens: 420, createdAt: "2026-08-31T09:00:00Z", startedAt: "2026-08-31T09:00:01Z", updatedAt: "2026-08-31T09:00:04Z", finishedAt: "2026-08-31T09:00:04Z", errorSummary: null, }; function metrics(requestedDatabaseId: string | null) { return { scope: requestedDatabaseId ? "database" : "global", databaseId: requestedDatabaseId, tables: 1, columns: 0, sensitiveColumns: 0, relationships: 0, descriptionTargets: 1, describedTargets: 0, descriptionCoverage: 0, updatedAt: "2026-08-31T09:00:00Z", }; } function json(route: Route, body: unknown, status = 200) { return route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body), }); } async function signInAsAdmin(page: Page) { const account = stack.localAccount("admin"); await page.getByLabel("Username").fill(account.username); await page.locator('input[name="password"]').fill(account.password); await page.getByRole("button", { name: "Sign in", exact: true }).click(); await expect(page.getByTestId("app-shell")).toBeVisible({ timeout: 30_000 }); } async function expectContextPanelGeometry(page: Page, accessibleName: string) { const manager = page .getByTestId("conversation-column") .getByRole("main", { name: "Database management" }); const panel = manager.getByRole("dialog", { name: accessibleName }); const sessionRail = page.getByRole("complementary", { name: "Session navigation" }); await expect(panel).toBeVisible(); expect( await panel.evaluate((element) => element.hasAttribute("data-catalog-panel")), ).toBe(true); await expect( panel.locator(':scope > [data-catalog-panel-region="header"]'), ).toHaveCount(1); await expect( panel.locator(':scope > [data-catalog-panel-region="body"]'), ).toHaveCount(1); const [managerBox, panelBox, sessionRailBox] = await Promise.all([ manager.boundingBox(), panel.boundingBox(), sessionRail.boundingBox(), ]); expect(managerBox).not.toBeNull(); expect(panelBox).not.toBeNull(); expect(sessionRailBox).not.toBeNull(); if (!managerBox || !panelBox || !sessionRailBox) return; const managerCenter = managerBox.x + managerBox.width / 2; const panelCenter = panelBox.x + panelBox.width / 2; expect(Math.abs(panelCenter - managerCenter)).toBeLessThanOrEqual(1); const expectedPanelWidth = Math.min(1200, managerBox.width - (managerBox.width <= 768 ? 24 : 32)); expect(Math.abs(panelBox.width - expectedPanelWidth)).toBeLessThanOrEqual(1); expect(panelBox.x).toBeGreaterThanOrEqual(managerBox.x - 1); expect(panelBox.x + panelBox.width).toBeLessThanOrEqual(managerBox.x + managerBox.width + 1); expect(panelBox.x + panelBox.width).toBeLessThanOrEqual(sessionRailBox.x + 1); expect(panelBox.y).toBeGreaterThanOrEqual(managerBox.y); expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(managerBox.y + managerBox.height + 1); } async function expectCurrentNavigation(page: Page, accessibleName: string) { const navigation = page.getByRole("complementary", { name: "Session navigation" }); const button = navigation.getByRole("button", { name: accessibleName, exact: true }); const available = navigation.locator('[data-navigation-state="available"]').first(); await expect(navigation.locator('[data-navigation-state="current"]')).toHaveCount(1); await expect(button).toHaveAttribute("data-navigation-state", "current"); await expect(button).toHaveAttribute("aria-current", "page"); expect(await button.getAttribute("class")).toContain("bg-[oklch(var(--nav-active))]"); const [style, availableStyle] = await Promise.all([ button.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, borderColor: getComputedStyle(element).borderTopColor, })), available.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, borderColor: getComputedStyle(element).borderTopColor, })), ]); expect(style.backgroundColor).not.toBe(availableStyle.backgroundColor); expect(style.borderColor).not.toBe(availableStyle.borderColor); const tokens = await page.evaluate(() => { const root = getComputedStyle(document.documentElement); const read = (name: string) => root.getPropertyValue(name).trim().split(/\s+/).map(Number); return { primary: read("--primary"), active: read("--nav-active") }; }); expect(tokens.active[2]).toBeCloseTo(tokens.primary[2], 1); expect(tokens.active[1]).toBeLessThan(tokens.primary[1]); expect(tokens.active[0]).toBeGreaterThan(tokens.primary[0]); } async function expectSelectedSessionScopeTab(page: Page, accessibleName: string) { const tablist = page.getByRole("tablist", { name: "Session scope" }); const selected = tablist.getByRole("tab", { name: accessibleName, exact: true }); const inactive = tablist.locator('[role="tab"][aria-selected="false"]'); await expect(tablist.locator('[role="tab"][aria-selected="true"]')).toHaveCount(1); await expect(selected).toHaveAttribute("data-tab-state", "active"); expect(await selected.getAttribute("class")).toContain("bg-[oklch(var(--nav-active))]"); const [selectedStyle, inactiveStyle] = await Promise.all([ selected.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, borderColor: getComputedStyle(element).borderBottomColor, })), inactive.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, borderColor: getComputedStyle(element).borderBottomColor, borderWidth: getComputedStyle(element).borderTopWidth, })), ]); expect(selectedStyle.backgroundColor).not.toBe(inactiveStyle.backgroundColor); expect(selectedStyle.borderColor).not.toBe(inactiveStyle.borderColor); expect(inactiveStyle.borderWidth).toBe("1px"); expect(inactiveStyle.backgroundColor).not.toMatch(/^(?:transparent|rgba\(0, 0, 0, 0\))$/); expect(inactiveStyle.borderColor).not.toMatch(/^(?:transparent|rgba\(0, 0, 0, 0\))$/); } function cssLightness(color: string): number { const oklab = color.match(/^okl(?:ab|ch)\(([\d.]+)(%)?/); if (oklab) { const value = Number(oklab[1]); return oklab[2] ? value / 100 : value; } const channels = color.match(/[\d.]+/g)?.slice(0, 3).map(Number) ?? []; if (channels.length !== 3) return Number.NaN; return color.startsWith("color(srgb") ? Math.max(...channels) : Math.max(...channels) / 255; } async function expectFleetRowTooltipBelow(trigger: Locator, expectedContent: string) { await trigger.hover(); await expect.poll(() => trigger.evaluate((element) => { const owner = element.closest("[data-tooltip]"); return owner ? getComputedStyle(owner, "::after").visibility : "missing"; })).toBe("visible"); await expect.poll(() => trigger.evaluate((element) => { const owner = element.closest("[data-tooltip]"); return owner ? getComputedStyle(owner, "::after").opacity : "missing"; })).toBe("1"); const state = await trigger.evaluate((element) => { const owner = element.closest("[data-tooltip]"); if (!owner) throw new Error("Row action tooltip owner is missing"); const style = getComputedStyle(owner, "::after"); return { content: style.content.replace(/^['\"]|['\"]$/g, ""), ownerHeight: owner.getBoundingClientRect().height, top: Number.parseFloat(style.top), right: Number.parseFloat(style.right), opacity: style.opacity, pointerEvents: style.pointerEvents, backgroundColor: style.backgroundColor, color: style.color, }; }); expect(state.content).toBe(expectedContent); expect(state.top - state.ownerHeight).toBeCloseTo(3, 1); expect(state.right).toBe(0); expect(state.opacity).toBe("1"); expect(state.pointerEvents).toBe("none"); expect(cssLightness(state.backgroundColor)).toBeLessThan(0.35); expect(cssLightness(state.color)).toBeGreaterThan(0.9); } async function expectContextPanelHeaderUsesPrimary( page: Page, accessibleName: string, ) { const manager = page .getByTestId("conversation-column") .getByRole("main", { name: "Database management" }); const panel = manager.getByRole("dialog", { name: accessibleName }); const header = panel.locator(':scope > [data-catalog-panel-region="header"]'); await expect(header).toHaveCount(1); const currentNavigation = page.locator('[data-navigation-state="current"]'); const [headerStyle, navigationStyle] = await Promise.all([ header.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, backgroundImage: getComputedStyle(element).backgroundImage, })), currentNavigation.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, })), ]); expect(headerStyle.backgroundImage).toBe("none"); expect(headerStyle.backgroundColor).not.toBe("rgba(0, 0, 0, 0)"); expect(headerStyle.backgroundColor).not.toBe(navigationStyle.backgroundColor); } async function expectWorkAreaPanelGeometry(page: Page, accessibleName: string) { const workArea = page.getByTestId("conversation-column"); const panel = workArea.getByRole("dialog", { name: accessibleName }); const sessionRail = page.getByRole("complementary", { name: "Session navigation" }); await expect(panel).toBeVisible(); await expect(panel).toHaveAttribute("aria-modal", "false"); await expect(panel).toHaveAttribute("data-work-area-panel", ""); await expect(panel.locator(':scope > [data-work-area-panel-region="header"]')).toHaveCount(1); await expect(panel.locator(':scope > [data-work-area-panel-region="body"]')).toHaveCount(1); const [workAreaBox, panelBox, sessionRailBox] = await Promise.all([ workArea.boundingBox(), panel.boundingBox(), sessionRail.boundingBox(), ]); expect(workAreaBox).not.toBeNull(); expect(panelBox).not.toBeNull(); expect(sessionRailBox).not.toBeNull(); if (!workAreaBox || !panelBox || !sessionRailBox) return; expect(Math.abs( panelBox.x + panelBox.width / 2 - (workAreaBox.x + workAreaBox.width / 2), )).toBeLessThanOrEqual(1); const expectedPanelWidth = Math.min(1200, workAreaBox.width - (workAreaBox.width <= 768 ? 24 : 32)); expect(Math.abs(panelBox.width - expectedPanelWidth)).toBeLessThanOrEqual(1); expect(panelBox.x).toBeGreaterThanOrEqual(workAreaBox.x - 1); expect(panelBox.x + panelBox.width).toBeLessThanOrEqual(workAreaBox.x + workAreaBox.width + 1); expect(panelBox.x + panelBox.width).toBeLessThanOrEqual(sessionRailBox.x + 1); expect(panelBox.y).toBeGreaterThanOrEqual(workAreaBox.y); expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(workAreaBox.y + workAreaBox.height + 1); } async function expectWorkAreaPanelHeaderUsesPrimary( page: Page, accessibleName: string, ) { const panel = page .getByTestId("conversation-column") .getByRole("dialog", { name: accessibleName }); const header = panel.locator(':scope > [data-work-area-panel-region="header"]'); const currentNavigation = page.locator('[data-navigation-state="current"]'); const [headerStyle, navigationStyle] = await Promise.all([ header.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, backgroundImage: getComputedStyle(element).backgroundImage, })), currentNavigation.evaluate((element) => ({ backgroundColor: getComputedStyle(element).backgroundColor, })), ]); expect(headerStyle.backgroundImage).toBe("none"); expect(headerStyle.backgroundColor).not.toBe(navigationStyle.backgroundColor); } test.beforeAll(async () => { stack = await createAuthenticationStack(); await stack.useLocalMode(); }); test.afterAll(async () => { await stack?.close(); }); test("context panels stay inside the manager and the Tables grid sits in a sidebar-colored frame", async ({ page }) => { const unexpectedCatalogRequests: string[] = []; const responses: Record = { "GET /api/catalog/databases": [database, unconfiguredDatabase], "GET /api/catalog/metadata-generation/models": { models: [ { id: "deepseek-v4-pro", label: "DeepSeek V4 Pro" }, { id: "deepseek-v4-flash", label: "DeepSeek V4 Flash" }, { id: "glm-53", label: "GLM 5.3" }, { id: "qwen-36", label: "AritmoLab Qwen 3.6 35B A3B" }, ], default: "glm-53", }, "GET /api/catalog/description-generation-runs?limit=50": [descriptionRun], [`GET /api/catalog/description-generation-runs/${descriptionRun.id}`]: descriptionRun, [`GET /api/catalog/description-generation-runs/${descriptionRun.id}/events-list?after=0`]: [{ sequence: 1, level: "info", message: "Description generation completed.", createdAt: "2026-08-31T09:00:04Z", }], "GET /api/catalog/sensitive-data-suggestion-runs?limit=50": [], [`GET /api/catalog/databases/${databaseId}/tables`]: [table], }; await page.route("**/api/catalog/**", async (route) => { const request = route.request(); const url = new URL(request.url()); const key = `${request.method()} ${url.pathname}${url.search}`; if (request.method() === "GET" && url.pathname === "/api/catalog/metrics") { const requestedId = url.searchParams.get("databaseId"); if (requestedId === null || requestedId === databaseId) { await json(route, metrics(requestedId)); return; } } if (Object.hasOwn(responses, key)) { await json(route, responses[key]); return; } unexpectedCatalogRequests.push(key); await json(route, { code: "unexpected_catalog_request" }, 500); }); await page.goto(stack.publicUrl); await signInAsAdmin(page); await expectCurrentNavigation(page, "New session"); await expectSelectedSessionScopeTab(page, "My sessions"); await page.getByRole("tab", { name: "All sessions", exact: true }).click(); await expectSelectedSessionScopeTab(page, "All sessions"); await page.getByRole("tab", { name: "My sessions", exact: true }).click(); await expectSelectedSessionScopeTab(page, "My sessions"); const adminNavigationRail = page.getByRole("complementary", { name: "Session navigation" }); const administration = adminNavigationRail.getByRole("button", { name: "Administration", exact: true }); await expect(administration).toHaveCSS("font-family", /Manrope/); await expect(administration).toHaveAttribute("aria-expanded", "false"); await expect(adminNavigationRail.getByRole("region", { name: "Administration" })).toHaveCount(0); await administration.click(); await expect(administration).toHaveAttribute("aria-expanded", "true"); const administrationPanel = adminNavigationRail.getByRole("region", { name: "Administration" }); await expect(administrationPanel).toBeVisible(); expect(await administrationPanel.locator(":scope > *").evaluateAll((elements) => elements.map((element) => ( element.getAttribute("role") === "separator" ? "separator" : element.textContent?.trim() )))).toEqual([ "Database management", "separator", "Workspace management", "Pi management", ]); await administration.click(); await expect(administration).toHaveAttribute("aria-expanded", "false"); await expect(adminNavigationRail.getByRole("button", { name: "Database management", exact: true })).toHaveCount(0); await administration.click(); await expect(administration).toHaveAttribute("aria-expanded", "true"); await adminNavigationRail .getByRole("button", { name: "Database management", exact: true }) .click(); await expectCurrentNavigation(page, "Database management"); await expect( page.getByRole("complementary", { name: "Session navigation" }), ).toBeVisible(); await expect( page.getByTestId("conversation-column").getByRole("main", { name: "Database management", }), ).toBeVisible(); await expect( page.getByRole("button", { name: "Back to workspace", exact: true }), ).toHaveCount(0); await expect(page.getByRole("button", { name: /Add database/i })).toHaveCount(0); await expect(page.getByRole("columnheader", { name: /Revision \/ Evidence/ })).toBeVisible(); await expect(page.getByRole("columnheader", { name: /NL→SQL runtime/ })).toBeVisible(); await expect(page.getByRole("columnheader", { name: /Metadata Catalog/ })).toBeVisible(); const metadataModelSelector = page.getByRole("combobox", { name: "Metadata-generation LLM model", }); await expect(metadataModelSelector).toHaveCount(1); await expect(metadataModelSelector).toHaveValue("glm-53"); await expect(metadataModelSelector.locator("option")).toHaveText([ "DeepSeek V4 Pro", "DeepSeek V4 Flash", "GLM 5.3", "AritmoLab Qwen 3.6 35B A3B", ]); await page .getByRole("button", { name: "Configure catalog for Research laboratory", exact: true }) .click(); const configurePanel = page.getByRole("dialog", { name: "Configure catalog" }); await expectContextPanelGeometry(page, "Configure catalog"); const configuredWorkspace = configurePanel.getByLabel("Workspace", { exact: true }); await expect(configuredWorkspace).toHaveValue("Research laboratory"); await expect(configuredWorkspace).toHaveAttribute("readonly", ""); await configurePanel.getByRole("button", { name: "Close database panel" }).click(); await page .getByRole("button", { name: "Edit Policlinico San Donato", exact: true }) .click(); await expectContextPanelGeometry(page, "Edit database"); await expectContextPanelHeaderUsesPrimary(page, "Edit database"); await page .getByRole("dialog", { name: "Edit database" }) .getByRole("button", { name: "Close database panel" }) .click(); await page.setViewportSize({ width: 1280, height: 800 }); await page .getByRole("button", { name: "Edit Policlinico San Donato", exact: true }) .click(); await expectContextPanelGeometry(page, "Edit database"); await page .getByRole("dialog", { name: "Edit database" }) .getByRole("button", { name: "Close database panel" }) .click(); await page.setViewportSize({ width: 1910, height: 911 }); const databaseTablesAction = page.getByRole("button", { name: "View tables for Policlinico San Donato", exact: true, }); await expectFleetRowTooltipBelow(databaseTablesAction, "Tables"); await databaseTablesAction.click(); await expect( page.getByRole("region", { name: "Tables for Policlinico San Donato", }), ).toBeVisible(); await page .getByRole("button", { name: "Description history", exact: true }) .click(); await expectContextPanelGeometry(page, "Description generation"); await expectContextPanelHeaderUsesPrimary(page, "Description generation"); const descriptionPanel = page.getByRole("dialog", { name: "Description generation" }); const descriptionBody = descriptionPanel.locator( ':scope > [data-catalog-panel-region="body"]', ); const progressTable = descriptionPanel.getByRole("table", { name: "Description generation progress", }); const eventLog = descriptionPanel.getByRole("log", { name: "Description generation events", }); const recentRuns = descriptionPanel.getByRole("region", { name: "Description generation history", }); await expect(progressTable.getByRole("columnheader")).toHaveCount(5); await expect(eventLog).toBeVisible(); await expect(recentRuns).toBeVisible(); const [bodyBox, eventLogBox, recentRunsBox] = await Promise.all([ descriptionBody.boundingBox(), eventLog.boundingBox(), recentRuns.boundingBox(), ]); expect(bodyBox).not.toBeNull(); expect(eventLogBox).not.toBeNull(); expect(recentRunsBox).not.toBeNull(); if (bodyBox && eventLogBox && recentRunsBox) { const eventBottomGutter = bodyBox.y + bodyBox.height - eventLogBox.y - eventLogBox.height; const recentBottomGutter = bodyBox.y + bodyBox.height - recentRunsBox.y - recentRunsBox.height; expect(eventBottomGutter).toBeGreaterThanOrEqual(10); expect(recentBottomGutter).toBeGreaterThanOrEqual(10); expect(eventLogBox.x + eventLogBox.width).toBeLessThan(recentRunsBox.x); } await page.setViewportSize({ width: 720, height: 800 }); const [compactManagerBox, compactDescriptionPanelBox] = await Promise.all([ page.getByTestId("conversation-column").getByRole("main", { name: "Database management", }).boundingBox(), descriptionPanel.boundingBox(), ]); expect(compactManagerBox).not.toBeNull(); expect(compactDescriptionPanelBox).not.toBeNull(); if (compactManagerBox && compactDescriptionPanelBox) { expect(compactManagerBox.width - compactDescriptionPanelBox.width).toBeGreaterThanOrEqual(20); } expect(await descriptionBody.evaluate((element) => getComputedStyle(element).display)).not.toBe("grid"); await page.setViewportSize({ width: 1910, height: 911 }); await descriptionPanel.getByRole("button", { name: /close/i }).click(); const applicationBar = page.locator( 'main[aria-label="Database management"] .thot-fleet-ledger__header', ); await expect(applicationBar).toHaveCount(1); await expect( applicationBar.getByRole("heading", { name: "Database management" }), ).toBeVisible(); await expect( applicationBar.getByRole("region", { name: "Catalog status" }), ).toBeVisible(); await expect( applicationBar.getByRole("group", { name: "Database management actions" }), ).toBeVisible(); await expect( applicationBar.getByRole("combobox", { name: "Metadata-generation LLM model" }), ).toHaveValue("glm-53"); await expect(metadataModelSelector).toHaveCount(1); await expect(applicationBar).not.toContainText("Thoth catalog · Fleet ledger"); await expect(applicationBar).not.toContainText("Inspect physical metadata"); const applicationBarLayout = await applicationBar.evaluate((element) => { const title = element.querySelector(".thot-fleet-ledger__heading-copy"); const status = element.querySelector(".thot-fleet-ledger__header-status"); const actions = element.querySelector(".thot-fleet-ledger__header-actions"); if (!title || !status || !actions) { throw new Error("Application bar regions are missing"); } const centerY = (node: Element) => { const rect = node.getBoundingClientRect(); return rect.top + rect.height / 2; }; return { backgroundImage: getComputedStyle(element).backgroundImage, height: element.getBoundingClientRect().height, centers: [centerY(title), centerY(status), centerY(actions)], }; }); expect(applicationBarLayout.backgroundImage).toBe("none"); expect(applicationBarLayout.height).toBeLessThanOrEqual(64); expect( Math.max(...applicationBarLayout.centers) - Math.min(...applicationBarLayout.centers), ).toBeLessThanOrEqual(1); await expect(page.getByRole("row", { name: /patients/ })).toBeVisible(); const editPatientsAction = page.getByRole("button", { name: "Edit description for patients", exact: true, }); await expectFleetRowTooltipBelow(editPatientsAction, "Edit metadata"); await editPatientsAction.click(); await expectContextPanelGeometry(page, "Review table description"); await expectContextPanelHeaderUsesPrimary(page, "Review table description"); const tableEditor = page.getByRole("dialog", { name: "Review table description" }); const tableEditorFooter = tableEditor.locator( ':scope > [data-catalog-panel-region="footer"]', ); await expect(tableEditorFooter).toHaveCount(1); await expect( tableEditorFooter.getByRole("button", { name: "Cancel", exact: true }), ).toBeVisible(); await expect( tableEditorFooter.getByRole("button", { name: "Save metadata", exact: true }), ).toBeVisible(); await tableEditor .getByRole("button", { name: "Close table metadata", exact: true }) .click(); const gridRoot = page.locator( 'main[aria-label="Database management"] .thot-fleet-ledger-grid .ag-root-wrapper', ); await expect(gridRoot).toHaveCount(1); await expect(gridRoot).toBeVisible(); await expect.poll( () => gridRoot.evaluate((element) => { const manager = element.closest('main[aria-label="Database management"]'); if (!manager) throw new Error("Database manager is missing"); const managerBottom = manager.getBoundingClientRect().bottom; const gridBottom = element.getBoundingClientRect().bottom; return Math.max( Math.abs(window.innerHeight - managerBottom), Math.abs(managerBottom - gridBottom - 30), ); }), { message: "AG Grid must end 30px above the manager bottom" }, ).toBeLessThanOrEqual(1); const gapColors = await gridRoot.evaluate((element) => { const workspace = element.closest(".thot-fleet-ledger__workspace"); const tableSurface = element.closest(".thot-fleet-ledger__content"); const sidebar = document.querySelector('aside[aria-label="Session navigation"]'); if (!workspace || !tableSurface || !sidebar) { throw new Error("Application layout is missing"); } return { gap: getComputedStyle(workspace).backgroundColor, table: getComputedStyle(tableSurface).backgroundColor, sidebar: getComputedStyle(sidebar).backgroundColor, }; }); expect(gapColors.gap).not.toBe("rgba(0, 0, 0, 0)"); expect(gapColors.gap).toBe(gapColors.sidebar); expect(gapColors.gap).not.toBe(gapColors.table); const workspace = page.locator( 'main[aria-label="Database management"] .thot-fleet-ledger__workspace', ); const tableSurface = page.locator( 'main[aria-label="Database management"] .thot-fleet-ledger__content', ); const sessionNavigation = page.getByRole("complementary", { name: "Session navigation", }); await expect.poll( async () => { const [workspaceBox, tableBox, gridBox, sidebarBox] = await Promise.all([ workspace.boundingBox(), tableSurface.boundingBox(), gridRoot.boundingBox(), sessionNavigation.boundingBox(), ]); if (!workspaceBox || !tableBox || !gridBox || !sidebarBox) { throw new Error("Database frame geometry is missing"); } return Math.max( Math.abs(tableBox.x - workspaceBox.x - 20), Math.abs(tableBox.x + tableBox.width - sidebarBox.x), Math.abs(tableBox.y + tableBox.height - gridBox.y - gridBox.height), ); }, { message: "The table must have a 20px left frame and its right border must stop at the footer", }, ).toBeLessThanOrEqual(1); const frameBorders = await tableSurface.evaluate((element) => { const sidebar = document.querySelector('aside[aria-label="Session navigation"]'); if (!sidebar) throw new Error("Session navigation is missing"); return { tableEnd: Number.parseFloat(getComputedStyle(element).borderInlineEndWidth), sidebarStart: Number.parseFloat( getComputedStyle(sidebar).borderInlineStartWidth, ), }; }); expect(frameBorders.tableEnd).toBe(1); expect(frameBorders.sidebarStart).toBe(0); await expect(page.locator(".thot-session-navigation__boundary")).toHaveCount(0); expect(unexpectedCatalogRequests).toEqual([]); }); test("Workspace and Pi management share the centered work-area panel without covering the session rail", async ({ page }) => { await page.route("**/api/workspace-registry/status", (route) => json(route, { branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false, repository: { host: "git.example.test", repository: "analytics/thoth-workspaces", transport: "ssh", }, })); await page.route("**/api/workspaces", (route) => json(route, [])); await page.route("**/api/pi-management/status", (route) => json(route, { version: "0.80.3", ready: true, credentials: "present", config: { provider: "zai", model: "glm-5.2", reasoning: "medium" }, checkedAt: "2026-08-31T09:00:00Z", })); await page.route("**/api/pi-management/options", (route) => json(route, { providers: ["zai"], models: [{ provider: "zai", id: "glm-5.2" }], reasoning: ["low", "medium", "high"], checkedAt: "2026-08-31T09:00:00Z", })); await page.goto(stack.publicUrl); await signInAsAdmin(page); const administration = page.getByRole("button", { name: "Administration", exact: true }); await expect(administration).toHaveAttribute("aria-expanded", "false"); await administration.click(); await expect(administration).toHaveAttribute("aria-expanded", "true"); for (const viewport of [ { width: 1910, height: 911 }, { width: 1280, height: 800 }, ]) { await page.setViewportSize(viewport); const workspaceTrigger = page.getByRole("button", { name: "Workspace management", exact: true, }); await workspaceTrigger.click(); await expectCurrentNavigation(page, "Workspace management"); await expectWorkAreaPanelGeometry(page, "Workspace management"); await expectWorkAreaPanelHeaderUsesPrimary(page, "Workspace management"); const piTrigger = page.getByRole("button", { name: "Pi management", exact: true }); await piTrigger.click(); await expectCurrentNavigation(page, "Pi management"); await expect( page.getByRole("dialog", { name: "Workspace management" }), ).toHaveCount(0); await expectWorkAreaPanelGeometry(page, "Pi management"); await expectWorkAreaPanelHeaderUsesPrimary(page, "Pi management"); await page .getByRole("dialog", { name: "Pi management" }) .getByRole("button", { name: "Close Pi management" }) .click(); await expect(piTrigger).toBeFocused(); await expectCurrentNavigation(page, "New session"); } await page.setViewportSize({ width: 720, height: 800 }); await page.getByRole("button", { name: "Workspace management", exact: true }).click(); await expectCurrentNavigation(page, "Workspace management"); const compactWorkArea = page.getByTestId("conversation-column"); const compactPanel = compactWorkArea.getByRole("dialog", { name: "Workspace management" }); const [compactWorkAreaBox, compactPanelBox] = await Promise.all([ compactWorkArea.boundingBox(), compactPanel.boundingBox(), ]); expect(compactWorkAreaBox).not.toBeNull(); expect(compactPanelBox).not.toBeNull(); if (compactWorkAreaBox && compactPanelBox) { expect(Math.abs(compactPanelBox.width - compactWorkAreaBox.width + 24)).toBeLessThanOrEqual(1); expect(Math.abs( compactPanelBox.x + compactPanelBox.width / 2 - (compactWorkAreaBox.x + compactWorkAreaBox.width / 2), )).toBeLessThanOrEqual(1); } const compactNavigation = compactPanel.getByRole("navigation", { name: "Workspaces" }); const compactContent = compactPanel.getByRole("region", { name: "Workspace configuration", }); const [compactNavigationBox, compactContentBox] = await Promise.all([ compactNavigation.boundingBox(), compactContent.boundingBox(), ]); expect(compactNavigationBox).not.toBeNull(); expect(compactContentBox).not.toBeNull(); if (compactNavigationBox && compactContentBox) { expect(compactContentBox.y).toBeGreaterThanOrEqual( compactNavigationBox.y + compactNavigationBox.height - 1, ); } });