Whiteboard: mindmap / calendar / webframe mutations are not undoable #183
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#183
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Same class of bug as #182 (kanban) in three more widgets. The mutations issue
WhiteboardSync.onUpdate(group)to persist but never bracket the change withWhiteboardHistory.snapshotBefore(id)+commitUpdate(id), so Ctrl+Z silently does nothing for:Mindmap (
static/web/js/whiteboard/mindmap.js)addChildToNode(line 476)toggleCollapseRoot(line 492)flipDirection(line 502)editMindmapTitleblur (line 544)deleteNode(line 684)editMindmapNodeblur (line 735)Calendar (
static/web/js/whiteboard/calendar.js)navigateNext(line 393)navigatePrev(line 408)cycleViewMode(line 419)WhiteboardSync.onUpdatewithout history (look up inselection_toolbar.js)Webframe (
static/web/js/whiteboard/webframe.js)applyNewUrl(group, rawUrl)(line 348-375) — the only mutation path; called from the selection-toolbar URL input and the URL prompt fallback. Afterif (newUrl === state.url) return;, snapshot, mutate, persist, commit.Expected
Each of those becomes a single undo step. Cancelled inline editors (no change) push nothing because
commitUpdateno-ops on identical before/after.Acceptance
onUpdate(group)still fires exactly once per mutation.Implementation Spec for Issue #183
Objective
Bracket every mindmap / calendar / webframe mutation with
WhiteboardHistory.snapshotBefore(id)+commitUpdate(id), applying the same pattern that landed in #182 for kanban.Approach
For each module, add a small file-local helper:
(Identical helpers in
calendar.jsandwebframe.js, namedpersistCalendarMutationandpersistWebframeMutation.)Then at each mutation site:
WhiteboardHistory.snapshotBefore(group.id());at the earliest unconditional point inside the callback.WhiteboardSync.onUpdate(group);withpersistXxxMutation(group);.commitUpdateno-ops on identical before/after, so cancelled inline editors push nothing onto the stack.Files to Modify
static/web/js/whiteboard/mindmap.jsstatic/web/js/whiteboard/calendar.jsstatic/web/js/whiteboard/webframe.jsImplementation Plan
Step 1: mindmap.js
Add helper near the top of the IIFE. Wrap these mutation sites:
addChildToNode(line 464-477) — snapshot at top after lock check; replaceonUpdate.toggleCollapseRoot(line 486-494) — snapshot inside theif (state && state.tree)block before mutation; replaceonUpdate.flipDirection(line 496-504) — snapshot inside theif (state)block; replaceonUpdate.editMindmapTitleblur handler (line 536-545) — snapshot inside the blur after the lock check, beforestate.title = input.value; replaceonUpdate.nodeData.comment = …; replaceonUpdate.onUpdate.deleteNode(line 663-685) — snapshot inside the function beforeremoveFromTree; replaceonUpdate.editMindmapNodeblur handler (line 727-736) — snapshot after lock check, beforenodeData.text = …; replaceonUpdate.Step 2: calendar.js
Add helper. Wrap:
onUpdate.navigateNext(line 381-394) — snapshot after theif (!state) return;early-exit; replaceonUpdate.navigatePrev(line 396-409) — same.cycleViewMode(line 411-420) — same.Note: also check
selection_toolbar.js_renderCalendar for the Today / view-mode select handlers; if they call functions in calendar.js the wrapping above covers them. If they mutate state directly and callonUpdatethemselves, add a snapshot/commit there too. Concrete grep before editing:grep -n "WhiteboardSync.onUpdate" selection_toolbar.js.Step 3: webframe.js
Add helper. Wrap
applyNewUrl(line 348-375):WhiteboardHistory.snapshotBefore(group.id());AFTER theif (newUrl === state.url) return;early-exit (line 356) so a no-op call doesn't push a phantom snapshot. The snapshot captures the state BEFOREstate.url = newUrl.WhiteboardSync.onUpdate(group)(line 373) withpersistWebframeMutation(group);.Acceptance Criteria
onUpdate(group)fires exactly once per mutation.Notes
snapshot()serializes viaWhiteboardSync.serializeForServer(group), which for these widgets includes their_mmState/_calState/_wfState. Undo restores the whole widget data as a single update — same as kanban.onUpdatestill fires once per mutation).Test Results + Final Summary
Changes
mindmap.js— addedpersistMindmapMutationhelper; wrapped 8 sites:addChildToNode,toggleCollapseRoot,flipDirection,editMindmapTitleblur, comment popup Save / Delete,deleteNode,editMindmapNodeblur.calendar.js— addedpersistCalendarMutationhelper; wrapped 4 sites: dblclick cycle handler,navigateNext,navigatePrev,cycleViewMode.webframe.js— addedpersistWebframeMutationhelper; wrappedapplyNewUrl(snapshot after the no-op early-exit so cancelled / same-URL inputs push nothing).Behaviour after fix
Every mindmap, calendar, and webframe mutation is one undo step. Cancelled inline editors with identical before/after push nothing because
commitUpdateno-ops on equal snapshots. Multi-user broadcasts continue to fire exactly once per mutation.Gates
node -c mindmap.js / calendar.js / webframe.js— JS syntax OKcargo fmt --check— passcargo clippy --workspace --all-targets -- -D warnings— passcargo test --workspace --lib— 0 tests, 0 failuresAcceptance Criteria
_mmState.treeis in the snapshot).onUpdate(group)fires exactly once per mutation.Out of scope
Manual verification still required
Rebuild + restart
hero_whiteboard_admin, hard-reload a board with mindmap / calendar / webframe widgets, exercise each mutation and confirm Ctrl+Z / Ctrl+Y.