feat(hub+cli+web): mermaid parse-failure feedback — detect broken diagrams, notify agent, show error placeholder#71
Conversation
…n#829) When the hub stores an assistant message containing one or more ```mermaid blocks whose diagram type is not recognised, it immediately emits a synthetic <render-issue> user-message back to the connected CLI socket. The CLI enqueues it and the agent sees it as the next user turn, prompting self-correction without any browser-round-trip. Changes: - hub/src/sync/mermaid.ts: extractFailingMermaidBlocks + buildMermaidRenderIssueHint Extracts mermaid fences from assistant message text, validates the first token against a case-insensitive set of known diagram types (mermaid v11), returns one MermaidIssue per failing block. - hub/src/socket/handlers/cli/sessionHandlers.ts: hook after bgDelta Calls the extractor after addMessage; emits a UserMessage-shaped update directly to the originating CLI socket (not to web clients). - cli/src/api/apiSession.ts: add '<render-issue>' to SYSTEM_INJECTION_PREFIXES Prevents the hint from being classified as an external human turn in the session log. - hub/src/sync/mermaid.test.ts: 13 unit tests covering extraction and hint building. Closes tiann#829 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
getDiagramType() now skips blank lines and lines starting with %% before
extracting the first token, preventing false positives when agents use
%%{init: {...}}%% config directives before the diagram type keyword.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
Live CLI sessions wrap assistant output in role:'agent' + content.type:'output' (Claude) or type:'codex' (Codex/Gemini/OpenCode). The previous guard only accepted role:'assistant', so real sessions never triggered the <render-issue> hint. Adds extractTextFromRecord() to dig through both agent wrapper shapes before falling back to the bare role:'assistant' format used in tests and older clients. Adds 4 new tests covering the two live-session envelope formats. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
getDiagramType() now skips YAML frontmatter blocks (--- delimiters) so diagrams with a title/config preamble are not falsely flagged. Also adds the six diagram types shipped in mermaid 11.12–11.14 that were missing from the allowlist: radar-beta, treemap, treeview-beta, venn-beta, wardley-beta, ishikawa. The actual keyword strings were verified from the mermaid 11.14.0 dist bundle (treemap and ishikawa have no -beta suffix). Adds two new test cases covering frontmatter skipping and all six new types. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
The synthetic <render-issue> message is not stored in the hub DB, so using msg.seq (the stored assistant message's position) as its sequence number would cause two 'new-message' updates with identical seqs on any client receiving both. Using Date.now() matches the pattern used by other synthetic hub updates (update-session, update-metadata). Also fixes a minor test case that used camelCase treeView-beta instead of the canonical lowercase treeview-beta. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
…rsor DB message seqs are small per-session integers; Date.now() returns an epoch-ms value orders of magnitude larger. The CLI's IncomingMessageFilter stores the max seen seq and uses it as afterSeq on reconnect — a Date.now() seq would advance the cursor past every real stored message, causing all subsequent messages to be skipped on backfill. Using msg.seq keeps the hint at the same cursor position as the triggering assistant message. Dedup is by UUID (id field), not seq, so there is no collision. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
…older Shows a beefy amber warning card when a mermaid block fails to render instead of spilling raw markup text into the chat. Adds a three-state render cycle (pending → silent skeleton, error → warning card, success → SVG) so the brief async-parse window is also clean. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
…ection Removes the conditional "if the diagram was intended" escape hatch and replaces the polite request with an unconditional MUST directive. Agents previously reasoned out of correcting when the original breakage felt intentional; the mandatory framing closes that gap. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e7c4f5dad
ℹ️ 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".
| '<command-name>', | ||
| '<local-command-caveat>', | ||
| '<system-reminder>', | ||
| '<render-issue>', |
There was a problem hiding this comment.
Keep render-issue echoes out of chat history
In Claude sessions where the echoed user turn is serialized as text blocks, this prefix only makes isExternalUserMessage wrap the echo as role:'agent'/content.type:'output'; the web normalizer still maps non-sidechain data.type === 'user' messages with all-text array content back to role:'user' (web/src/chat/normalizeAgent.ts:349-364). That means the internal <render-issue> prompt and diagram snippet can appear in the visible transcript even though it is meant to be a system injection; suppress this prefix in the web normalizer or avoid storing the echo.
Useful? React with 👍 / 👎.
| The agent used an unrecognised diagram type. It has been notified and | ||
| will provide a corrected version below. |
There was a problem hiding this comment.
Avoid promising fixes for syntax-only Mermaid errors
This error card is shown for every mermaid.parse failure and every render exception, but the hub only sends <render-issue> hints when extractFailingMermaidBlocks sees an unrecognized first token. For valid diagram types with bad syntax, e.g. graph TD\nA --, no hint is emitted, so the UI tells the user the agent was notified and will correct it when nothing triggered a correction; either notify for all failures or make this message generic.
Useful? React with 👍 / 👎.
| if (state === 'error') { | ||
| return <MermaidRenderError /> |
There was a problem hiding this comment.
Update Mermaid error tests for the new placeholder
This new error branch no longer renders .aui-mermaid-fallback or the raw source, but the existing web tests in web/src/components/assistant-ui/mermaid-diagram.test.tsx:72-99 still wait for that selector/text in both parse-failure and render-throw cases. The root bun run test script includes test:web, so this commit will fail CI once dependencies are installed unless those assertions are updated for the new placeholder.
Useful? React with 👍 / 👎.
| 'flowchart', | ||
| 'graph', | ||
| 'sequencediagram', | ||
| 'classdiagram', |
There was a problem hiding this comment.
Add classDiagram-v2 to the Mermaid whitelist
Mermaid accepts classDiagram-v2 as a class diagram declaration, but this whitelist only includes classdiagram. When an agent emits a valid classDiagram-v2 block, the browser can render it while the hub misclassifies it as an unrecognized type and injects a correction prompt, causing an unnecessary follow-up correction; include the v2 alias or delegate this check to Mermaid itself.
Useful? React with 👍 / 👎.
- Add classDiagram-v2 to known diagram types to prevent false-positive render-issue hints on a valid mermaid alias - Update error card secondary text to avoid overpromising agent notification for non-type-recognition failures (syntax errors) - Update web tests to assert on .aui-mermaid-render-error placeholder and absence of raw markup rather than the removed .aui-mermaid-fallback via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
|
Thanks for the review. Addressed in 202bef4:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 202bef4056
ℹ️ 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".
| 'mindmap', | ||
| 'timeline', | ||
| 'sankey-beta', | ||
| 'xychart-beta', |
There was a problem hiding this comment.
Accept Mermaid's non-beta chart tokens
Mermaid 11.14 accepts xychart as well as xychart-beta, and its treemap detector also accepts declarations such as treemap-beta; this whitelist only includes xychart-beta and treemap. In sessions where an agent emits either valid form, the web renderer can render the diagram but extractFailingMermaidBlocks will inject a <render-issue> correction prompt anyway, causing an unnecessary follow-up turn. Include the accepted aliases or delegate detection to Mermaid itself.
Useful? React with 👍 / 👎.
Summary
Closes / tracks upstream issue tiann#829.
Agents occasionally emit mermaid diagrams with unrecognised diagram types — typos
(
grph TD), hallucinated types, or stale syntax — that render as raw markup insteadof a diagram. Until now the only fix was to manually notice and ask the agent to
re-emit. This PR makes that automatic and deterministic.
What ships:
Hub — new
hub/src/sync/mermaid.tsextracts all\``mermaidfences from agent messages (Claudeoutput, Codexcodexformat, bareassistant), checks the first non-blank, non-directive token against the full mermaid v11.14 type list, and builds ahint for any unrecognised blocks.sessionHandlers.ts` calls this after every agent message and emits the hint as asynthetic user message back into the running CLI session.
CLI —
<render-issue>added toSYSTEM_INJECTION_PREFIXESso the hint isclassified as a system injection (not an external human turn) and routed to the agent
immediately.
Web —
MermaidDiagramcomponent gains a three-state render cycle(
pending→ silent pulsing skeleton,error→ amber warning card,success→ SVGwith lightbox). The amber card reads "Diagram rendering failed — The agent used an
unrecognised diagram type. It has been notified and will provide a corrected version
below." No raw markup is ever shown.
Hint prompt — mandatory imperative: "You MUST re-emit the corrected diagram now.
Do not explain, apologise, or comment — just output the fixed mermaid block with a
valid diagram type." Tested across Claude, Cursor, and Codex agents: all three
self-correct on the next turn without operator intervention.
Test plan
hub/src/sync/mermaid.test.ts— 20 unit tests covering all message formats,YAML frontmatter,
%%{init}directives, multi-block, truncation, hint structuregrph TDmermaid block in a live session — amber card shown immediately,<render-issue>hint fires on next turn, correctedgraph TDre-emittedgraph TD,flowchart LR, etc.) — no false positives, no hintFuture work
In-place replacement of the broken chart with the corrected one when the agent
re-emits. This PR delivers the detection + notification + placeholder; the
replacement is tracked separately.
🤖 Generated with HAPI — fork-internal review before upstream submission.