Skip to content

fix: prevent participant box overlap in sequence diagrams#5

Merged
fasouto merged 4 commits into
fasouto:mainfrom
searleser97:fix/sequence-participant-overlap
Jun 3, 2026
Merged

fix: prevent participant box overlap in sequence diagrams#5
fasouto merged 4 commits into
fasouto:mainfrom
searleser97:fix/sequence-participant-overlap

Conversation

@searleser97

@searleser97 searleser97 commented May 13, 2026

Copy link
Copy Markdown
Contributor

Problem

When sequence diagrams have many participants with long names but few direct messages between adjacent pairs, participant boxes overlap in the header row.

Example input:

sequenceDiagram
    participant A as AuthenticationService
    participant B as UserSessionManager
    participant C as TokenValidationMiddleware
    participant D as PermissionsEvaluator
    participant E as AuditLogService
    participant F as NotificationDispatcher
    A->>B: createSession(credentials)
    B->>C: validateToken()
Loading

Why --width N doesn't help

The --width flag and auto-fit logic only compact a diagram (reduce gap/padding) when it exceeds the target width. They don't expand the initial layout. The overlap occurs because _MIN_GAP = 16 (the floor for gap between participant centers) is smaller than the combined half-widths of adjacent boxes. This means boxes overlap in the initial layout calculation, before any compaction is applied.

Tested with --width 500 --no-auto-fit — the overlap persists because the layout algorithm never ensures gaps are wide enough for the actual box sizes.

Fix

1. Prevent participant box overlap (commit dfef9be)

Added a constraint in _compute_layout() that ensures the gap between adjacent participant centers is at least the sum of their half-box-widths plus 2 characters of spacing.

# Ensure gaps are wide enough that participant boxes don't overlap
for i in range(n - 1):
    box_gap_need = (box_widths[i] + box_widths[i + 1]) // 2 + 2
    gap_mins[i] = max(gap_mins[i], box_gap_need)

2. Remove unnecessary minimum box size constraints (commit dd7f546)

  • sequence.py: Removed hardcoded minimum box width of 12. Box width is now exactly label + padding_x + 2 (borders), so boxes are only as wide as needed.
  • placement.py: Lowered minimum content_height from 3 to 1. When --padding-y 0 is passed, flowchart nodes no longer have extra blank lines above/below the label.

Users can now get tight boxes with --padding-x 2 --padding-y 0. Default behavior (padding_x=4, padding_y=2) is unchanged.

3. Expand canvas width for 'over' notes on rightmost participants (commit 1b25b6b)

Notes with position == "over" on the rightmost participant (or spanning to it) were not accounted for in the canvas width calculation. This caused the right border of such note boxes to be clipped. The fix adds width calculation for "over" notes in max_right.

if ev.position == "over":
    lines = _note_lines(ev)
    note_width = max(display_width(line) for line in lines) + 4
    if len(ev.participants) == 2:
        ...
        needed = note_x + note_width + 1
        max_right = max(max_right, needed)
    elif len(ev.participants) == 1:
        ...
        needed = note_x + note_width + 1
        max_right = max(max_right, needed)

Visual comparison — Sequence diagram

Before (default --padding-x 4, min box width 12):

 ┌─────────┐      ┌───────┐
 │  Alice  │      │  Bob  │
 └─────────┘      └───────┘
      ┆ Hello Bob     ┆
      ────────────────►
      ┆ Hi Alice      ┆
      ◄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
      ┆               ┆

After (--padding-x 2, no min width floor):

 ┌───────┐        ┌─────┐
 │ Alice │        │ Bob │
 └───────┘        └─────┘
     ┆ Hello Bob     ┆
     ────────────────►
     ┆ Hi Alice      ┆
     ◄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
     ┆               ┆

Visual comparison — Flowchart

Before (default --padding-x 4 --padding-y 2):

┌────────────┐
│            │
│   Start    │
│            │
└──────┬─────┘
       │
       ▼
┌──────◇─────┐
│            │
│  Decision  │
│            │
└──────◇─────┘
       │
       ├─────────────────╮No
    Yes│                 │
       ▼                 ▼
┌────────────┐    ┌────────────┐
│            │    │            │
│  Process   │    │    End     │
│            │    │            │
└────────────┘    └────────────┘

After (--padding-x 2 --padding-y 0):

┌──────────┐
│  Start   │
└─────┬────┘
      │
      ▼
┌─────◇────┐
│ Decision │
└─────◇────┘
      │
      ├───────────────╮No
   Yes│               │
      ▼               ▼
┌──────────┐    ┌──────────┐
│ Process  │    │   End    │
└──────────┘    └──────────┘

Tests

All existing sequence diagram tests pass. The snapshot failures are pre-existing (statediagram) and unrelated to these changes.

searleser97 and others added 2 commits May 13, 2026 10:28
When sequence diagrams have many participants with long names but few
direct messages between adjacent ones, the gap between participant
centers could be smaller than the combined half-widths of adjacent
boxes, causing visual overlap in the header.

This fix ensures the minimum gap between adjacent participant centers
accounts for the actual box widths, preventing overlapping labels.

Co-authored-by: Copilot <[email protected]>
- sequence.py: Remove hardcoded min width of 12; box width is now
  exactly label + padding_x + 2 (borders)
- placement.py: Lower min content_height from 3 to 1 so padding_y=0
  produces boxes with no extra vertical space

Users can now get tight boxes with --padding-x 2 --padding-y 0.
Default behavior (padding_x=4, padding_y=2) is unchanged.

Co-authored-by: Copilot <[email protected]>
@searleser97

Copy link
Copy Markdown
Contributor Author

@fasouto please take a look at this PR

Notes with position 'over' on the rightmost participant (or spanning
to it) were not accounted for in the canvas width calculation, causing
their right border to be clipped.

Co-authored-by: Copilot <[email protected]>
@searleser97

Copy link
Copy Markdown
Contributor Author

@fasouto , friendly ping...

@fasouto

fasouto commented May 19, 2026 via email

Copy link
Copy Markdown
Owner

The tighter participant boxes shrink the canvas, which exposed three
layout regressions that the wider min-width-12 boxes had masked:

- "over" notes on a left-positioned participant clipped on the right,
  because the layout reserved width from the unclamped note_x while the
  drawing clamps it to >= 0. Mirror the clamp in the width calc.
- Nested block frames (loop/alt/opt) clipped on the right, because the
  canvas-width loop never accounted for block frames. Reserve their
  right edge via _block_frame_bounds.
- Nested block frames lost their left indent (inner frames collapsed
  onto column 0), because the per-depth indent was inside the max(0, ..)
  clamp. Clamp only the depth-0 base, then add the indent.

Regenerate the sequence snapshots: 7 reflect the intended tighter boxes,
nested_blocks reflects the restored, fully-closed frames.
@fasouto

fasouto commented Jun 3, 2026

Copy link
Copy Markdown
Owner

(back from holidays, thanks for your patience)

I checked it and the overlap fix is an improvement, and sizing the participant boxes to their labels actually matches how Mermaid renders them, so I'm happy to move it in that direction. Aesthetically I like them all with the same size, but it's less practical so we will do that.

I went ahead and pushed a follow-up commit (8d910ed) instead of sending it back and forth. Two things to flag:

  • Tests: main is fully green for me (1101 passed). The 8 failures on this branch were all sequence snapshots (not statediagram), caused by the box-width change
  • Three small regressions the tighter boxes exposed (the old min-width-12 boxes had been hiding them), all fixed in the follow-up:
  1. Note over on a left-positioned actor clipped on the right (the layout reserved width from the unclamped note_x, but _draw_note clamps it to >= 0).
  2. Nested loop/alt/opt block frames clipped on the right (the canvas-width calc never accounted for block frames).
  3. Nested block frames lost their left indent and collapsed onto column 0 (the per-depth indent was inside the max(0, …) clamp).

Snapshots are regenerated and the full suite is green. Thanks for your PR @searleser97 !!

@fasouto fasouto merged commit 2625514 into fasouto:main Jun 3, 2026
3 checks passed
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.

2 participants