Whiteboard: kanban column / card mutations are not undoable #182
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#182
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
Nearly every kanban edit on the whiteboard issues
WhiteboardSync.onUpdate(group)to persist the change but never brackets the mutation withWhiteboardHistory.snapshotBefore(id)+commitUpdate(id). As a result Ctrl+Z does not roll back any of:deleteSelectedCard(kanban.js:862-880)addCardToFirstColumn(kanban.js:883-890)The only kanban changes that do undo today are:
snapshotBefore/commitUpdateatkanban.js:274-287, 326, 338)objects.jshandles it)Affected call sites
Mutation sites in
kanban.jscallingonUpdate(group)without history brackets:kanban.js:76-78, 199, 232, 339, 381, 400, 431, 564, 604, 689, 708, 721, 729, 740, 748, 879, 888.Reference: the column drag-reorder path that already does it correctly:
kanban.js:274-287.Expected
Every user-visible kanban mutation is one undo step. Pressing Ctrl+Z right after deleting a card restores that card with its text, icon, position in the column, and column membership. Same for column add/delete/edit.
Acceptance
deleteSelectedCardandaddCardToFirstColumnkeyboard / shortcut paths participate in undo.onUpdate) still fires exactly once per mutation (no double emit).Out of scope
Mindmap / Calendar / Webframe / Ungroup / Selection-toolbar property edits have the same class of issue — separate items in the audit; file as follow-ups.
Implementation Spec for Issue #182
Objective
Every kanban mutation becomes a single undo step. Wrap each existing
WhiteboardSync.onUpdate(group)call site inkanban.jswithWhiteboardHistory.snapshotBefore(id)+commitUpdate(id), following the pattern already used by the column-drag-reorder path atkanban.js:274-287.Approach
Introduce a small file-local helper instead of repeating four lines at every callsite:
At every mutation callback:
WhiteboardHistory.snapshotBefore(group.id());at the top of the callback (before any state change).WhiteboardSync.onUpdate(group);withpersistKanbanMutation(group);.The snapshot captures the full server-serialized state via
WhiteboardSync.serializeForServer(group)(history.js:17-21), which includes the kanban columns/cards data stored ongroup._kanbanState.commitUpdateno-ops if before/after are identical, so callbacks that bail out early (e.g. user dismissing an inline editor unchanged) push nothing onto the stack.The existing drag-reorder flow at
kanban.js:274-287, 326, 338already does the right thing — leave it alone; just make sure the helper does the sameonUpdate+commitUpdateorder so its behavior matches.Files to Modify
crates/hero_whiteboard_admin/static/web/js/whiteboard/kanban.jsImplementation Plan
Step 1: Add the helper
Insert near the top of the IIFE in
kanban.js, immediately after the imports / config constants:Step 2: Bracket each onUpdate callsite
For each of these line numbers (current state — may shift after edits), add
WhiteboardHistory.snapshotBefore(group.id());at the top of the enclosing callback and replace theWhiteboardSync.onUpdate(group);call withpersistKanbanMutation(group);::76-78— group drag-end has bothonUpdatecalls (object move + recomputeParentFrame). The firstonUpdateis already correctly paired with the existingsnapshotBeforeat line 72 andcommitUpdateat line 75. The secondonUpdate(group)fromrecomputeParentFrameshould remain as a plainonUpdate(the state diff is the same; no separate history entry needed).:199— edit column title inline editoronDone.:232— add column button click.:339— edit column color or similar inrenderColumn.:381, 400, 431— withinrenderColumn: column-title edit completion, delete column, etc. Wrap each.:564— withinrenderCard: card-text edit completion.:604— card delete button.:689, 708, 721, 729, 740, 748— context-menu actions (move up / move down / move to column / duplicate / delete).:879—deleteSelectedCard.:888—addCardToFirstColumn.For each: place the
snapshotBeforeat the earliest point inside the callback that runs unconditionally (so areturnin an early-exit branch doesn't leave a stale pending snapshot —commitUpdateignores the stale entry anyway, but cleaner this way).Already-correct sites to leave alone:
:274-287— column drag-reorder start. Keep its existingsnapshotBefore/commitUpdate. Replace theWhiteboardSync.onUpdate(group)if any inside this callback withpersistKanbanMutation(group)only if thecommitUpdatealready runs after — looking at the code,commitUpdateis called inside dragend; no change needed.:326, 338— these arecommitUpdatecalls in the dragend branches; leave alone.Acceptance Criteria
deleteSelectedCardandaddCardToFirstColumnshortcuts participate in undo.onUpdate(group)fires exactly once per mutation (no double emit).commitUpdateno-ops on identical before/after).Notes
snapshotserializes viaWhiteboardSync.serializeForServer(group), which for kanban widgets includes_kanbanState.columns(the whole shape). Undo restores the entire kanban widget data as a single update, which is exactly what we want.onUpdate(group)is the existing persistence path, used unchanged.onUpdate. Remote clients see oneobject.updatedper kanban mutation, same as today.Test Results + Final Summary
Changes
static/web/js/whiteboard/kanban.js:persistKanbanMutation(group)helper that runsWhiteboardHistory.commitUpdate(group.id())thenWhiteboardSync.onUpdate(group).snapshotBefore(group.id())...persistKanbanMutation(group)pattern.Sites wrapped (line numbers post-edit)
deleteSelectedCard(line 891-898).addCardToFirstColumn(line 905-908).Left intentionally alone
WhiteboardSync.onUpdate(group)afterrecomputeParentFrame(group)indragend(line 86) — same logical state, no separate history entry needed.commitUpdatecalls in column drag-reorder early-exit branches (lines 297, 336, 529, 549) — these are cleanup paths that bail without firingonUpdate.Behaviour after fix
Every kanban mutation now becomes a single undo step. Cancelled inline editors (Escape, blur with no change) push nothing because
commitUpdateno-ops on identical before/after snapshots. Multi-user broadcasts continue to fire exactly once per mutation.Gates
node -c kanban.js— JS syntax OKcargo fmt --check— passcargo clippy --workspace --all-targets -- -D warnings— passcargo test --workspace --lib— 0 tests, 0 failuresAcceptance Criteria
deleteSelectedCard/addCardToFirstColumnparticipate in undo.onUpdate(group)fires exactly once per mutation (no double emit).Manual verification still required
Rebuild + restart
hero_whiteboard_admin, hard-reload, open a board with a kanban widget, walk through each mutation and confirm Ctrl+Z / Ctrl+Y behave correctly.Out of scope (per the issue)
Mindmap / calendar / webframe URL / ungroup / selection-toolbar property edits — same class of issue, separate audit items.