Mind map selection outline does not update on collapse/expand #157

Open
opened 2026-05-06 10:12:39 +00:00 by AhmedHanafy725 · 3 comments
Member

When the user toggles collapse/expand on a mind-map node (the +/− indicator) or on the root via the keyboard shortcut, the underlying tree re-renders with a different size, but the selection outline / transformer bounding box does not refresh — it keeps the old extents from before the toggle.

Expected

After renderMindmap(group), the transformer attached to the mindmap (if any) should recompute its bounding box (tr.forceUpdate() in Konva terms) so the selection outline matches the new visual extents.

Actual

The outline keeps the old size, and dragging the corner handles operates on stale dimensions.

Repro

  1. Create a mind map
  2. Select it (transformer handles appear around it)
  3. Click any +/− toggle to collapse a branch — the layout shrinks, but the selection box stays the original size
  4. Expand again — selection still stale

Notes

The relevant code path is indicator.on("click tap", ...) in mindmap.js (sets node.collapsed and calls renderMindmap(group)), as well as toggleCollapseRoot for the root-level shortcut. After the redraw, the transformer needs to be told to refresh — pattern used elsewhere is something like WhiteboardObjects.refreshTransformer(group) or tr.forceUpdate(). Verify how other re-rendering mutations (e.g., flipDirection, addChildToNode, deleteNode) handle the transformer.

When the user toggles collapse/expand on a mind-map node (the +/− indicator) or on the root via the keyboard shortcut, the underlying tree re-renders with a different size, but the selection outline / transformer bounding box does not refresh — it keeps the old extents from before the toggle. ## Expected After `renderMindmap(group)`, the transformer attached to the mindmap (if any) should recompute its bounding box (`tr.forceUpdate()` in Konva terms) so the selection outline matches the new visual extents. ## Actual The outline keeps the old size, and dragging the corner handles operates on stale dimensions. ## Repro 1. Create a mind map 2. Select it (transformer handles appear around it) 3. Click any +/− toggle to collapse a branch — the layout shrinks, but the selection box stays the original size 4. Expand again — selection still stale ## Notes The relevant code path is `indicator.on("click tap", ...)` in `mindmap.js` (sets `node.collapsed` and calls `renderMindmap(group)`), as well as `toggleCollapseRoot` for the root-level shortcut. After the redraw, the transformer needs to be told to refresh — pattern used elsewhere is something like `WhiteboardObjects.refreshTransformer(group)` or `tr.forceUpdate()`. Verify how other re-rendering mutations (e.g., `flipDirection`, `addChildToNode`, `deleteNode`) handle the transformer.
Author
Member

Implementation Spec

Root cause

renderMindmap(group) in mindmap.js calls group.destroyChildren() and rebuilds the sub-tree, which changes the group's bounding box. A Konva.Transformer caches its attached nodes' bounding boxes, so the selection outline keeps the old extents until something calls tr.forceUpdate().

Three mutating paths already do the refresh inline (addChildToNode ~L463–466, flipDirection ~L493–496, deleteNode ~L681–686). But several other paths skip it:

Site Trigger
indicator.on('click tap', …) clicking +/− toggle — the reported bug
toggleCollapseRoot keyboard shortcut
editMindmapNode blur text edit changes node width
editMindmapTitle blur title edit changes title width
showCommentPopup Save / Remove adds/removes 💬 indicator

Approach: refresh inside renderMindmap

renderMindmap is the single point through which every mutating path funnels. One forceUpdate() at the end covers all current sites and any future ones. This mirrors the existing pattern in objects.js where refreshTransformerFor(group) is called from every re-render helper. The cost is a no-op when the mindmap isn't selected (tr.nodes().indexOf(group) >= 0 guard), so no flicker risk.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js — only file.

Implementation Plan

Step 1 — add helper + single call from renderMindmap

  1. Add a private helper near the top of the IIFE (after measureNodeText):
    function refreshTransformerFor(group) {
        if (typeof WhiteboardTools === 'undefined' || !WhiteboardTools.getTransformer) return;
        var tr = WhiteboardTools.getTransformer();
        if (tr && tr.nodes && tr.nodes().indexOf(group) >= 0 && tr.forceUpdate) {
            tr.forceUpdate();
        }
    }
    
  2. Append one line at the very end of renderMindmap, after WhiteboardCanvas.getObjectLayer().batchDraw();:
    refreshTransformerFor(group);
    
  3. Remove the now-redundant inline forceUpdate blocks in addChildToNode, flipDirection, and deleteNode so the file is tight and consistent with objects.js.

Dependencies: none.

Acceptance Criteria

With a mindmap selected (transformer attached):

  • Click /+ on any non-root node — selection outline tracks the contracted/expanded extent in the same frame.
  • toggleCollapseRoot keyboard shortcut — outline tracks new bounds.
  • Direction flip ↕/↔ — outline tracks rotated layout.
  • Add child + — outline grows.
  • Right-click → Delete Node — outline shrinks.
  • Double-click a node, edit to wider text, blur — outline matches new width.
  • Double-click title, edit, blur — outline matches.
  • Add/Remove comment via popup — outline refreshed.
  • While mindmap not selected, every action above still works (helper short-circuits).

What NOT to break

  • #156 / c40d249 lock work — gates run before renderMindmap, so locked behavior is unchanged.
  • #155 / 92f9859 menu unification — not touched.
  • #154 / e3c4cb8 per-node contextmenu wiring — not touched.
  • #153-chain variable-node-size work (measureNodeText, layoutTree, assignDepthAxis, getLayoutBounds, wrap mode) — not touched. Only adds a single line at the tail of renderMindmap.
  • WhiteboardSync.onUpdate / WhiteboardHistoryforceUpdate() only refreshes transformer handles; it does not emit transform/transformend events that would loop into sync.
  • Drag/drop and the transformer attachment lifecycle — not modified.
## Implementation Spec ### Root cause `renderMindmap(group)` in `mindmap.js` calls `group.destroyChildren()` and rebuilds the sub-tree, which changes the group's bounding box. A `Konva.Transformer` caches its attached nodes' bounding boxes, so the selection outline keeps the old extents until something calls `tr.forceUpdate()`. Three mutating paths already do the refresh inline (`addChildToNode` ~L463–466, `flipDirection` ~L493–496, `deleteNode` ~L681–686). But several other paths skip it: | Site | Trigger | |---|---| | `indicator.on('click tap', …)` | clicking +/− toggle — **the reported bug** | | `toggleCollapseRoot` | keyboard shortcut | | `editMindmapNode` blur | text edit changes node width | | `editMindmapTitle` blur | title edit changes title width | | `showCommentPopup` Save / Remove | adds/removes 💬 indicator | ### Approach: refresh inside `renderMindmap` `renderMindmap` is the single point through which every mutating path funnels. One `forceUpdate()` at the end covers all current sites and any future ones. This mirrors the existing pattern in `objects.js` where `refreshTransformerFor(group)` is called from every re-render helper. The cost is a no-op when the mindmap isn't selected (`tr.nodes().indexOf(group) >= 0` guard), so no flicker risk. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js` — only file. ### Implementation Plan #### Step 1 — add helper + single call from `renderMindmap` 1. Add a private helper near the top of the IIFE (after `measureNodeText`): ```js function refreshTransformerFor(group) { if (typeof WhiteboardTools === 'undefined' || !WhiteboardTools.getTransformer) return; var tr = WhiteboardTools.getTransformer(); if (tr && tr.nodes && tr.nodes().indexOf(group) >= 0 && tr.forceUpdate) { tr.forceUpdate(); } } ``` 2. Append one line at the very end of `renderMindmap`, after `WhiteboardCanvas.getObjectLayer().batchDraw();`: ```js refreshTransformerFor(group); ``` 3. Remove the now-redundant inline `forceUpdate` blocks in `addChildToNode`, `flipDirection`, and `deleteNode` so the file is tight and consistent with `objects.js`. Dependencies: none. ### Acceptance Criteria With a mindmap selected (transformer attached): - [ ] Click `−`/`+` on any non-root node — selection outline tracks the contracted/expanded extent in the same frame. - [ ] `toggleCollapseRoot` keyboard shortcut — outline tracks new bounds. - [ ] Direction flip ↕/↔ — outline tracks rotated layout. - [ ] Add child `+` — outline grows. - [ ] Right-click → Delete Node — outline shrinks. - [ ] Double-click a node, edit to wider text, blur — outline matches new width. - [ ] Double-click title, edit, blur — outline matches. - [ ] Add/Remove comment via popup — outline refreshed. - [ ] While mindmap not selected, every action above still works (helper short-circuits). ### What NOT to break - #156 / `c40d249` lock work — gates run before `renderMindmap`, so locked behavior is unchanged. - #155 / `92f9859` menu unification — not touched. - #154 / `e3c4cb8` per-node contextmenu wiring — not touched. - #153-chain variable-node-size work (`measureNodeText`, `layoutTree`, `assignDepthAxis`, `getLayoutBounds`, wrap mode) — not touched. Only adds a single line at the tail of `renderMindmap`. - `WhiteboardSync.onUpdate` / `WhiteboardHistory` — `forceUpdate()` only refreshes transformer handles; it does not emit `transform`/`transformend` events that would loop into sync. - Drag/drop and the transformer attachment lifecycle — not modified.
Author
Member

Validation

Check Result
Files changed 1 file (mindmap.js, +13/-16)
cargo check --workspace pass
cargo test --workspace --lib pass
forceUpdate references 1 (the new helper) — three inline blocks removed
## Validation | Check | Result | |---|---| | Files changed | 1 file (mindmap.js, +13/-16) | | `cargo check --workspace` | pass | | `cargo test --workspace --lib` | pass | | `forceUpdate` references | 1 (the new helper) — three inline blocks removed |
Author
Member

Implementation summary

Changes

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js — added a private refreshTransformerFor(group) helper and call it once at the tail of renderMindmap (after batchDraw). The helper short-circuits when the mindmap isn't currently selected, so the cost is zero on unselected mindmaps. Removed three inline forceUpdate blocks in addChildToNode, flipDirection, and deleteNode since they're now redundant.

Every mutating path that calls renderMindmap now refreshes the transformer automatically: collapse/expand toggle on any node (the reported bug), toggleCollapseRoot keyboard shortcut, direction flip, add child, delete node, node text edit blur, title text edit blur, and comment popup Save/Remove.

Validation

  • cargo check --workspace: pass
  • cargo test --workspace --lib: pass
  • Diff scope: 1 file (mindmap.js, +13/-16 — net -3 lines)

Notes

UI-only change. Verify visually by selecting a mindmap (transformer handles appear), then clicking any +/− toggle — the selection outline should contract/expand to match the new layout instantly. Unselected mindmaps behave exactly as before.

## Implementation summary ### Changes - `crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js` — added a private `refreshTransformerFor(group)` helper and call it once at the tail of `renderMindmap` (after `batchDraw`). The helper short-circuits when the mindmap isn't currently selected, so the cost is zero on unselected mindmaps. Removed three inline `forceUpdate` blocks in `addChildToNode`, `flipDirection`, and `deleteNode` since they're now redundant. Every mutating path that calls `renderMindmap` now refreshes the transformer automatically: collapse/expand toggle on any node (the reported bug), `toggleCollapseRoot` keyboard shortcut, direction flip, add child, delete node, node text edit blur, title text edit blur, and comment popup Save/Remove. ### Validation - `cargo check --workspace`: pass - `cargo test --workspace --lib`: pass - Diff scope: 1 file (mindmap.js, +13/-16 — net -3 lines) ### Notes UI-only change. Verify visually by selecting a mindmap (transformer handles appear), then clicking any +/− toggle — the selection outline should contract/expand to match the new layout instantly. Unselected mindmaps behave exactly as before.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#157
No description provided.