Skip to content

fix: preserve Mermaid edge curves on drag and warn on unbalanced artifact HTML#1

Merged
auspham merged 3 commits into
mainfrom
austinpham/mermaid-curve-and-html-validation
Jul 8, 2026
Merged

fix: preserve Mermaid edge curves on drag and warn on unbalanced artifact HTML#1
auspham merged 3 commits into
mainfrom
austinpham/mermaid-curve-and-html-validation

Conversation

@auspham

@auspham auspham commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes to the review surface, found while reviewing real plan/design artifacts.

1. Mermaid rearrange geometry (client/diagram.js). Several related defects in how a diagram renders and rebuilds, all visible once a diagram has parallel edges, curved edges, or a dense hub of labelled edges:

  • Curves collapsed. Dragging a node redrew each connected edge as a straight line, flattening curves and stacking parallel edges between the same two nodes onto one identical segment. The rebuild now maps each edge's original path onto the moved endpoints with a similarity transform, so curves stay curved and parallel edges keep their separation.
  • Arrowheads stacked. Endpoints were re-derived from node centres, so the fanned starts and arrowheads of parallel edges collapsed onto a single toward-centre point. Each endpoint is now pinned to its node's original attach point and moved by that node's drag delta.
  • Bending jumped the arrowhead. Dragging a label to bend its edge used the same collapsed endpoints, so the arrowhead jumped to the shared point. The bend branch now uses the pinned endpoints and bows only the middle toward the cursor.
  • Labels bound to the wrong edge. The edge-label to edge match compared positions in mismatched coordinate frames, assigned greedily in DOM order, and measured to each edge's arc-midpoint (which on a curved edge sits nowhere near the label) - so a label could bind to the wrong edge and a node drag then flung it across the diagram onto an unrelated arrow. Both positions are now lifted into the common frame and each label is paired to the edge it lies closest to (minimum distance along the whole path), globally-closest-first.
  • Labels sat off their edge until dragged. Mermaid drops a label a little off a curved edge, and htmlit only snapped it on when the edge was dragged. Labels are now snapped onto their edge on a fresh render (a saved arrangement still wins), so they sit on their arrow from the start.

2. Warn the agent when artifact HTML has unbalanced block tags (htmlit_core/validation.py). htmlit serves the artifact verbatim, so a stray or unclosed <div>/<section> collapses the layout (a container closes early and following sections escape the centring wrapper, rendering full-width). The browser silently repairs the DOM, so the client cannot see it. A stdlib structure check re-reads the raw file whenever the client reports layout and folds any imbalance into layout_warnings as an unbalanced-html warning, naming each stray/unclosed tag with its line. It tracks only containers that require an explicit end tag, so it never false-positives on optional-end tags (<p>, <li>, <td>). No silent auto-repair: htmlit reports the problem so the fix lands in the author's source.

3. Make the answer/anchor jump two-way (client/annotate.js). Clicking a comment anchor already jumped to its agent answer, but there was no way back. A plain click on an answer block's "You asked:" header now scrolls to and flashes the anchored spot. The click is scoped to the header/block itself, not the answer body, so the answer text stays selectable and commentable; the header gets a pointer cursor and hover underline.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor or tooling
  • Other:

How was this tested?

Every change is TDD: each new test fails before its fix and passes after (verified by reverting each change and re-running its test).

  • python -m pytest tests/python (45 passed, incl. new test_validation.py + server integration tests)
  • ruff check skills/htmlit tests/python (clean)
  • npm test (browser client, 19 passed, incl. parallel-edge curve/arrowhead, label-bend pinning, dense-diagram label matching across straight and curved edges, labels-on-line-at-load, answer-anchor jump, and the existing subgraph tests)

Checklist

  • I added or updated tests for this change (a bug fix has a test that fails before the fix).
  • I did not add any runtime dependency to the skill (standard library only).
  • I did not commit vendored assets (Mermaid, highlight.js, Idiomorph).
  • Commits follow Conventional Commits and are signed off (git commit -s).

@auspham auspham force-pushed the austinpham/mermaid-curve-and-html-validation branch 5 times, most recently from 0ab4f07 to fa82a9f Compare July 8, 2026 07:21
auspham added 3 commits July 8, 2026 17:29
…on drag

The Mermaid rearrange rebuild had several related defects, all visible once a
diagram had parallel edges, curved edges, or a dense hub of labelled edges:

- Dragging a node redrew each connected edge as a straight line, flattening curves
  and stacking every parallel edge between the same two nodes onto one identical
  segment. rebuildEdge now maps each edge's original path onto the moved endpoints
  with a similarity transform (mParsePath / mPathFromSeq / mFitTransform), so a
  curve stays curved and parallel edges keep the separation Mermaid gave them.
- Endpoints were re-derived from the node centres, so the fanned starts and
  arrowheads of parallel edges collapsed onto a single toward-centre point. Each
  endpoint is now pinned to its node's original attach point and moved by that
  node's drag delta (edgeEnds), so the fan - arrowheads included - survives.
- Bending an edge (dragging its label) used the same collapsed endpoints, so the
  arrowhead jumped to the shared point. The bend branch now uses the pinned
  endpoints too, bowing only the middle toward the cursor.
- The edge-label to edge match compared label and path positions in mismatched
  coordinate frames, assigned greedily in DOM order, and measured to each edge's
  arc-midpoint - which on a curved edge sits nowhere near where the label was
  placed. So a label could bind to the wrong edge and a node drag then flung it
  across the diagram onto an unrelated arrow. Both positions are now lifted into
  the common frame and each label is paired to the edge it lies closest to (its
  minimum distance along the whole path), globally-closest-first.
- A label could also sit off its (curved) edge until dragged, because htmlit only
  snapped a label onto its line when that edge was rebuilt. Labels are now snapped
  onto their edge on a fresh render, so they sit on their arrow from the start; a
  saved arrangement still takes precedence.

Adds fixtures and browser regression tests for each case (parallel-edge curve and
arrowhead separation, label-bend arrowhead pinning, dense-diagram label matching
across straight and curved edges, and labels sitting on their line at load); each
fails before the fix and passes after.
htmlit serves an artifact verbatim, so malformed markup - a stray or unclosed
<div>/<section> - collapses the author's layout: a container closes early and the
sections after it escape the centring wrapper, rendering full-width. The browser
silently repairs the DOM, so the injected client cannot see the original mistake.

Add a stdlib structure check that re-reads the raw file whenever the client reports
layout (on load and after every morph) and folds any container imbalance into the
layout_warnings channel as an unbalanced-html warning, naming each stray/unclosed
tag with its line. Tracking only block containers that require an explicit end tag
keeps it free of false positives from optional-end tags (<p>, <li>, <td>, ...).

Covered by unit tests for the detector and an integration test through the poll.

Signed-off-by: Austin (Ngoc Thang) Pham <austinpham@microsoft.com>
The comment anchor already jumps to its agent answer when clicked, but there was
no way back: clicking the answer did nothing. Make the jump two-way - a plain click
on an answer block's "You asked:" header now scrolls to and flashes the anchored
spot. The click is scoped to the header/block itself (not the answer body), so the
answer text stays selectable and commentable, and the header gets a pointer cursor
+ hover underline to signal it is a back-link.

Adds a fixture and a browser test that fails before the change (the anchor never
flashes) and passes after.

Signed-off-by: Austin (Ngoc Thang) Pham <austinpham@microsoft.com>
@auspham auspham force-pushed the austinpham/mermaid-curve-and-html-validation branch from fa82a9f to 57501af Compare July 8, 2026 07:29
@auspham auspham merged commit 5938573 into main Jul 8, 2026
4 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.

1 participant