feat(site/src/pages/AgentsPage/components): open Mermaid diagrams in a lightbox - #29374
Conversation
… 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 = ({ |
There was a problem hiding this comment.
Can we not make use of our existing Lightbox component? iirc a lot of work went into it for a few edge cases
There was a problem hiding this comment.
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.
| // 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))`; | ||
| }; |
There was a problem hiding this comment.
this concerns me a little but i don't know why. i feel like there has gotta be a better way
There was a problem hiding this comment.
agreed i'll investigate
There was a problem hiding this comment.
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,...">throughImageLightboxgetsobject-containfor 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
viewBoxfrom 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.
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.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.ImageLightboxinto a sharedLightbox(same overlay and85vh/90vwbounds) that both images and diagrams render through. Mermaid emits aviewBox, 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.onCloseAutoFocus, since Radix's default did not reliably reach the trigger inside the message tree.waitForDiagramhelper waited on the placeholder'sstatusrole 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
MermaidLightboxstory captures the open state.Verification
pnpm check,pnpm lint,pnpm lint:types,pnpm formatpnpm test:storybook src/pages/AgentsPage/components/ChatElements/Response.stories.tsx(18 pass)Generated by Coder Agents on behalf of @kylecarbs.