Skip to content

feat(hub+cli+web): mermaid parse-failure feedback — detect broken diagrams, notify agent, show error placeholder#71

Open
heavygee wants to merge 9 commits into
mainfrom
feat/mermaid-parse-failure-feedback
Open

feat(hub+cli+web): mermaid parse-failure feedback — detect broken diagrams, notify agent, show error placeholder#71
heavygee wants to merge 9 commits into
mainfrom
feat/mermaid-parse-failure-feedback

Conversation

@heavygee

Copy link
Copy Markdown
Owner

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 instead
of 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.ts extracts all \``mermaidfences from agent messages (Claudeoutput, Codex codexformat, bareassistant), checks the first non-blank, non-directive token against the full mermaid v11.14 type list, and builds a hint for any unrecognised blocks.sessionHandlers.ts` calls this after every agent message and emits the hint as a
    synthetic user message back into the running CLI session.

  • CLI<render-issue> added to SYSTEM_INJECTION_PREFIXES so the hint is
    classified as a system injection (not an external human turn) and routed to the agent
    immediately.

  • WebMermaidDiagram component gains a three-state render cycle
    (pending → silent pulsing skeleton, error → amber warning card, success → SVG
    with 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 structure
  • Emit grph TD mermaid block in a live session — amber card shown immediately,
    <render-issue> hint fires on next turn, corrected graph TD re-emitted
  • Valid diagrams (graph TD, flowchart LR, etc.) — no false positives, no hint
  • Pending state — no raw code flash while async parse runs
  • Verified across three agent backends: Claude, Cursor (Codex CLI), Codex

Future 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.

heavygee and others added 8 commits June 28, 2026 12:06
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread cli/src/api/apiSession.ts
'<command-name>',
'<local-command-caveat>',
'<system-reminder>',
'<render-issue>',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +98 to +99
The agent used an unrecognised diagram type. It has been notified and
will provide a corrected version below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +163 to +164
if (state === 'error') {
return <MermaidRenderError />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread hub/src/sync/mermaid.ts
'flowchart',
'graph',
'sequencediagram',
'classdiagram',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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>
@heavygee

Copy link
Copy Markdown
Owner Author

Thanks for the review. Addressed in 202bef4:

  • P1 ✅ Updated mermaid-diagram.test.tsx — both error-state tests now assert on .aui-mermaid-render-error + absence of raw markup (the .aui-mermaid-fallback selector is gone). 3/3 web tests pass.
  • P2 ✅ Added classdiagram-v2 to KNOWN_DIAGRAM_TYPES in mermaid.ts — valid classDiagram-v2 blocks no longer trigger false-positive hints.
  • P2 ✅ Error card secondary text changed from "The agent used an unrecognised diagram type. It has been notified…" to the honest "The diagram could not be rendered. A corrected version may follow below." — avoids overpromising on syntax errors where no hint fires.
  • P2 (deferred) Chat-history echo concern noted for future work — the SYSTEM_INJECTION_PREFIXES path in apiSession.ts is the existing pattern used by other system injections; a fuller fix to prevent echoing through normalizeAgent.ts is out of scope for this PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread hub/src/sync/mermaid.ts
'mindmap',
'timeline',
'sankey-beta',
'xychart-beta',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant