Repository navigation
Render Mermaid diagrams inline - #3679
Conversation
rumpl
commented
Jul 16, 2026
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🔴 CRITICAL
1 high-severity finding in new Mermaid rendering code.
Lower-confidence findings (not posted inline)
- [low] pkg/mermaid/parser.go:33 — Sequence diagrams with only notes (no messages) incorrectly rejected as ErrInvalidDiagram (confidence: weak 35/100)
- [low] pkg/mermaid/parser.go:69 — Escape check in splitMermaidStatements uses simple look-behind, mishandling double-escaped backslashes (confidence: negligible 25/100 — dropped)
- [low] pkg/mermaid/parser.go:144 — readDelimited byte-level escape check can misfire on multi-byte UTF-8 content (confidence: negligible 25/100 — dropped)
- [low] pkg/tui/components/mermaid/render_sequence.go:115 — Self-message label may overflow canvas width silently (confidence: negligible 20/100 — dropped)
- [low] pkg/tui/components/mermaid/render_graph.go:14 — append(roots, order...) may mutate roots backing array if it has spare capacity (confidence: negligible 20/100 — dropped)
| func writeMermaidCanvas(canvas []rune, start int, value string) { | ||
| for _, r := range value { | ||
| if start >= 0 && start < len(canvas) { | ||
| canvas[start] = r | ||
| } | ||
| start++ | ||
| } | ||
| } |
There was a problem hiding this comment.
[high] writeMermaidCanvas skips a column for every wide Unicode character, corrupting diagram layout
The loop advances start by 1 for every rune, but double-width characters (CJK, emoji) occupy 2 terminal columns. After writing a wide rune to canvas[start], the next character is placed at start+1 — the second visual cell of the wide character — causing characters to visually overlap and misalign.
The rest of the file correctly uses runewidth.StringWidth() and runewidth.RuneWidth() for width accounting; this function is the odd one out. The fix is to advance by the rune's display width and, for the two-column case, also blank out the adjacent cell so it is not filled with stale content:
| func writeMermaidCanvas(canvas []rune, start int, value string) { | |
| for _, r := range value { | |
| if start >= 0 && start < len(canvas) { | |
| canvas[start] = r | |
| } | |
| start++ | |
| } | |
| } | |
| func writeMermaidCanvas(canvas []rune, start int, value string) { | |
| for _, r := range value { | |
| if start >= 0 && start < len(canvas) { | |
| canvas[start] = r | |
| } | |
| rw := runewidth.RuneWidth(r) | |
| if rw == 2 && start+1 >= 0 && start+1 < len(canvas) { | |
| canvas[start+1] = ' ' | |
| } | |
| start += rw | |
| } | |
| } |
| Confidence | Score |
|---|---|
| 🟡 moderate | 67/100 |
There was a problem hiding this comment.
Fixed in aba4fe7. I changed the canvas from []rune to display-cell-aware []string storage: wide runes reserve continuation cells that serialize to zero additional width, and zero-width combining runes are appended to the preceding cell. This avoids the extra terminal column that a literal space in the continuation cell would introduce. Added regression coverage for CJK and combining characters in render_primitives_test.go.
There was a problem hiding this comment.
Excellent fix! The cell-aware storage approach is much cleaner than my suggestion. Storing wide-rune continuation cells as empty strings and appending zero-width combining characters to the preceding cell correctly handles both categories of non-trivial Unicode without introducing phantom width. The test coverage for both CJK and combining characters confirms it works as designed.
Thank you for the thorough implementation!
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
c20c17b to
aba4fe7
Compare
|
Review follow-up in
Validation passes with |
- Add Mermaid diagram section under Thinking and Tool Details documenting which diagram types are rendered inline and the fallback behavior for unsupported types (PR #3679) - Update the Lean TUI section to list supported slash commands and explicitly note that /model is available (PR #3674) - Update Runtime Model Switching to clarify that /model works in both the full TUI and the lean TUI, while Ctrl+M is full TUI only
docker-agent 1.111.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>This release adds a new scheduler toolset, inline Mermaid diagram rendering, model switching in the lean TUI, project config autodiscovery, and a range of bug fixes and performance improvements across the agent, TUI, and server. ## What's New - Adds a built-in `scheduler` toolset with `create_schedule`, `list_schedules`, and `cancel_schedule` tools, enabling agents to schedule instructions to run once at a time, after a delay, or on a recurring interval - Adds prompt cache miss warnings via an opt-in `warn_on_cache_miss` user setting that emits notifications when cached input tokens are absent after the first session response - Adds inline Mermaid diagram rendering in the TUI - Adds model switching (`/model`) to the lean TUI and shares fuzzy model search between the lean and full TUIs - Adds autodiscovery of `docker-agent.yaml`, `docker-agent.yml`, and `docker-agent.hcl` project config files for no-argument local `docker agent run` - Surfaces background-job elicitation (user-input) requests in the TUI, ensuring each request opens exactly one dialog regardless of which runtime delivers it - Makes a custom `base_url` on a model automatically imply `bypass_models_gateway: true`, removing the need to set it explicitly ## Improvements - Improves session cost details in the TUI: aligns layout, color-codes cost percentages per message, groups averages together, and puts "Total" on its own line - Speeds up model switcher credential discovery by deduplicating credential names and resolving lookups concurrently, reducing model-picker latency - Parallelizes toolset startup and prefetches the MCP catalog off the load critical path, reducing agent startup time ## Bug Fixes - Fixes glob wildcard matching in permissions so `*` and `?` span path separators (`/`) in argument values such as file paths and URLs - Fixes a bug where setting `defer_all: true` on a toolset caused the toolset's custom `Instructions` to be permanently dropped from the agent's context - Fixes `SafetyPolicy` not persisting across turns, causing users to be re-prompted for tool approvals they had already opted into - Fixes background elicitations over the API so requests from concurrent background jobs are replayable and answerable instead of being auto-declined - Fixes Anthropic cache breakpoints exceeding the hard limit of 4 when deferred tools are present, which caused API errors at runtime - Fixes the TUI editor not regaining focus after external editing (`Ctrl+G`), which caused Enter to route to the transcript instead of sending the composed message - Fixes the scheduler schema so `type: scheduler` is accepted in agent config files (was previously rejected by JSON schema validation) - Fixes foreground elicitations being delivered twice into the app event stream - Fixes a mutex unlock in the scheduler's `setRuntime` method to use `defer` for safer lock release - Fixes VCR cassette path normalization for portable prompt-file matching - Fixes CI workflow YAML indentation that caused all CI runs to fail before any job could start ## Technical Changes - Adds `MapSlice` fan-out helper in the concurrent utilities package and uses it for parallel toolset operations - Serializes interactive OAuth flows across toolsets to prevent concurrent conflicts - Brings the documentation portal to WCAG 2.1 AA conformance across light and dark themes and adds a pa11y-ci CI gate to maintain it - Adds new documentation pages for the scheduler toolset, sandbox templates, custom commands, user settings, context and compaction guide, and fixes stale command syntax in the Named Commands docs --- ## What's Changed * docs: update CHANGELOG.md for v1.110.0 by @docker-read-write[bot] in docker/docker-agent#3665 * fix: repair CI workflow YAML and failing AgentsMd e2e test on main by @dgageot in docker/docker-agent#3666 * feat(tools): add "scheduler" toolset to run instructions on a time or recurring interval by @dwin-gharibi in docker/docker-agent#3632 * fix(config): add scheduler toolset to agent-schema.json type enum by @aheritier in docker/docker-agent#3670 * feat: custom base_url implies bypass_models_gateway by @dgageot in docker/docker-agent#3667 * feat: add prompt cache miss warnings by @rumpl in docker/docker-agent#3671 * Improve session cost details by @rumpl in docker/docker-agent#3672 * perf: speed up model switcher credential discovery by @rumpl in docker/docker-agent#3673 * Add model switching to the lean TUI by @rumpl in docker/docker-agent#3674 * feat(tui,app): surface & correlate background-job elicitations (#3584) by @aheritier in docker/docker-agent#3624 * chore: bump go-isatty, openai-go, and libopenapi by @dgageot in docker/docker-agent#3675 * ci: bump golangci-lint-action to v9.3.0 and fix DeferMutexUnlock lint offense by @dgageot in docker/docker-agent#3676 * ci: bump pinned actions to latest same-major versions by @dgageot in docker/docker-agent#3677 * feat(server): session-scoped elicitation sink for API/server runtimes (#3584) by @aheritier in docker/docker-agent#3625 * Render Mermaid diagrams inline by @rumpl in docker/docker-agent#3679 * fix(server): surface background elicitations over API by @Sayt-0 in docker/docker-agent#3678 * fix(teamloader): preserve deferred toolset instructions by @Piyush0049 in docker/docker-agent#3680 * fix(permissions): match glob wildcards across path separators by @parveshsaini in docker/docker-agent#3605 * docs(agents): fix stale command syntax and sub_agents claim in Named Commands by @aheritier in docker/docker-agent#3684 * docs(sandbox): document Docker Sandboxes integration and published sbx templates by @aheritier in docker/docker-agent#3682 * docs: surface orphan pages and add compaction, commands, and user-settings docs by @aheritier in docker/docker-agent#3683 * fix(docs): WCAG 2.1 AA accessibility remediation for the docs portal by @aheritier in docker/docker-agent#3687 * fix(tui): refocus the editor after external editing so Enter sends the content by @dgageot in docker/docker-agent#3690 * Implements the path-aware, two-tier documentation accessibility CI gate by @aheritier in docker/docker-agent#3691 * feat(run): autodiscover project agent config by @HajimohammadiNet in docker/docker-agent#3681 * perf: parallelize toolset startup and prefetch MCP catalog by @dgageot in docker/docker-agent#3688 * fix(anthropic): keep cache breakpoints within Anthropic's limit of 4 with deferred tools by @dgageot in docker/docker-agent#3692 * fix(docs): render --link-hover on light-mode callout-link hover by @aheritier in docker/docker-agent#3693 * build(deps): bump the pip group across 1 directory with 2 updates by @dependabot[bot] in docker/docker-agent#3697 * fix(session): persist SafetyPolicy across turns by @trungutt in docker/docker-agent#3685 ## New Contributors * @parveshsaini made their first contribution in docker/docker-agent#3605 * @HajimohammadiNet made their first contribution in docker/docker-agent#3681 **Full Changelog**: docker/docker-agent@v1.110.0...v1.111.0 </pre> <p>View the full release notes at <a href="https://github.com/docker/docker-agent/releases/tag/v1.111.0">https://github.com/docker/docker-agent/releases/tag/v1.111.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!14465