Skip to content

feat(site/src/pages/AgentsPage/components): open Mermaid diagrams in a lightbox - #29374

Merged
kylecarbs merged 2 commits into
mainfrom
feat/agents-mermaid-lightbox
Sep 15, 2026
Merged

kylecarbs merged 2 commits into
mainfrom
feat/agents-mermaid-lightbox

Conversation

@kylecarbs

@kylecarbs kylecarbs commented Sep 15, 2026

Copy link
Copy Markdown
Member

Follow-up to #29306. Mermaid diagrams in agent chat are scaled down to the message column, which makes wide ones (org charts, sequence diagrams with many participants) hard to read. This makes a rendered diagram clickable and opens it in a lightbox, the same affordance chat images already have via ImageLightbox.

  • The rendered diagram is a button (View diagram full size) with a zoom-in cursor, a subtle border change, and a maximize badge that appears on hover and keyboard focus.
  • The dialog shell is extracted from ImageLightbox into a shared Lightbox (same overlay and 85vh / 90vw bounds) that both images and diagrams render through. Mermaid emits a viewBox, so the SVG is sized to the largest width that keeps the whole diagram inside those bounds: at 1400x900 the sample flowchart goes from 914px inline to 1212px, the sequence diagram from 675px to 1212px, and at 700x600 it scales down to fit with no internal scrolling. It stays vector, so nothing blurs.
  • Escape and overlay click close it; focus returns to the diagram button via onCloseAutoFocus, since Radix's default did not reliably reach the trigger inside the message tree.
  • The Response stories' waitForDiagram helper waited on the placeholder's status role by name, but that role takes no name from content so the wait resolved immediately. It now waits for the diagram button, which also makes the Pixel captures deterministic.

New MermaidLightbox story captures the open state.

Verification
  • pnpm check, pnpm lint, pnpm lint:types, pnpm format
  • pnpm test:storybook src/pages/AgentsPage/components/ChatElements/Response.stories.tsx (18 pass)
  • Desktop browser check in both themes and at 1400x900 and 700x600: hover/focus affordances, dialog geometry and sizing, no internal scrollbars, no layout shift behind the overlay, label placement identical to inline, Escape and overlay close, focus restored to the trigger, no console errors.

Generated by Coder Agents on behalf of @kylecarbs.

… diagrams in a lightbox

Rendered diagrams are now a button that opens the SVG in a dialog sized
to the viewport, the same affordance chat images have. The dialog
returns focus to the diagram on close.
return `min(calc(90vw - 3rem), calc((85vh - 3rem) * ${width / height}))`;
};

const DiagramLightbox = ({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not make use of our existing Lightbox component? iirc a lot of work went into it for a few edge cases

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 492b6c4. ImageLightbox was img-specific (takes a src), so I pulled its dialog shell out into a Lightbox component (same overlay, same 90vw/85vh bounds, borderless frame, sr-only title) and made both ImageLightbox and the diagram render through it. ImageLightbox's markup and behaviour are unchanged; the only addition to the shell is an optional onCloseAutoFocus so the diagram can return focus to its trigger.

If the edge-case work you remember lives somewhere other than ImageLightbox.tsx (that file is 25 lines and hasn't changed since the routing refactor), point me at it and I'll fold it in.

…een chat images and Mermaid diagrams

Extract the dialog shell from ImageLightbox so the diagram preview
reuses it instead of assembling its own Dialog.
@kylecarbs kylecarbs changed the title feat(site/src/pages/AgentsPage/components/ChatElements): open Mermaid diagrams in a lightbox feat(site/src/pages/AgentsPage/components): open Mermaid diagrams in a lightbox Sep 15, 2026
Comment on lines +203 to +217
// Mermaid emits a viewBox, so the SVG scales with its container. The
// lightbox sizes its padded (3rem) frame to the largest width that
// keeps the whole diagram inside the 90vw by 85vh bounds.
const fittedWidth = (svg: string): string | undefined => {
const viewBox = svg
.match(/viewBox="([^"]+)"/)?.[1]
.trim()
.split(/\s+/);
const width = Number(viewBox?.[2]);
const height = Number(viewBox?.[3]);
if (!(width > 0 && height > 0)) {
return undefined;
}
return `min(90vw, calc((85vh - 3rem) * ${width / height} + 3rem))`;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this concerns me a little but i don't know why. i feel like there has gotta be a better way

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed i'll investigate

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked into alternatives:

  • Pure CSS (width: calc(90vw - 3rem); height: auto; max-height: calc(85vh - 3rem) on the SVG, no viewBox parsing). Chromium does not apply the aspect-ratio-preserving max-height reduction to inline <svg> the way it does for <img>: when height-limited the SVG squashes (measured ratio 2.42 vs 1.93 intrinsic). So CSS alone cannot fit both bounds.
  • <img src="data:image/svg+xml,..."> through ImageLightbox gets object-contain for free, but an SVG-as-image cannot use page fonts, so labels fall back to a different font than the one mermaid measured against and overflow their boxes.
  • Reading viewBox from the DOM (svg.viewBox.baseVal) is the same information as the regex, just needing a ref and an effect to get it.

The diagram's aspect ratio is only available from the viewBox mermaid emits, so some parse of it is required to size the frame for both bounds. Keeping the regex since it is the smallest version of that; happy to switch to DOMParser if you would rather not pattern-match on the string.

@kylecarbs
kylecarbs merged commit 88137fc into main Sep 15, 2026
32 of 34 checks passed
@kylecarbs
kylecarbs deleted the feat/agents-mermaid-lightbox branch September 15, 2026 20:31
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants