From 76e19dc42a4fa7bd6decac67849e82c1e061ff80 Mon Sep 17 00:00:00 2001 From: ewan Date: Tue, 25 Aug 2026 09:24:36 +0000 Subject: [PATCH 1/7] fix(harness): let the profile menu outgrow a narrow rail instead of wrapping its rows matchWidth pins the panel to the rail's width, so at RAIL_MIN labels like "Open Sapiom dashboard" wrapped inside a fixed-height row and read as overlapping items. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/profile-menu-narrow-rail.md | 5 +++ packages/harness/web/e2e/popover-crop.spec.ts | 38 +++++++++++++++++++ packages/harness/web/src/styles.css | 12 +++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changeset/profile-menu-narrow-rail.md diff --git a/.changeset/profile-menu-narrow-rail.md b/.changeset/profile-menu-narrow-rail.md new file mode 100644 index 000000000..f4fca7c7c --- /dev/null +++ b/.changeset/profile-menu-narrow-rail.md @@ -0,0 +1,5 @@ +--- +"@sapiom/harness": patch +--- + +Keep the rail's profile menu readable when the rail is dragged narrow: the panel now grows to its widest row instead of inheriting the rail's width and wrapping labels on top of each other. diff --git a/packages/harness/web/e2e/popover-crop.spec.ts b/packages/harness/web/e2e/popover-crop.spec.ts index 7a9f5f850..6afc06a38 100644 --- a/packages/harness/web/e2e/popover-crop.spec.ts +++ b/packages/harness/web/e2e/popover-crop.spec.ts @@ -64,6 +64,44 @@ test("profile menu opens uncropped off the rail footer", async ({ page }) => { await expectUncropped(page, page.getByTestId("profile-menu")); }); +/* matchWidth pins the profile menu to the rail's width, so the narrowest rail + is the case where its labels wrap and rows overlap each other. */ +test("profile menu rows stay single-line at the rail's minimum width", async ({ page }) => { + await page.evaluate(() => { + localStorage.setItem("sapiom-harness-pane-widths", JSON.stringify({ rail: 180 })); + }); + await page.reload(); + await expect(page.locator(".rail-workflows")).toBeVisible(); + + const trigger = page.getByTestId("brand-identity"); + await trigger.click(); + const menu = page.getByTestId("profile-menu"); + await expect(menu).toBeVisible(); + + const rows = await menu.locator(".profile-menu-item").evaluateAll((els) => + els.map((el) => { + const rect = el.getBoundingClientRect(); + return { top: rect.top, bottom: rect.bottom, height: rect.height, overflow: el.scrollHeight - el.clientHeight }; + }), + ); + expect(rows.length).toBeGreaterThan(2); + for (const row of rows) { + // --tree-row-h is 30px; a wrapped label would double this. + expect(row.height, "row is one line tall").toBeLessThanOrEqual(31); + // A wrapped label spilling past a fixed-height row is what reads as + // "overlapping menu items" on screen while the boxes still look fine. + expect(row.overflow, "label fits inside its row").toBeLessThanOrEqual(1); + } + for (let i = 1; i < rows.length; i += 1) { + expect(rows[i].top, "row starts below the one before it").toBeGreaterThanOrEqual(rows[i - 1].bottom - 0.5); + } + // The panel outgrows the narrow rail rather than squeezing its labels. + const triggerBox = await trigger.boundingBox(); + const menuBox = await menu.boundingBox(); + expect(menuBox!.width).toBeGreaterThan(triggerBox!.width); + await expectUncropped(page, menu); +}); + test("settings popover opens uncropped off the rail footer", async ({ page }) => { await page.getByTestId("brand-identity").click(); await page.getByTestId("settings-trigger").click(); diff --git a/packages/harness/web/src/styles.css b/packages/harness/web/src/styles.css index 679ecffd9..a7462548e 100644 --- a/packages/harness/web/src/styles.css +++ b/packages/harness/web/src/styles.css @@ -1474,6 +1474,14 @@ button.rail-footer-card:hover { min-width: 12rem; } +/* AnchoredPopover's matchWidth pins this panel to the rail's own width, which + the user can drag narrow — narrower than "Open Sapiom dashboard". min-width + beats that inline width, so rows stay on one line instead of wrapping into + each other; the popover's measured pass shifts the wider panel into view. */ +.profile-menu { + min-width: max-content; +} + @media (prefers-reduced-motion: reduce) { .canvas-detail-menu, .canvas-run-menu, @@ -1493,7 +1501,9 @@ button.rail-footer-card:hover { align-items: center; gap: var(--sp2); width: 100%; - height: var(--tree-row-h); + /* A floor, not a fixed height: a row that does wrap (a long session name in + a narrow menu) grows instead of spilling over its neighbours. */ + min-height: var(--tree-row-h); padding: 0 var(--sp2); border: 0; background: transparent; From 633b5a0e8cdd59857d1584ec36380ce3c825dcc4 Mon Sep 17 00:00:00 2001 From: ewan Date: Tue, 25 Aug 2026 13:28:40 +0000 Subject: [PATCH 2/7] fix(harness): give the profile menu the studio rail's own readable width Match the studio rail reference: the footer menu never matches its trigger. It takes --menu-w with a 240px floor and its rows stay one line, so dragging the rail to its minimum no longer wraps labels over each other. --- .changeset/profile-menu-narrow-rail.md | 2 +- packages/harness/web/e2e/popover-crop.spec.ts | 7 +++--- .../web/src/components/WorkflowsRail.tsx | 1 - packages/harness/web/src/styles.css | 24 +++++++++++++------ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.changeset/profile-menu-narrow-rail.md b/.changeset/profile-menu-narrow-rail.md index f4fca7c7c..aa42f0272 100644 --- a/.changeset/profile-menu-narrow-rail.md +++ b/.changeset/profile-menu-narrow-rail.md @@ -2,4 +2,4 @@ "@sapiom/harness": patch --- -Keep the rail's profile menu readable when the rail is dragged narrow: the panel now grows to its widest row instead of inheriting the rail's width and wrapping labels on top of each other. +Keep the rail's profile menu readable when the rail is dragged narrow: the panel now carries its own readable width (the studio rail's `--menu-w`) instead of inheriting the rail's width and wrapping labels on top of each other. diff --git a/packages/harness/web/e2e/popover-crop.spec.ts b/packages/harness/web/e2e/popover-crop.spec.ts index 6afc06a38..80ecf714b 100644 --- a/packages/harness/web/e2e/popover-crop.spec.ts +++ b/packages/harness/web/e2e/popover-crop.spec.ts @@ -64,8 +64,8 @@ test("profile menu opens uncropped off the rail footer", async ({ page }) => { await expectUncropped(page, page.getByTestId("profile-menu")); }); -/* matchWidth pins the profile menu to the rail's width, so the narrowest rail - is the case where its labels wrap and rows overlap each other. */ +/* The profile menu carries its own readable width; the narrowest rail is where + inheriting the trigger's width used to wrap labels over each other. */ test("profile menu rows stay single-line at the rail's minimum width", async ({ page }) => { await page.evaluate(() => { localStorage.setItem("sapiom-harness-pane-widths", JSON.stringify({ rail: 180 })); @@ -95,10 +95,11 @@ test("profile menu rows stay single-line at the rail's minimum width", async ({ for (let i = 1; i < rows.length; i += 1) { expect(rows[i].top, "row starts below the one before it").toBeGreaterThanOrEqual(rows[i - 1].bottom - 0.5); } - // The panel outgrows the narrow rail rather than squeezing its labels. + // The panel keeps its own width rather than squeezing into the narrow rail. const triggerBox = await trigger.boundingBox(); const menuBox = await menu.boundingBox(); expect(menuBox!.width).toBeGreaterThan(triggerBox!.width); + expect(menuBox!.width, "menu holds its readable floor").toBeGreaterThanOrEqual(240); await expectUncropped(page, menu); }); diff --git a/packages/harness/web/src/components/WorkflowsRail.tsx b/packages/harness/web/src/components/WorkflowsRail.tsx index 9f8180858..b070cad6f 100644 --- a/packages/harness/web/src/components/WorkflowsRail.tsx +++ b/packages/harness/web/src/components/WorkflowsRail.tsx @@ -1179,7 +1179,6 @@ function ProfileRow({ anchorRef={triggerRef} onDismiss={closeMenu} placement="up-start" - matchWidth className="profile-menu" role="menu" testid="profile-menu" diff --git a/packages/harness/web/src/styles.css b/packages/harness/web/src/styles.css index a7462548e..5e4d2f903 100644 --- a/packages/harness/web/src/styles.css +++ b/packages/harness/web/src/styles.css @@ -39,6 +39,10 @@ its glyphs land on the pane inset while the hover shape breathes around them. Mirrors the studio rail reference. */ --row-bleed: var(--sp2); + /* ONE readable width for floating menus, capped to the viewport. Menus are + read as their own surface, so they take this width rather than the width + of whatever row opened them. Mirrors the studio rail reference. */ + --menu-w: min(var(--col-w), calc(100vw - var(--sp4))); --font-sans: var(--font); --font-mono: var(--mono); @@ -1474,12 +1478,14 @@ button.rail-footer-card:hover { min-width: 12rem; } -/* AnchoredPopover's matchWidth pins this panel to the rail's own width, which - the user can drag narrow — narrower than "Open Sapiom dashboard". min-width - beats that inline width, so rows stay on one line instead of wrapping into - each other; the popover's measured pass shifts the wider panel into view. */ +/* Footer / profile menus: fixed readable width — never match the rail trigger, + whose width the user can drag down to the rail minimum, narrower than "Open + Sapiom dashboard". A menu is its own surface, so it is sized by what it has + to say, not by the row that opened it; the popover's measured pass shifts the + wider panel back into view. Mirrors the studio rail reference. */ .profile-menu { - min-width: max-content; + width: var(--menu-w); + min-width: 240px; } @media (prefers-reduced-motion: reduce) { @@ -1501,9 +1507,13 @@ button.rail-footer-card:hover { align-items: center; gap: var(--sp2); width: 100%; - /* A floor, not a fixed height: a row that does wrap (a long session name in - a narrow menu) grows instead of spilling over its neighbours. */ + /* A floor, not a fixed height, and one line: at the fixed menu width a label + clips rather than wrapping on top of the row below it. */ + height: auto; min-height: var(--tree-row-h); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; padding: 0 var(--sp2); border: 0; background: transparent; From bac70ce327644dcef489521a724e446014e9fd10 Mon Sep 17 00:00:00 2001 From: ewan Date: Tue, 25 Aug 2026 14:09:07 +0000 Subject: [PATCH 3/7] fix(harness): open the profile menu beside the rail, not over it The rest of the studio rail reference's footer-menu anatomy: right-bottom beside the rail (harness's right-end + besideRef) and the roomier menu well, so the panel gets its own ground instead of floating on the rail's footprint. --- packages/harness/web/e2e/popover-crop.spec.ts | 4 ++++ .../harness/web/src/components/WorkflowsRail.tsx | 12 ++++++++++-- packages/harness/web/src/styles.css | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/harness/web/e2e/popover-crop.spec.ts b/packages/harness/web/e2e/popover-crop.spec.ts index 80ecf714b..f668b81c9 100644 --- a/packages/harness/web/e2e/popover-crop.spec.ts +++ b/packages/harness/web/e2e/popover-crop.spec.ts @@ -100,6 +100,10 @@ test("profile menu rows stay single-line at the rail's minimum width", async ({ const menuBox = await menu.boundingBox(); expect(menuBox!.width).toBeGreaterThan(triggerBox!.width); expect(menuBox!.width, "menu holds its readable floor").toBeGreaterThanOrEqual(240); + // And it opens beside the rail rather than over it: on the rail's own + // footprint a panel this shape reads as nothing having happened. + const railBox = await page.locator(".rail-workflows").boundingBox(); + expect(menuBox!.x, "menu clears the rail's right edge").toBeGreaterThanOrEqual(railBox!.x + railBox!.width); await expectUncropped(page, menu); }); diff --git a/packages/harness/web/src/components/WorkflowsRail.tsx b/packages/harness/web/src/components/WorkflowsRail.tsx index b070cad6f..648947711 100644 --- a/packages/harness/web/src/components/WorkflowsRail.tsx +++ b/packages/harness/web/src/components/WorkflowsRail.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import type { JSX } from "react"; +import type { JSX, RefObject } from "react"; import type { AppState, EditorKind, @@ -940,6 +940,7 @@ export function WorkflowsRail({ fixture in demo); a view with nothing to state renders nothing. */} ; authenticated: boolean; organizationName: string | null; telemetryOptIn: boolean; @@ -1174,11 +1178,15 @@ function ProfileRow({ /> + {/* The profile menu opens BESIDE the rail, bottom edges aligned: a panel + that floats over the rail on the rail's own footprint reads as nothing + having happened. Clearing the rail gives it its own ground. */} Date: Tue, 25 Aug 2026 14:12:15 +0000 Subject: [PATCH 4/7] fix(harness): open the rail settings popover beside the rail at its own width Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/profile-menu-narrow-rail.md | 2 +- packages/harness/web/e2e/popover-crop.spec.ts | 12 ++++++++++-- .../harness/web/src/components/WorkflowsRail.tsx | 10 +++++----- packages/harness/web/src/styles.css | 9 ++++++--- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.changeset/profile-menu-narrow-rail.md b/.changeset/profile-menu-narrow-rail.md index aa42f0272..bd21121c0 100644 --- a/.changeset/profile-menu-narrow-rail.md +++ b/.changeset/profile-menu-narrow-rail.md @@ -2,4 +2,4 @@ "@sapiom/harness": patch --- -Keep the rail's profile menu readable when the rail is dragged narrow: the panel now carries its own readable width (the studio rail's `--menu-w`) instead of inheriting the rail's width and wrapping labels on top of each other. +Keep the rail's footer panels readable when the rail is dragged narrow: the profile menu and settings popover now carry their own readable width (the studio rail's `--menu-w` / `320px`) and open beside the rail, instead of inheriting the rail's width and wrapping labels on top of each other. diff --git a/packages/harness/web/e2e/popover-crop.spec.ts b/packages/harness/web/e2e/popover-crop.spec.ts index f668b81c9..3cf667627 100644 --- a/packages/harness/web/e2e/popover-crop.spec.ts +++ b/packages/harness/web/e2e/popover-crop.spec.ts @@ -107,10 +107,18 @@ test("profile menu rows stay single-line at the rail's minimum width", async ({ await expectUncropped(page, menu); }); -test("settings popover opens uncropped off the rail footer", async ({ page }) => { +test("settings popover opens beside the rail, uncropped", async ({ page }) => { await page.getByTestId("brand-identity").click(); await page.getByTestId("settings-trigger").click(); - await expectUncropped(page, page.getByTestId("settings-popover")); + const popover = page.getByTestId("settings-popover"); + await expectUncropped(page, popover); + + const popoverBox = await popover.boundingBox(); + expect(popoverBox!.width, "popover holds its readable floor").toBeGreaterThanOrEqual(280); + const railBox = await page.locator(".rail-workflows").boundingBox(); + expect(popoverBox!.x, "popover clears the rail's right edge").toBeGreaterThanOrEqual( + railBox!.x + railBox!.width, + ); }); test("session bar menu opens uncropped at the header's right cluster", async ({ page }) => { diff --git a/packages/harness/web/src/components/WorkflowsRail.tsx b/packages/harness/web/src/components/WorkflowsRail.tsx index 648947711..073558284 100644 --- a/packages/harness/web/src/components/WorkflowsRail.tsx +++ b/packages/harness/web/src/components/WorkflowsRail.tsx @@ -1155,8 +1155,8 @@ function ProfileRow({ open={settingsOpen} anchorRef={triggerRef} onDismiss={closeSettings} - placement="up-start" - matchWidth + placement="right-end" + besideRef={railRef} className="settings-popover" testid="settings-popover" > @@ -1178,9 +1178,9 @@ function ProfileRow({ /> - {/* The profile menu opens BESIDE the rail, bottom edges aligned: a panel - that floats over the rail on the rail's own footprint reads as nothing - having happened. Clearing the rail gives it its own ground. */} + {/* Like the settings panel above: BESIDE the rail, bottom edges aligned. + A panel that floats over the rail on the rail's own footprint reads as + nothing having happened; clearing the rail gives it its own ground. */} Date: Tue, 25 Aug 2026 14:25:58 +0000 Subject: [PATCH 5/7] test(harness): dismiss the settings popover from a point measured clear of it Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/harness/web/e2e/dismiss.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/harness/web/e2e/dismiss.spec.ts b/packages/harness/web/e2e/dismiss.spec.ts index 4d71a391e..c0e3fe672 100644 --- a/packages/harness/web/e2e/dismiss.spec.ts +++ b/packages/harness/web/e2e/dismiss.spec.ts @@ -56,11 +56,11 @@ test.describe("settings popover", () => { await page.getByTestId("telemetry-toggle").click(); await expect(popover).toBeVisible(); - // The popover is anchored to the footer account row and grows UP across the - // full-width rail (up-start + matchWidth), so it now covers the rail's - // brand lockup — click the main panel instead, which the rail-width popover - // never reaches. - await page.locator(".terminal-slot").click(); + // The panel floats beside the rail over the main pane, so no fixed selector + // is reliably outside it: click a point measured to be clear of it instead. + const box = (await popover.boundingBox())!; + const viewport = page.viewportSize()!; + await page.mouse.click(Math.min(box.x + box.width + 24, viewport.width - 8), 8); await expect(popover).toBeHidden(); }); From 50f56a4a97dc240755b5e0982bf79ed899dc4a5b Mon Sep 17 00:00:00 2001 From: ewan Date: Tue, 25 Aug 2026 15:50:24 +0000 Subject: [PATCH 6/7] fix(harness): truncate the plan readout instead of letting it run under the Upgrade pill Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/profile-menu-narrow-rail.md | 2 +- .../harness/web/e2e/rail-footer-cards.spec.ts | 54 ++++++++++++++++--- packages/harness/web/src/styles.css | 4 ++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/.changeset/profile-menu-narrow-rail.md b/.changeset/profile-menu-narrow-rail.md index bd21121c0..9ffc8ae52 100644 --- a/.changeset/profile-menu-narrow-rail.md +++ b/.changeset/profile-menu-narrow-rail.md @@ -2,4 +2,4 @@ "@sapiom/harness": patch --- -Keep the rail's footer panels readable when the rail is dragged narrow: the profile menu and settings popover now carry their own readable width (the studio rail's `--menu-w` / `320px`) and open beside the rail, instead of inheriting the rail's width and wrapping labels on top of each other. +Keep the rail's footer panels readable when the rail is dragged narrow: the profile menu and settings popover now carry their own readable width (the studio rail's `--menu-w` / `320px`) and open beside the rail, instead of inheriting the rail's width and wrapping labels on top of each other. The plan card's spend readout now truncates with the plan name instead of running under the Upgrade pill at the same widths. diff --git a/packages/harness/web/e2e/rail-footer-cards.spec.ts b/packages/harness/web/e2e/rail-footer-cards.spec.ts index b1d848ee8..ffeec5925 100644 --- a/packages/harness/web/e2e/rail-footer-cards.spec.ts +++ b/packages/harness/web/e2e/rail-footer-cards.spec.ts @@ -27,7 +27,9 @@ test.describe("plan card", () => { await expect(page.locator(".rail-workflows")).toBeVisible(); }); - test("renders the mock plan with the spend-vs-limit readout", async ({ page }) => { + test("renders the mock plan with the spend-vs-limit readout", async ({ + page, + }) => { const card = page.getByTestId("plan-card"); await expect(card).toBeVisible(); await expect(card).toContainText("Free plan"); @@ -46,7 +48,9 @@ test.describe("plan card", () => { await expect(upgrade).toHaveAttribute("target", "_blank"); }); - test("the overflow menu offers the two billing links and dismisses", async ({ page }) => { + test("the overflow menu offers the two billing links and dismisses", async ({ + page, + }) => { await page.getByTestId("plan-menu-trigger").click(); const menu = page.getByTestId("plan-menu"); await expect(menu).toBeVisible(); @@ -57,14 +61,48 @@ test.describe("plan card", () => { await expect(menu).not.toBeVisible(); }); - test("sits above the account row inside one footer block", async ({ page }) => { + test("keeps the readout clear of the Upgrade pill at the rail's minimum width", async ({ + page, + }) => { + await page.evaluate(() => { + localStorage.setItem( + "sapiom-harness-pane-widths", + JSON.stringify({ rail: 180 }), + ); + }); + await page.reload(); + await expect(page.getByTestId("plan-card")).toBeVisible(); + + const readout = page.getByTestId("plan-balance"); + // The copy column shrinks with the rail, so the money line has to CLIP: + // painted at full length it runs straight under the CTA beside it. + const clipped = await readout.evaluate( + (el) => el.scrollWidth > el.clientWidth, + ); + const readoutBox = (await readout.boundingBox())!; + const upgradeBox = (await page.getByTestId("plan-upgrade").boundingBox())!; + expect( + readoutBox.x + readoutBox.width, + "readout ends before the Upgrade pill", + ).toBeLessThanOrEqual(upgradeBox.x + 0.5); + expect( + clipped, + "a value with nowhere to go is ellipsised, not overflowing", + ).toBe(true); + }); + + test("sits above the account row inside one footer block", async ({ + page, + }) => { const footer = page.locator(".rail-footer"); await expect(footer.getByTestId("plan-card")).toBeVisible(); await expect(footer.getByTestId("brand-identity")).toBeVisible(); const cardBox = await footer.getByTestId("plan-card").boundingBox(); const accountBox = await footer.getByTestId("brand-identity").boundingBox(); expect(cardBox && accountBox && cardBox.y < accountBox.y).toBe(true); - await footer.screenshot({ path: "web/e2e/screenshots/rail-footer-plan-card.png" }); + await footer.screenshot({ + path: "web/e2e/screenshots/rail-footer-plan-card.png", + }); }); }); @@ -101,7 +139,9 @@ test.describe("update card", () => { // can't see and doesn't need to. return Promise.resolve({ kind: "downloaded", version: "0.4.2" }); }, - onUpdateState: (cb: (state: { kind: string; version?: string }) => void) => { + onUpdateState: ( + cb: (state: { kind: string; version?: string }) => void, + ) => { window.__pushUpdateState = cb; return () => {}; }, @@ -132,9 +172,7 @@ test.describe("update card", () => { // Click → exactly one checkForUpdates() round-trip, and the card STAYS // (it outlives "Later"; only a state push removes it). await card.click(); - await expect - .poll(() => page.evaluate(() => window.__updateChecks)) - .toBe(1); + await expect.poll(() => page.evaluate(() => window.__updateChecks)).toBe(1); await expect(card).toBeVisible(); // A `none` push (failed apply cleared pending) retracts the card. diff --git a/packages/harness/web/src/styles.css b/packages/harness/web/src/styles.css index a905693b9..1be00693f 100644 --- a/packages/harness/web/src/styles.css +++ b/packages/harness/web/src/styles.css @@ -1306,6 +1306,10 @@ input { font-size: var(--type-meta); color: var(--text-dim); white-space: nowrap; + /* Clips like the name above it: the copy column shrinks with the rail, and a + value with nowhere left to go must end rather than run under the CTA. */ + overflow: hidden; + text-overflow: ellipsis; } /* The Upgrade CTA rides the shared .pill surface but at a clickable height — From a18327d37e142d7fdda59e23da36c34956ebd4f7 Mon Sep 17 00:00:00 2001 From: ewan Date: Tue, 25 Aug 2026 15:51:16 +0000 Subject: [PATCH 7/7] test(harness): keep the plan-card regression in the file's existing formatting Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../harness/web/e2e/rail-footer-cards.spec.ts | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/packages/harness/web/e2e/rail-footer-cards.spec.ts b/packages/harness/web/e2e/rail-footer-cards.spec.ts index ffeec5925..aa53045ca 100644 --- a/packages/harness/web/e2e/rail-footer-cards.spec.ts +++ b/packages/harness/web/e2e/rail-footer-cards.spec.ts @@ -27,9 +27,7 @@ test.describe("plan card", () => { await expect(page.locator(".rail-workflows")).toBeVisible(); }); - test("renders the mock plan with the spend-vs-limit readout", async ({ - page, - }) => { + test("renders the mock plan with the spend-vs-limit readout", async ({ page }) => { const card = page.getByTestId("plan-card"); await expect(card).toBeVisible(); await expect(card).toContainText("Free plan"); @@ -48,9 +46,7 @@ test.describe("plan card", () => { await expect(upgrade).toHaveAttribute("target", "_blank"); }); - test("the overflow menu offers the two billing links and dismisses", async ({ - page, - }) => { + test("the overflow menu offers the two billing links and dismisses", async ({ page }) => { await page.getByTestId("plan-menu-trigger").click(); const menu = page.getByTestId("plan-menu"); await expect(menu).toBeVisible(); @@ -65,44 +61,31 @@ test.describe("plan card", () => { page, }) => { await page.evaluate(() => { - localStorage.setItem( - "sapiom-harness-pane-widths", - JSON.stringify({ rail: 180 }), - ); + localStorage.setItem("sapiom-harness-pane-widths", JSON.stringify({ rail: 180 })); }); await page.reload(); await expect(page.getByTestId("plan-card")).toBeVisible(); - const readout = page.getByTestId("plan-balance"); - // The copy column shrinks with the rail, so the money line has to CLIP: + // The copy column shrinks with the rail, so the money line has to clip: // painted at full length it runs straight under the CTA beside it. - const clipped = await readout.evaluate( - (el) => el.scrollWidth > el.clientWidth, - ); + const readout = page.getByTestId("plan-balance"); + const clipped = await readout.evaluate((el) => el.scrollWidth > el.clientWidth); const readoutBox = (await readout.boundingBox())!; const upgradeBox = (await page.getByTestId("plan-upgrade").boundingBox())!; - expect( - readoutBox.x + readoutBox.width, - "readout ends before the Upgrade pill", - ).toBeLessThanOrEqual(upgradeBox.x + 0.5); - expect( - clipped, - "a value with nowhere to go is ellipsised, not overflowing", - ).toBe(true); + expect(readoutBox.x + readoutBox.width, "readout ends before the pill").toBeLessThanOrEqual( + upgradeBox.x + 0.5, + ); + expect(clipped, "a value with nowhere to go is ellipsised, not overflowing").toBe(true); }); - test("sits above the account row inside one footer block", async ({ - page, - }) => { + test("sits above the account row inside one footer block", async ({ page }) => { const footer = page.locator(".rail-footer"); await expect(footer.getByTestId("plan-card")).toBeVisible(); await expect(footer.getByTestId("brand-identity")).toBeVisible(); const cardBox = await footer.getByTestId("plan-card").boundingBox(); const accountBox = await footer.getByTestId("brand-identity").boundingBox(); expect(cardBox && accountBox && cardBox.y < accountBox.y).toBe(true); - await footer.screenshot({ - path: "web/e2e/screenshots/rail-footer-plan-card.png", - }); + await footer.screenshot({ path: "web/e2e/screenshots/rail-footer-plan-card.png" }); }); }); @@ -139,9 +122,7 @@ test.describe("update card", () => { // can't see and doesn't need to. return Promise.resolve({ kind: "downloaded", version: "0.4.2" }); }, - onUpdateState: ( - cb: (state: { kind: string; version?: string }) => void, - ) => { + onUpdateState: (cb: (state: { kind: string; version?: string }) => void) => { window.__pushUpdateState = cb; return () => {}; }, @@ -172,7 +153,9 @@ test.describe("update card", () => { // Click → exactly one checkForUpdates() round-trip, and the card STAYS // (it outlives "Later"; only a state push removes it). await card.click(); - await expect.poll(() => page.evaluate(() => window.__updateChecks)).toBe(1); + await expect + .poll(() => page.evaluate(() => window.__updateChecks)) + .toBe(1); await expect(card).toBeVisible(); // A `none` push (failed apply cleared pending) retracts the card.