feat: Add multi-line labels and width control with auto-fitting - #47
feat: Add multi-line labels and width control with auto-fitting#47warnes wants to merge 34 commits into
Conversation
- Fix off-by-one panic in renderNoteRightOf - Fix renderNoteLeftOf lifeline zig-zag issue - Fix renderNoteOver to handle long text properly - Add edge case tests
- Fix nesting indent direction (inward instead of outward) - Fix empty blocks to render a visible box - Fix block width to account for content (messages/notes) - Add tests for edge cases
- loop: rounded corners (╭╮╰╯) - alt/opt: dotted borders (┄┆) - par: double vertical sides (║) with mixed corners - critical/break: full double lines (═║╔╗╚╝) - rect: standard box (unchanged)
Critical: - Refactor parseBlock to eliminate sd.Elements mutation hack - Clarify block width calculation (rename to calculateBlockRightEdge) High: - Support quoted actor identifiers in parseNote - Validate divider placement (reject dividers as first content) - Use messageSpacing inside blocks for consistent rhythm Medium: - Fix quadratic buildLine performance (track width incrementally) - Consistent error line numbers in parseBlock
Map labeled node IDs to a single display label so edges referencing the ID don't create duplicate nodes. Normalize labeled subgraph names and add regression tests for node and subgraph aliases.
Introduce MaxWidth and FitPolicy with validation to\nsupport width-aware rendering behavior.
Store graph direction on layout instances and honor\nconfig overrides while defaulting to the diagram\nheader when unset.
- Support <br/> and <br> HTML tags for multi-line node labels in graph diagrams - Add -w/--maxWidth CLI flag for diagram width control - Implement comprehensive test coverage (11 tests, all passing) - Add project-level copilot-instructions.md with Go development standards - Update .gitignore for build artifacts and backup files Technical details: - Modified cmd/graph.go to convert <br/> tags to newlines before label splitting - Extended cmd/root.go to add maxWidth flag parameter - Updated internal/diagram/config.go to pass maxWidth through NewCLIConfig - Added cmd/graph_br_tag_test.go with 7 table-driven tests for tag conversion - Added internal/diagram/config_maxwidth_test.go with 4 tests for width parameter All tests pass successfully with proper validation of features
- Add <br/> and <br> tag support for multi-line node labels - Add -w/--maxWidth CLI flag to constrain diagram width - Auto-enable fitting when width specified (no separate flag needed) - Add --fit flag to force fitting without width constraint - Fix test expectations for text centering alignment - Add TODO for multi-row wrapping feature with visual examples - Update README with usage examples and feature documentation - Add copyright notices for contributions
- Replace byte-based len() with utf8.RuneCountInString() for character counting - Use []rune slicing instead of byte indexing for proper multi-byte character handling - Fix text wrapping to correctly handle UTF-8 characters (box-drawing, CJK, emoji) - Fix label rendering in drawBox to use rune-based indexing - Add comprehensive UTF-8 test suite with box-drawing, Japanese, and emoji characters This fixes corruption of Unicode characters like ├─> and └─> that was causing them to display as ââ> instead of rendering correctly. Resolves the issue where multi-byte UTF-8 characters were being corrupted during text wrapping and rendering operations.
- Add go-runewidth dependency for proper CJK/emoji width calculation - Disable centering for multi-line labels to preserve tree structure alignment - Fix text wrapping to account for display width (CJK chars are 2 columns wide) - Update maxLineWidth to use display width instead of character count - Separate grid position (characters) from display width (visual columns) - Add comprehensive test for multi-line nodes with UTF-8 tree structures This ensures that: 1. Tree diagrams with ├─> and └─> maintain proper left alignment 2. CJK characters (日本語) display without extra spaces 3. Emoji render correctly 4. Multi-line labels don't break structured content
- Add --center-multi-line-labels flag for controlling label alignment - Add --version flag to display version information - Enhance UTF-8 support for wide characters (CJK, emoji) in labels - Fix UTF-8 test expected output for proper wide character rendering - Update README with Features section and usage examples - Update CLI flags documentation with new options
AlexanderGrooff
left a comment
There was a problem hiding this comment.
Hi there, thanks for your contribution. I appreciate the effort, but there's quite some things to be improved still. Please have a look at the comments, I stopped reviewing at some point because these points can be applied across your proposed change.
| MIT License | ||
|
|
||
| Copyright (c) 2023 Alexander Grooff | ||
| Copyright (c) 2026 Gregory R. Warnes |
| │ A │ │ B │ | ||
| └─┬─┘ └─┬─┘ | ||
| │ │ | ||
| ┌┄┴┄┄┄┄┄┄┄┄┄┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ |
There was a problem hiding this comment.
This isn't how I expect this to show. This example on mermaid.live shows that the B node gets moved to the other side of the block:
There was a problem hiding this comment.
Please add test cases covering the behavioural changes you're introducing:
- multi-lines examples
- edge cases
- wrapping around max width
- all these examples with multiple nodes, different tree sizes etc
| --- | ||
| +-----+ +---------+ | ||
| | | | | | ||
| | ABC |---->| BCDEFG | |
There was a problem hiding this comment.
Please remove these AI artifacts
There was a problem hiding this comment.
Please remove this AI artifact
| ) | ||
|
|
||
| // Version of mermaid-ascii | ||
| const Version = "1.0.0-fccdata" |
| @@ -1,3 +1,7 @@ | |||
| // Copyright (c) 2023 Alexander Grooff | |||
| // Copyright (c) 2026 Gregory R. Warnes | |||
| rootCmd.PersistentFlags().IntVarP(&boxBorderPadding, "borderPadding", "p", boxBorderPadding, "Padding between text and border") | ||
| rootCmd.PersistentFlags().IntVarP(&maxWidth, "maxWidth", "w", maxWidth, "Maximum diagram width in characters (0 = unlimited)") | ||
| rootCmd.PersistentFlags().BoolVar(&fitDiagram, "fit", false, "Force automatic fitting even without width constraint") | ||
| rootCmd.PersistentFlags().BoolVar(¢erMultiLineLabels, "center-multi-line-labels", false, "Center multi-line node labels as a block") |
| b["日本語 🎉"] | ||
| a --> b` | ||
|
|
||
| expected := `+------------+ |
There was a problem hiding this comment.
move these tests to the cmd/testdata/ascii dir
|
Thanks for your response. I'll work on this later this week.
Gregory R. Warnes, Ph.D.
***@***.***
Eternity is a long time, take a friend!
… On Jan 25, 2026, at 5:49 PM, Alexander Grooff ***@***.***> wrote:
@AlexanderGrooff requested changes on this pull request.
Hi there, thanks for your contribution. I appreciate the effort, but there's quite some things to be improved still. Please have a look at the comments, I stopped reviewing at some point because these points can be applied across your proposed change.
In LICENSE <#47 (comment)>:
> @@ -1,6 +1,7 @@
MIT License
Copyright (c) 2023 Alexander Grooff
+Copyright (c) 2026 Gregory R. Warnes
Please remove this
In cmd/testdata/sequence/block_extra_long_message.txt <#47 (comment)>:
> @@ -0,0 +1,20 @@
+sequenceDiagram
+ participant A
+ participant B
+ alt Success scenario with an extremely long conditional label that needs lots of space
+ A->>B: This message has an extraordinarily long label that should cause the block to expand significantly beyond the normal participant width
+ end
+---
+┌───┐ ┌───┐
+│ A │ │ B │
+└─┬─┘ └─┬─┘
+ │ │
+┌┄┴┄┄┄┄┄┄┄┄┄┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
This isn't how I expect this to show. This example on mermaid.live <https://mermaid.live/edit#pako:eNplUsuO2zAM_BWCZzewHTt-HBbYba89bU-FL4zE2MLKUqoHdtMg_14maYoFypM0GM4MQZ5Rec04YuRfmZ3ib4bmQOvkQOpIIRlljuQSPP8Pvdwhsgles1IcI0TFjoLx8G7SAuSAP1Lgle0JrHczKO-0ScY7smBpzxbSQgkcs47CSBH8AeKRFN-1r_X85enpZYQfi4mwignNDAvFhzr5oM3V9OHxSTcuPlsNinJkQRj21qs3SF46ZQQN0czOHIyScaR9zyfJdyM6H1bJ-Hncd6PTco_FTmOBczAaxxQyF7iy8K9fPF8pE4rIyhOO8tQU3iac3EV6ROmn9-ujLfg8LzgeyEb55aOm9FjBP4qYcfjqs0s49rubBI5n_MCxroZNV1VD1--qttruyrbAk8Bdt6mHbbNr2mZbd8NwKfD3zbTc9F1blmXdN91Qt33ZFMiyEB--38_gdg0FUk7-9eTU32SXP4tatpg> shows that the B node gets moved to the other side of the block:
image.png (view on web) <https://github.com/user-attachments/assets/1df84863-b53b-4ad3-afbd-db141ce5e07a>
On cmd/testdata/sequence/block_long_message.txt <#47 (comment)>:
Please add test cases covering the behavioural changes you're introducing:
multi-lines examples
edge cases
wrapping around max width
all these examples with multiple nodes, different tree sizes etc
In cmd/testdata/ascii/two_layer_single_graph_longer_names.txt <#47 (comment)>:
> @@ -4,7 +4,7 @@ ABC --> CDEFGHI
---
+-----+ +---------+
| | | |
-| ABC |---->| BCDEFG |
Why does this change?
On docs/plans/2026-01-20-block-syntax-design.md <#47 (comment)>:
Please remove these AI artifacts
On .github/copilot-instructions.md <#47 (comment)>:
Please remove this AI artifact
In cmd/root.go <#47 (comment)>:
> @@ -10,20 +14,33 @@ import (
"github.com/spf13/cobra"
)
+// Version of mermaid-ascii
+const Version = "1.0.0-fccdata"
Please remove this
In cmd/root.go <#47 (comment)>:
> @@ -1,3 +1,7 @@
+// Copyright (c) 2023 Alexander Grooff
+// Copyright (c) 2026 Gregory R. Warnes
Remove these copyrights
In cmd/root.go <#47 (comment)>:
> rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose output")
rootCmd.PersistentFlags().BoolVarP(&useAscii, "ascii", "a", false, "Don't use extended character set")
rootCmd.PersistentFlags().BoolVarP(&Coords, "coords", "c", false, "Show coordinates")
rootCmd.PersistentFlags().IntVarP(&paddingBetweenX, "paddingX", "x", paddingBetweenX, "Horizontal space between nodes")
rootCmd.PersistentFlags().IntVarP(&paddingBetweenY, "paddingY", "y", paddingBetweenY, "Vertical space between nodes")
rootCmd.PersistentFlags().IntVarP(&boxBorderPadding, "borderPadding", "p", boxBorderPadding, "Padding between text and border")
+ rootCmd.PersistentFlags().IntVarP(&maxWidth, "maxWidth", "w", maxWidth, "Maximum diagram width in characters (0 = unlimited)")
+ rootCmd.PersistentFlags().BoolVar(&fitDiagram, "fit", false, "Force automatic fitting even without width constraint")
+ rootCmd.PersistentFlags().BoolVar(¢erMultiLineLabels, "center-multi-line-labels", false, "Center multi-line node labels as a block")
Why a separate flag?
In cmd/utf8_test.go <#47 (comment)>:
> +
+import (
+ "testing"
+
+ "github.com/AlexanderGrooff/mermaid-ascii/internal/diagram"
+)
+
+// TestUTF8MultiLineNode tests that multi-line nodes with UTF-8 characters
+// render properly without content being split across multiple boxes.
+func TestUTF8MultiLineNode(t *testing.T) {
+ input := `flowchart TD
+ a["┌─ TIMER<br/>├─> Step 1<br/>└─> Step 2"]
+ b["日本語 🎉"]
+ a --> b`
+
+ expected := `+------------+
move these tests to the cmd/testdata/ascii dir
—
Reply to this email directly, view it on GitHub <#47 (review)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABO4GX4YFMKQG2ELS4NH5Q34IVCA3AVCNFSM6AAAAACSXVY64KVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTOMBUGI4TINBUGQ>.
You are receiving this because you authored the thread.
|
|
I think multiline label is very critical for everyday usage.. 👍🏼 |
Implements escalating fit strategies when a max width is specified: base → compact → tight → label wrap → direction flip → edge label ellipsis → edge label drop. Each strategy is tried and the first output that fits within the width constraint is returned; if none fit, the narrowest output is used. Adapted from Warnes-Innovations/mermaid-ascii PR AlexanderGrooff#47, cleaned up and ported to the current label model (PR AlexanderGrooff#58).
The shared config repo was renamed vscode-config → agent-config and the compat symlink removed; update stale references accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
This PR adds two new features to mermaid-ascii:
<br/>and<br>HTML tags-w/--maxWidthCLI flag with automatic fittingChanges
Multi-line Label Support
<br/>or<br>tags to create multi-line labelsA["First<br/>Second"]renders as two lines<br/>) and non-closing (<br>) tags are supportedWidth Control
-w/--maxWidthflag constrains diagram width in characters--fitflag for forcing fitting without a width constraintAdditional Improvements
Usage Examples
Multi-line Labels
Width Control
Testing
Breaking Changes
None - all changes are backward compatible.
Future Work
Added TODO for multi-row wrapping feature that would wrap long linear chains into multiple rows while preserving LR direction (see
cmd/fit_graph.golines 100-130 for visual examples).