Skip to content

fix: make a "+" mean an offer, and a direction change mean direction (#549) - #552

Merged
vibhavkatre merged 2 commits into
frappe:mainfrom
bvnaik05:fix/mindmap-flowchart-ux-549
Aug 18, 2026
Merged

fix: make a "+" mean an offer, and a direction change mean direction (#549)#552
vibhavkatre merged 2 commits into
frappe:mainfrom
bvnaik05:fix/mindmap-flowchart-ux-549

Conversation

@bvnaik05

Copy link
Copy Markdown
Contributor

Closes #549.

What was wrong

A + did not mean "you can add something here." Every flowchart node marked all four sides whether or not a side already carried a connector, so one finished connection was framed by a mark at each end and read as an unfinished target. A decision crowded both branch previews onto the bottom edge about 40px apart, where the Yes pill covered the No handle — and, once Yes existed, covered the branch it had just created. A mind-map node hid its handles outright while it was being named, so reaching for the + with the caret still blinking found nothing there.

A direction change moved the diagram instead of changing its direction. positionByLevels lays every chart out from PAD, so a re-flow teleported the chart to the top-left of the canvas — on top of whatever else was there — and each further toggle moved it again. It also re-read crossCenter inside its own loop, off nodes that same pass had already moved, so the layout was not a pure function of its input: running it twice landed a few pixels off.

Measured on the same two-node chart, before and after:

before after
start 565,356 512,356
→ L→R 60,238 400,424
→ T→B 230,104 512,356
→ L→R drifting 400,424

What changed

Handles describe available actions (#549 items 1–4)

  • Occupancy comes from chooseSides — the same function the renderer routes with — so an occupied side is exactly the side the drawn connector uses, and it re-derives itself after a drag rather than trusting an anchor written at creation time.
  • A decision gives each untaken branch a side of its own (Yes down, No right); an extended branch offers nothing. A side is skipped when the branch pill, not only the mark, would land on another node, and a blocked side moves the branch along instead of costing it its handle.
  • Naming a node no longer suppresses its handles — it only stops them appearing on selection alone, which is what [Canvas][Bug] A new mind-map child shows its "+" before it is named #510 was actually protecting.
  • The handle under the pointer darkens and grows slightly, so a hit radius far wider than the mark still says which direction a press would take.

Direction change stays put (item 6) — the cross centre is measured once, and the re-flow is anchored back onto the chart's own bounding-box centre. The toggle is idempotent and cycles between two stable states.

Tidy up and Number the steps removed from flowcharts (items 5, 7) — including the numbering plumbing behind them (autoNumberFlow, isFlowNumbered, seedStepNumbers, the stepPrefix tags). The mind map keeps its Tidy up; stripStepNumber stays, since the outline conversions still use it.

Deliberately out of scope

  • FlowchartLayer.vue still draws its own + per node. It renders only for pre-unification single-type documents, and every case in the issue is the unified canvas. Happy to extend the same rules there if wanted.
  • A direction change cannot guarantee zero overlap with unrelated canvas content — an L→R chart is wider than the T→B one it came from. The reported cause (flying to the origin, onto the mind map) is fixed.

Testing

yarn test — 1692 passing. New coverage for side/branch occupancy, preview-footprint avoidance, layout anchoring, idempotency, and the full toggle cycle. Verified by hand in the running app: handles reveal on hover while a node is being edited and stay quiet when the pointer leaves; a connected pair shows no marks at either end of the connection; the toggle cycles between two positions without drift.

🤖 Generated with Claude Code

…rappe#549)

A flowchart node marked every side with a "+" whether or not that side
already carried a connector, so a finished connection was framed by one
mark at each end and read as unfinished. A decision crowded both of its
branch previews onto the bottom edge, ~40px apart, where the "Yes" pill
covered the "No" handle and then the branch it had just created. And a
mind-map node hid its handles entirely while it was being named, so
reaching for the "+" with the caret still blinking found nothing.

Handles now describe what is actually available:

- A side that carries a connector offers no "+". The side comes from
  chooseSides, the same function the renderer routes with, so it matches
  the drawn connector and re-derives itself after a drag instead of
  trusting an anchor written at creation time.
- A decision gives each untaken branch a side of its own — Yes down, No
  right — and an extended branch offers nothing. A side is also skipped
  when the branch PILL, not just the mark, would land on another node,
  and a blocked side moves the branch along rather than costing it its
  handle.
- Naming a node no longer suppresses its handles; it only stops them
  appearing on selection alone, which is what frappe#510 was protecting.
- The handle under the pointer darkens and grows, so a hit radius far
  wider than the mark still says which direction a press would take.

Changing the flow direction moved the whole chart. positionByLevels lays
every chart out from PAD, so a re-flow teleported the diagram to the
top-left of the canvas — over whatever else was there — and each further
toggle moved it again. It also re-read crossCenter inside its own loop,
off nodes that same pass had already moved, so the layout was not even a
pure function of its input and a second run landed pixels off the first.
The centre is now measured once and the result anchored back onto the
chart's own bounding-box centre: L→R→T→B→L→R cycles between two states
and stays put.

Tidy up and Number the steps are gone from flowcharts, along with the
numbering plumbing behind them (autoNumberFlow, isFlowNumbered, the
stepPrefix tags). Both asked the user to re-arrange a chart they had
already arranged by hand. The mind map keeps its Tidy up.

The legacy FlowchartLayer, which renders only for pre-unification
single-type documents, is left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 18, 2026 09:27

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Letting hover reveal a mind-map node's handles while it is being named
(the previous commit) went one step too far: clicking a "+" leaves the
pointer parked exactly where the handle was, and the child is born under
it, so the new node offered a "+" of its own before it even had a name.
That is the complaint frappe#510 fixed, and e2e caught it.

Opening an editor now drops the hover. Since hover only re-arms on a
pointermove, a node born under a stationary pointer offers nothing, while
reaching back toward it still produces a handle — because that is the
user actually asking. Both frappe#549 item 1 and frappe#510 hold, and neither depends
on where the pointer happened to be when the editor opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@vibhavkatre vibhavkatre left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed against #549, all seven items.

Correctness

Occupancy is derived, not stored. occupancy() re-runs chooseSides — the same function useFlowchartRoutes routes with, and with the same three guards (role, both endpoints known, no self-loop) — so a "+" disappears from exactly the side the drawn connector uses, and it follows the connector after a drag. This is the right call: an anchor written at creation time would have gone stale.

positionByLevels reading crossCenter inside its own loop was a real defect, not just drift: the layout was not a pure function of its input. Hoisting the measurement out and anchoring the result on the chart's own bounding-box centre fixes both the teleport and the idempotency, and the toggle-cycle test pins it.

buildContext gained a parameter, and the mind-map's same-named buildContext is a different function that already defaulted connectors. No caller is left passing the wrong arity.

Notes, none blocking

  1. A decision with more open branches than free sides silently drops the surplus — branches.slice(0, sides.length). A three-branch decision hemmed in on two sides offers two handles and the third outcome has no affordance. Rare, and the fallback in offerableSides keeps it from ever showing nothing.
  2. A chart numbered before this keeps "1. " baked into its labels, and the toggle that removed them is gone. The stored flowchart.stepPrefix is now orphaned. Removal is what #549 item 7 asked for, so this is a note for the record rather than a change request — those labels are editable by hand.
  3. The two new assertions in mindmapDragLayer.test.js match source text. That file already works this way, so it is consistent, but a harmless refactor (watch(editingNodeId, () => clearHover())) breaks them with no behaviour change.

All three checks green. Merging.

@vibhavkatre
vibhavkatre merged commit 4aef12e into frappe:main Aug 18, 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.

Mindmap & Flowchart UX Improvements and Cleanup

3 participants