Make mindmap navigation more intuitive (active node, keyboard traversal, clearer controls) #212

Closed
opened 2026-05-21 12:21:29 +00:00 by AhmedHanafy725 · 1 comment

Make mindmap navigation more intuitive (active node, keyboard traversal, clearer controls)

Background

User feedback: navigating and editing a mindmap is not intuitive. Today the per-node affordances are faint always-on glyphs and keyboard actions only ever apply to the root node, so there is no sense of "where you are" in the tree and no fast way to move/build it. Reference tools (e.g. Miro) make the active node and the add/collapse controls clearly visible.

Current behavior (static/web/js/whiteboard/mindmap.js, static/web/js/whiteboard/shortcuts.js):

  • Each node draws a faint text + (add child) and +/- (collapse) glyph; low-contrast and small hit area.
  • There is no concept of a selected/active node within a mindmap; selecting the mindmap selects the whole object via the transformer.
  • Keyboard (handleWidgetShortcut, mindmap branch) only acts on the root: Tab adds a child to root, Space collapses root, L flips direction.

Objective

Add an "active node" concept inside a selected mindmap with a clear visual highlight, full keyboard traversal/editing, and more visible Miro-style on-node controls — without regressing existing rendering, undo/redo, sync, or locking.

Requirements

  1. Active node + visible selection state

    • Track state.activeNode on the mindmap; default to the root when the mindmap becomes the single selected object.
    • The active node renders with a clear highlight (accent outline / halo distinct from the normal node border).
    • Clicking a node makes it the active node. Deselecting the mindmap clears the highlight.
  2. Keyboard traversal and editing (only when a single mindmap is selected and not locked; ignore while an inline editor input is focused)

    • Arrow keys move the active node, relative to layout direction:
      • Vertical: Down = first child, Up = parent, Left/Right = previous/next sibling.
      • Horizontal: Right = first child, Left = parent, Up/Down = previous/next sibling.
    • Tab = add a child to the active node, make it active, and open its inline editor.
    • Enter = add a sibling after the active node (a child of its parent), make it active, open editor; if the active node is the root, fall back to adding a child.
    • Delete/Backspace = delete the active node (never the root) and move active to the parent (or nearest sibling).
    • F2 = edit the active node's text. Space = toggle collapse of the active node.
    • Each action is one undo step and syncs, reusing the existing snapshotBefore / persistMindmapMutation pattern.
  3. Clearer on-node controls (Miro-style)

    • Replace the faint glyphs with circular buttons with a real background and larger hit area.
    • Collapse/expand becomes a single circular toggle on the child side: shows the hidden direct-child count when collapsed, and a minus/chevron when expanded.
    • Add-child becomes a circular + on the child side, faint by default and emphasized on node hover (mouseenter/mouseleave toggling opacity, no full re-render).

Files to modify

  • crates/hero_whiteboard_admin/static/web/js/whiteboard/mindmap.js - active-node state + highlight, parent/sibling/first-child lookups, new exported actions (set active, move active, add child/sibling to active, delete active, edit active), redesigned circular controls with hover emphasis.
  • crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js - extend the type === 'mindmap' branch of handleWidgetShortcut to drive the new active-node actions (arrows, Tab, Enter, Delete/Backspace, F2, Space).

Implementation plan

Step 1: Active-node state, lookups, and highlight (mindmap.js)

  • Add state.activeNode (node ref); helper findParent(tree, node); helpers for first child / previous-next sibling.
  • In drawNodes, when the node is the active node, add a highlight (extra accent stroke/halo). Add a mm-node click handler that sets the active node and re-renders.
  • Default activeNode to root on selection; expose setActiveFromSelection(group) and clear on deselect.
    Dependencies: none

Step 2: Active-node actions API (mindmap.js)

  • Export moveActive(group, dir), addChildToActive(group), addSiblingToActive(group), deleteActive(group), editActive(group), toggleCollapseActive(group), reusing addChildToNode / deleteNode / editMindmapNode and the history+sync pattern. New-node actions set the new node active and open its editor.
    Dependencies: Step 1

Step 3: Redesigned circular controls with hover emphasis (mindmap.js)

  • Draw collapse/expand as a circle (count when collapsed, minus/chevron when expanded) and add-child as a circle +, both with backgrounds and larger hit areas; default add-child opacity low, raised on node hover via mouseenter/mouseleave + batchDraw.
    Dependencies: Step 1

Step 4: Keyboard wiring (shortcuts.js)

  • Extend the mindmap branch of handleWidgetShortcut: arrows -> moveActive; Tab -> addChildToActive; Enter -> addSiblingToActive; Delete/Backspace -> deleteActive; F2 -> editActive; Space -> toggleCollapseActive; keep L = flip. Guard against firing while an inline input is focused.
    Dependencies: Steps 1-2

Acceptance criteria

  • Selecting a mindmap highlights an active node (root by default); clicking another node moves the highlight.
  • Arrow keys traverse parent/child/siblings correctly in both vertical and horizontal layouts.
  • Tab adds a child to the active node and starts editing it; Enter adds a sibling and starts editing; Delete removes the active node (never the root) and reselects a sensible neighbor.
  • Each keyboard action is a single undo step and is synced to other clients.
  • Collapse/expand control is a clear circular toggle showing the hidden-child count when collapsed; add-child is a clear circular button emphasized on hover.
  • No regression to add/edit/delete via mouse, right-click menu, comments, flip direction, drag, lock, or persistence.

Notes

Nodes are plain tree objects without ids or parent pointers; active node is tracked by object reference (stable across re-render) and parent is resolved by walking the tree. Keyboard handling must no-op while an inline <input>/<textarea> editor is focused so typing is not intercepted.

## Make mindmap navigation more intuitive (active node, keyboard traversal, clearer controls) ### Background User feedback: navigating and editing a mindmap is not intuitive. Today the per-node affordances are faint always-on glyphs and keyboard actions only ever apply to the root node, so there is no sense of "where you are" in the tree and no fast way to move/build it. Reference tools (e.g. Miro) make the active node and the add/collapse controls clearly visible. Current behavior (`static/web/js/whiteboard/mindmap.js`, `static/web/js/whiteboard/shortcuts.js`): - Each node draws a faint text `+` (add child) and `+`/`-` (collapse) glyph; low-contrast and small hit area. - There is no concept of a selected/active node within a mindmap; selecting the mindmap selects the whole object via the transformer. - Keyboard (`handleWidgetShortcut`, mindmap branch) only acts on the root: Tab adds a child to root, Space collapses root, L flips direction. ### Objective Add an "active node" concept inside a selected mindmap with a clear visual highlight, full keyboard traversal/editing, and more visible Miro-style on-node controls — without regressing existing rendering, undo/redo, sync, or locking. ### Requirements 1. Active node + visible selection state - Track `state.activeNode` on the mindmap; default to the root when the mindmap becomes the single selected object. - The active node renders with a clear highlight (accent outline / halo distinct from the normal node border). - Clicking a node makes it the active node. Deselecting the mindmap clears the highlight. 2. Keyboard traversal and editing (only when a single mindmap is selected and not locked; ignore while an inline editor input is focused) - Arrow keys move the active node, relative to layout direction: - Vertical: Down = first child, Up = parent, Left/Right = previous/next sibling. - Horizontal: Right = first child, Left = parent, Up/Down = previous/next sibling. - Tab = add a child to the active node, make it active, and open its inline editor. - Enter = add a sibling after the active node (a child of its parent), make it active, open editor; if the active node is the root, fall back to adding a child. - Delete/Backspace = delete the active node (never the root) and move active to the parent (or nearest sibling). - F2 = edit the active node's text. Space = toggle collapse of the active node. - Each action is one undo step and syncs, reusing the existing `snapshotBefore` / `persistMindmapMutation` pattern. 3. Clearer on-node controls (Miro-style) - Replace the faint glyphs with circular buttons with a real background and larger hit area. - Collapse/expand becomes a single circular toggle on the child side: shows the hidden direct-child count when collapsed, and a minus/chevron when expanded. - Add-child becomes a circular `+` on the child side, faint by default and emphasized on node hover (mouseenter/mouseleave toggling opacity, no full re-render). ### Files to modify - `crates/hero_whiteboard_admin/static/web/js/whiteboard/mindmap.js` - active-node state + highlight, parent/sibling/first-child lookups, new exported actions (set active, move active, add child/sibling to active, delete active, edit active), redesigned circular controls with hover emphasis. - `crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js` - extend the `type === 'mindmap'` branch of `handleWidgetShortcut` to drive the new active-node actions (arrows, Tab, Enter, Delete/Backspace, F2, Space). ### Implementation plan #### Step 1: Active-node state, lookups, and highlight (mindmap.js) - Add `state.activeNode` (node ref); helper `findParent(tree, node)`; helpers for first child / previous-next sibling. - In `drawNodes`, when the node is the active node, add a highlight (extra accent stroke/halo). Add a `mm-node` click handler that sets the active node and re-renders. - Default `activeNode` to root on selection; expose `setActiveFromSelection(group)` and clear on deselect. Dependencies: none #### Step 2: Active-node actions API (mindmap.js) - Export `moveActive(group, dir)`, `addChildToActive(group)`, `addSiblingToActive(group)`, `deleteActive(group)`, `editActive(group)`, `toggleCollapseActive(group)`, reusing `addChildToNode` / `deleteNode` / `editMindmapNode` and the history+sync pattern. New-node actions set the new node active and open its editor. Dependencies: Step 1 #### Step 3: Redesigned circular controls with hover emphasis (mindmap.js) - Draw collapse/expand as a circle (count when collapsed, minus/chevron when expanded) and add-child as a circle `+`, both with backgrounds and larger hit areas; default add-child opacity low, raised on node hover via mouseenter/mouseleave + batchDraw. Dependencies: Step 1 #### Step 4: Keyboard wiring (shortcuts.js) - Extend the mindmap branch of `handleWidgetShortcut`: arrows -> `moveActive`; Tab -> `addChildToActive`; Enter -> `addSiblingToActive`; Delete/Backspace -> `deleteActive`; F2 -> `editActive`; Space -> `toggleCollapseActive`; keep L = flip. Guard against firing while an inline input is focused. Dependencies: Steps 1-2 ### Acceptance criteria - [ ] Selecting a mindmap highlights an active node (root by default); clicking another node moves the highlight. - [ ] Arrow keys traverse parent/child/siblings correctly in both vertical and horizontal layouts. - [ ] Tab adds a child to the active node and starts editing it; Enter adds a sibling and starts editing; Delete removes the active node (never the root) and reselects a sensible neighbor. - [ ] Each keyboard action is a single undo step and is synced to other clients. - [ ] Collapse/expand control is a clear circular toggle showing the hidden-child count when collapsed; add-child is a clear circular button emphasized on hover. - [ ] No regression to add/edit/delete via mouse, right-click menu, comments, flip direction, drag, lock, or persistence. ### Notes Nodes are plain tree objects without ids or parent pointers; active node is tracked by object reference (stable across re-render) and parent is resolved by walking the tree. Keyboard handling must no-op while an inline `<input>`/`<textarea>` editor is focused so typing is not intercepted.
Author
Owner

Implemented: active node, keyboard traversal, clearer controls

Changes

  • static/web/js/whiteboard/mindmap.js
    • Added an active-node concept (state.activeNode, defaults to root) with a dashed highlight halo that is repositioned (not re-rendered) on plain selection clicks, so double-click-to-edit keeps working.
    • Helpers: findParent, nodeExists, visibleChildren, findNodeRect, updateActiveHighlight, ensureActive, setActive.
    • Active-node actions: moveActive (arrow traversal resolved against layout direction), addChildToActive, addSiblingToActive, deleteActive (refuses root, reselects a sensible neighbor), editActive, toggleCollapseActive. New-node actions set the new node active and open its inline editor.
    • Controls redesigned as circular buttons (makeCircleBtn): collapse/expand is a single circle showing the hidden direct-child count when collapsed and a minus when expanded; add-child is a + circle that is faint by default and brightens on node/button hover, with a real (larger) hit area. addChildToNode now accepts an insert index, sets the new node active, and returns it.
  • static/web/js/whiteboard/shortcuts.js
    • Extended the mindmap branch of handleWidgetShortcut to drive the active node: Arrows = traverse; Tab = add child + edit; Enter = add sibling + edit; F2 = edit; Delete/Backspace = delete active node (on the root it falls through so the whole mindmap is deleted); Space = collapse active; L = flip direction.

Verification

Rust workspace lib tests (no regression):

cargo test --workspace --lib  ->  ok. 0 failed

Traversal logic test (replicates moveActive/deleteActive against a sample tree, both layout directions):

PASS  V root Down -> A          PASS  H root Right -> A
PASS  V A Up -> root            PASS  H A Left -> root
PASS  V A Right(next) -> B      PASS  H A Down(next) -> B
PASS  V B Left(prev) -> A       PASS  H A Up(prev) -> none
PASS  V B Down (collapsed) -> none
PASS  V A1 Right(next) -> A2    PASS  del A2 -> A1
PASS  V C Right(next) -> none   PASS  del A1 -> A2
                               PASS  del C -> B
                               PASS  del B1 -> B (parent)
                               PASS  del root -> refused
ALL PASS (16)

Deployed locally (release build, installed, service restarted); the served mindmap.js and shortcuts.js carry the changes.

Manual check needed (no browser automation in this environment)

Select a mindmap, then: click a node to highlight it; use arrows to move; Tab/Enter to add child/sibling; Delete to remove; hover a node to reveal the brighter + button; collapse a branch to see the count badge.

## Implemented: active node, keyboard traversal, clearer controls ### Changes - `static/web/js/whiteboard/mindmap.js` - Added an active-node concept (`state.activeNode`, defaults to root) with a dashed highlight halo that is repositioned (not re-rendered) on plain selection clicks, so double-click-to-edit keeps working. - Helpers: `findParent`, `nodeExists`, `visibleChildren`, `findNodeRect`, `updateActiveHighlight`, `ensureActive`, `setActive`. - Active-node actions: `moveActive` (arrow traversal resolved against layout direction), `addChildToActive`, `addSiblingToActive`, `deleteActive` (refuses root, reselects a sensible neighbor), `editActive`, `toggleCollapseActive`. New-node actions set the new node active and open its inline editor. - Controls redesigned as circular buttons (`makeCircleBtn`): collapse/expand is a single circle showing the hidden direct-child count when collapsed and a minus when expanded; add-child is a `+` circle that is faint by default and brightens on node/button hover, with a real (larger) hit area. `addChildToNode` now accepts an insert index, sets the new node active, and returns it. - `static/web/js/whiteboard/shortcuts.js` - Extended the mindmap branch of `handleWidgetShortcut` to drive the active node: Arrows = traverse; Tab = add child + edit; Enter = add sibling + edit; F2 = edit; Delete/Backspace = delete active node (on the root it falls through so the whole mindmap is deleted); Space = collapse active; L = flip direction. ### Verification Rust workspace lib tests (no regression): ``` cargo test --workspace --lib -> ok. 0 failed ``` Traversal logic test (replicates moveActive/deleteActive against a sample tree, both layout directions): ``` PASS V root Down -> A PASS H root Right -> A PASS V A Up -> root PASS H A Left -> root PASS V A Right(next) -> B PASS H A Down(next) -> B PASS V B Left(prev) -> A PASS H A Up(prev) -> none PASS V B Down (collapsed) -> none PASS V A1 Right(next) -> A2 PASS del A2 -> A1 PASS V C Right(next) -> none PASS del A1 -> A2 PASS del C -> B PASS del B1 -> B (parent) PASS del root -> refused ALL PASS (16) ``` Deployed locally (release build, installed, service restarted); the served `mindmap.js` and `shortcuts.js` carry the changes. ### Manual check needed (no browser automation in this environment) Select a mindmap, then: click a node to highlight it; use arrows to move; Tab/Enter to add child/sibling; Delete to remove; hover a node to reveal the brighter + button; collapse a branch to see the count badge.
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#212
No description provided.