Render Mermaid diagrams in prose with a kawaii treatment (+ Pretext label metrics) - #9
Render Mermaid diagrams in prose with a kawaii treatment (+ Pretext label metrics)#9maceip wants to merge 2 commits into
Conversation
Fenced ```mermaid blocks in agent turns and comments render as inline SVG through a vendored copy of beautiful-mermaid (Craft, MIT) with surgical edits marked AXP: leaf-shaped arrowheads with a faint midrib, rounded corners on every rectangle and polygon vertex, rounded edge bends with round caps, sticker-soft node shadows, dashed rounded subgraphs, pill edge labels, and no remote font @import (the workspace loads nothing third-party). - ui/src/Diagram.tsx renders synchronously, refuses script-like output and falls back to the source with the parse error - Loaded lazily from the Markdown pre renderer; ELK is its own 1.5 MB chunk - Vendored sources live in ui/vendor and are typed through a small .d.ts boundary so the workspace's strict tsconfig applies to our code only - Palette in ui/src/diagram-theme.ts; samples in docs/design/diagrams regenerated by scripts/design/render-diagrams.mts - Demo fixture: the email-task session now includes a flowchart - Notices cover beautiful-mermaid, ELK (EPL-2.0) and entities (BSD-2)
…lines beautiful-mermaid sized nodes from a character-width table for Inter and could not wrap a label, so a wordy node became a very wide box. Two hooks in the vendored renderer, no-ops outside a browser: setTextMeasurer for exact single-line widths in the page's real font, and setLabelWrapper, which breaks node labels over 190px (edge labels over 140px) into lines and then narrows the width by binary search until one more line would be needed, so lines come out balanced. ui/src/diagram-text.ts implements both with @chenglou/pretext (MIT, canvas-measured, off the DOM); Diagram.tsx installs them. Review samples and the demo fixture gain a long label.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85ffeb2d54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <figure | ||
| className="diagram" | ||
| role="img" | ||
| aria-label="Diagram" |
There was a problem hiding this comment.
Expose the diagram text to assistive technology
For every successfully rendered Mermaid block, assigning role="img" with only the generic label "Diagram" causes assistive technology to treat the entire figure as a single image and ignore its SVG descendants. Screen-reader users therefore lose all node and edge text that was previously available in the fenced source. Include the diagram source or an equivalent textual description in the accessible name/description instead of replacing it with a generic label.
Useful? React with 👍 / 👎.
| const lines = layoutWithLines(ready, high, fontSize * 1.4).lines.map( | ||
| (line) => line.text.trim(), | ||
| ); | ||
| return lines.filter(Boolean).join("\n"); |
There was a problem hiding this comment.
Preserve inline formatting while wrapping labels
When a formatted flowchart label such as A[**A sufficiently long bold label**] exceeds the wrapping width, the parser has already converted the markup to <b>...</b>, and these lines split and rejoin that tagged string verbatim. Because renderLineContent() parses each resulting line with fresh formatting state, only the line containing the opening tag remains bold (and a break inside a tag can expose it as text). Wrap the plain label while carrying or reapplying formatting across generated line boundaries.
Useful? React with 👍 / 👎.
| * Returns the type keyword used for routing to the correct pipeline. | ||
| */ | ||
| function detectDiagramType(text: string): 'flowchart' | 'sequence' | 'class' | 'er' | 'xychart' { | ||
| const firstLine = text.trim().split(/[\n;]/)[0]?.trim().toLowerCase() ?? '' |
There was a problem hiding this comment.
Skip leading comments when detecting diagram type
A valid non-flowchart diagram with a leading Mermaid comment, such as %% explanation followed by sequenceDiagram, is classified as a flowchart because detection examines the first raw line. The flowchart parser later removes the comment but rejects sequenceDiagram as its header, so the UI falls back to source instead of rendering. Filter comment lines before selecting the diagram pipeline, as the individual parsers already do.
Useful? React with 👍 / 👎.
| // --- activate / deactivate explicit commands --- | ||
| // These are handled implicitly via +/- on messages but can also appear standalone | ||
| // For now, we skip explicit activate/deactivate lines (they affect rendering only) |
There was a problem hiding this comment.
Render standalone sequence activation commands
For standard sequence diagrams that use activate B and deactivate B on separate lines, the parser silently discards both commands, so the resulting SVG omits the activation bar and misrepresents the interaction without showing a parse error. The layout and renderer already support activation records through message shorthand; these standalone commands need to update the same activation state rather than being skipped.
Useful? React with 👍 / 👎.
| try { | ||
| const svg = renderMermaidSVG(code, { | ||
| ...AXP_DIAGRAM_COLORS, | ||
| font: "AXP Runde", |
There was a problem hiding this comment.
Use the self-hosted workspace font for diagrams
The SVG requests AXP Runde, but the application only self-hosts DM Sans Variable and contains no @font-face or package defining AXP Runde. Consequently every browser falls through to its platform system-ui font, rather than inheriting the workspace font as intended, and the Pretext measurements and wrapping vary by operating system. Use the loaded DM Sans Variable family consistently or actually provide the requested font.
Useful? React with 👍 / 👎.
| "elkjs", | ||
| "entities", | ||
| "@chenglou/pretext", |
There was a problem hiding this comment.
Ship the vendored renderer's license with the package
The build now collects licenses for the renderer's npm dependencies but never copies ui/vendor/beautiful-mermaid/LICENSE into dist. Because the published package includes dist but excludes ui/vendor, consumers receive the bundled beautiful-mermaid code without its required MIT copyright and permission notice; the short attribution in THIRD_PARTY_NOTICES.md is not the full notice. Append the vendored license to dist/ui/licenses/bundled.txt or otherwise include it in the published files.
Useful? React with 👍 / 👎.
What
Fenced
mermaidblocks in agent turns and comments render as inline SVG through a vendored copy of beautiful-mermaid (Craft, MIT) with surgical edits markedAXP::@import(upstream pulls Inter from Google Fonts inside the SVG; the workspace loads nothing third-party)Second commit: real text metrics via Pretext (MIT). Upstream sized nodes from an Inter character-width table and could not wrap a label; now nodes fit AXP Runde exactly and labels over 190px break into balanced lines ("Checkpoint saved with / the bundle and patch") via two hooks that are no-ops outside a browser.
Wiring:
ui/src/Diagram.tsxrenders synchronously, refuses script-like output and falls back to the source with the parse error; loaded lazily from the Markdownprerenderer (ELK is its own 1.5 MB chunk). Vendored sources live inui/vendorbehind a small.d.tsboundary so the workspace's strict tsconfig applies to our code only. Notices cover beautiful-mermaid, ELK (EPL-2.0), entities (BSD-2) and Pretext.Look at
docs/design/diagrams.mddocs/design/diagrams/*.svg— a flowchart, a state diagram, nested subgraphs (regenerate withnpx tsx scripts/design/render-diagrams.mts)npm run demo:ui→ "Handle email task retries" has a flowchart in the transcriptVerification
npm run checkandnpm run test:uipass.Landing
Adjacent-line conflicts with siblings in
THIRD_PARTY_NOTICES.md/scripts/ui-notices.mjsonly.