From ea30d2616a6ea1ad51447cde6e2fc6da4904fe21 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:58:28 +0000 Subject: [PATCH] Restore Mermaid source toggles and bound inline previews (#8932) * fix(app): keep Mermaid controls usable in inline previews * test(app): verify Mermaid controls through user journeys Keep source-toggle mechanics in diagram helpers and compare preview geometry with its screen and controls instead of requiring exact pixel heights. Co-authored-by: luke --------- Co-authored-by: luke Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../e2e/browser/mermaid-fullscreen.spec.ts | 36 ++++++++++++ packages/app/e2e/support/helpers/diagram.ts | 56 +++++++++++++++++++ .../markdown/fence/mermaid/host.web.tsx | 34 ++++++----- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/packages/app/e2e/browser/mermaid-fullscreen.spec.ts b/packages/app/e2e/browser/mermaid-fullscreen.spec.ts index 72b05ed83e..4b458c4d03 100644 --- a/packages/app/e2e/browser/mermaid-fullscreen.spec.ts +++ b/packages/app/e2e/browser/mermaid-fullscreen.spec.ts @@ -1,6 +1,8 @@ import { test } from "../support/fixtures"; import { closeDiagramFullscreen, + expectInlineDiagramFitsViewport, + toggleDiagramSourceAndVerifyControls, closeDiagramFullscreenFromOutside, expectCompletedDiagram, expectFullscreenDiagram, @@ -21,6 +23,36 @@ const MERMAID_DIAGRAM = [ "```", ].join("\n"); +test("bounds a tall inline diagram", async ({ page }) => { + test.setTimeout(120_000); + const diagram = [ + "```mermaid", + "flowchart TD", + ...Array.from({ length: 30 }, (_, i) => `N${i} --> N${i + 1}`), + "```", + ].join("\n"); + const agent = await seedMockAgentWorkspace({ + repoPrefix: "mermaid-tall-", + title: "Tall Mermaid", + featureValues: { + mockStreamingAssistantResponse: diagram, + mockStreamingAssistantIntervalMs: 10, + }, + }); + try { + await openAgentRoute(page, agent); + await requestDiagram(agent); + await waitForDiagramTurnToComplete(agent); + await expectCompletedDiagram(page, ["N0", "N30"]); + await expectInlineDiagramFitsViewport(page); + await openDiagramFullscreen(page); + await expectFullscreenDiagram(page, ["N0", "N30"]); + await closeDiagramFullscreen(page); + } finally { + await agent.cleanup(); + } +}); + test("opens a Mermaid diagram fullscreen and closes it again", async ({ page }) => { test.setTimeout(120_000); @@ -40,6 +72,10 @@ test("opens a Mermaid diagram fullscreen and closes it again", async ({ page }) await expectCompletedDiagram(page, ["Start", "Ship"]); }); + await test.step("Source toggles preserve the diagram and exclude hidden controls", async () => { + await toggleDiagramSourceAndVerifyControls(page); + }); + await test.step("The toolbar opens the diagram fullscreen", async () => { await openDiagramFullscreen(page); await expectFullscreenDiagram(page, ["Start", "Review", "Ship"]); diff --git a/packages/app/e2e/support/helpers/diagram.ts b/packages/app/e2e/support/helpers/diagram.ts index 82fd745de1..48ecc60086 100644 --- a/packages/app/e2e/support/helpers/diagram.ts +++ b/packages/app/e2e/support/helpers/diagram.ts @@ -148,3 +148,59 @@ export async function expectFullscreenDiagram( export async function expectFullscreenDiagramClosed(page: Page): Promise { await expect(page.getByTestId(FULLSCREEN_VIEWPORT)).toHaveCount(0); } + +/** Tall diagrams stay within the screen so their controls remain reachable. */ +export async function expectInlineDiagramFitsViewport(page: Page): Promise { + const viewport = page.getByTestId("mermaid-viewport").last(); + const box = await viewport.boundingBox(); + expect(box, "the inline diagram has measurable layout").not.toBeNull(); + const screenHeight = page.viewportSize()!.height; + expect(box!.height, "the inline diagram fits within the screen").toBeLessThan(screenHeight); + const contentHeight = await diagramSvg(page, "mermaid-viewport").evaluate( + (svg) => (svg as SVGSVGElement).viewBox.baseVal.height, + ); + expect(box!.height, "a tall diagram is bounded rather than expanding the chat").toBeLessThan( + contentHeight, + ); +} + +/** Exercise pointer toggles and the controls encountered along that same journey. */ +export async function toggleDiagramSourceAndVerifyControls(page: Page): Promise { + const viewport = page.getByTestId("mermaid-viewport").last(); + const iframe = await viewport.locator("iframe").elementHandle(); + for (let iteration = 0; iteration < 3; iteration++) { + await viewport.hover(); + await page.getByRole("button", { name: "View source" }).last().click(); + const viewDiagram = page.getByRole("button", { name: "View diagram" }).last(); + await viewDiagram.click({ timeout: 10_000 }); + await expect(viewDiagram).toHaveCount(0); + await expectFullscreenDiagramClosed(page); + expect( + await viewport.locator("iframe").evaluate((node, original) => node === original, iframe), + "switching views preserves the rendered diagram", + ).toBe(true); + } + await viewport.hover(); + await page.getByRole("button", { name: "View source" }).last().click(); + const hiddenFullscreen = viewport.getByRole("button", { + name: "Fullscreen", + includeHidden: true, + }); + await hiddenFullscreen.focus(); + await expect(hiddenFullscreen).not.toBeFocused(); + await page.getByRole("button", { name: "View diagram" }).last().click(); + await viewport.hover(); + const fullscreen = viewport.getByRole("button", { name: "Fullscreen", exact: true }); + const box = await viewport.boundingBox(); + const control = await fullscreen.boundingBox(); + expect(box).not.toBeNull(); + expect(control).not.toBeNull(); + expect(control!.y, "the toolbar starts inside the preview").toBeGreaterThanOrEqual(box!.y); + expect(control!.y + control!.height, "the toolbar fits inside the preview").toBeLessThanOrEqual( + box!.y + box!.height, + ); + await viewport.getByRole("button", { name: "Zoom in" }).click(); + await expect(viewport.getByRole("button", { name: "Reset view" })).toBeEnabled(); + await viewport.getByRole("button", { name: "Reset view" }).click(); + await expect(viewport.getByRole("button", { name: "Reset view" })).toBeDisabled(); +} diff --git a/packages/app/src/components/markdown/fence/mermaid/host.web.tsx b/packages/app/src/components/markdown/fence/mermaid/host.web.tsx index 9370bc90c0..94d6ccc666 100644 --- a/packages/app/src/components/markdown/fence/mermaid/host.web.tsx +++ b/packages/app/src/components/markdown/fence/mermaid/host.web.tsx @@ -72,7 +72,7 @@ function MermaidFenceHostImpl({ () => [ getDiagramBoxStyle(textStyle), containerStyle, - { height: runtimeHeight, minHeight: MIN_DIAGRAM_BOX_HEIGHT }, + { height: Math.min(runtimeHeight, MAX_PREVIEW_HEIGHT), minHeight: MIN_DIAGRAM_BOX_HEIGHT }, ], [runtimeHeight, textStyle], ); @@ -111,20 +111,22 @@ function MermaidFenceHostImpl({ ) : null} ) : null} - - - +
+ + + +
{isFullscreen && visible ? (