Kanban: cannot select a card to delete it with Delete key #48
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#48
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
Kanban cards cannot be selected individually. The only way to delete a card today is 3-dot menu -> Delete. Users expect to click a card to select it, then press Delete/Backspace to remove it.
Evidence
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.js,renderCard()(around line 218): cardRect has no selection state; clicking it does nothing (except start a drag if moved).shortcuts.jsroutes toWhiteboardTools.deleteSelected()which operates on transformer.nodes() — currently only the kanban group can be transformer-selected.Fix (sketch)
group._selectedCardIdx/_selectedColIdx, redraw that cardRect with an accent stroke).shortcuts.jswidget branch for kanban) that removes the selected card from state, re-renders, syncs.Implementation Spec for Issue #48
Objective
Enable per-card selection in the Kanban widget so users can click a card to select it and press Delete/Backspace to remove it, while preserving the existing whole-kanban delete behavior when no card is internally selected.
Requirements
WhiteboardTools.deleteSelected()).shortcuts.jsalready enforces this).WhiteboardSync.onUpdate) must be called on card delete, consistent with how the 3-dot menu's "Delete" currently works (note: the existing menu delete does not callWhiteboardHistory.snapshotBefore/commitUpdate— we'll match that behavior for minimal scope and not fix it in this bug).Files to Modify/Create
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.js- Add internal card selection state on the kanban group, wire card-click to set it, clear it on relevant events, render selected card with an accent stroke, and expose adeleteSelectedCard(group)helper.crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js- In the kanban branch ofhandleWidgetShortcut, intercept Delete/Backspace before the default group-delete path when a card is internally selected.No files need to be created.
Implementation Plan
Step 1: Add internal selection state and accent rendering to cards
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.jscreateKanban(x, y, options)(around line 30–35, wheregroup._kanbanStateis initialized), initialize two fields on the group itself (not on_kanbanState, so they don't get serialized bysync.js):group._selectedColIdx = -1;andgroup._selectedCardIdx = -1;. These are ephemeral per-client UI state and must not leak into_kanbanState.renderCard(group, col, colIdx, card, cardIdx, cx, cy, columns, colW, cardH)(around lines 231–244), when constructingcardRect:isSelected = (group._selectedColIdx === colIdx && group._selectedCardIdx === cardIdx).stroke: '#007AFF'andstrokeWidth: 2oncardRect. Otherwise keep the existingstroke: '#495057'/strokeWidth: 1.renderCardso that a fullrenderKanban(group)re-draw (triggered by state mutations like card edit/add/move) correctly recomputes the accent based on the current_selectedColIdx/_selectedCardIdx.mouseenter/mouseleavehandlers oncardRectas-is; they only changefill, notstroke, so they don't conflict with the selection accent.Dependencies: none.
Step 2: Wire card click to set internal selection
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.jsrenderCard, add aclick taphandler oncardRectthat:e.cancelBubble = true— the stage-level click handler intools.jsshould still run so the kanban group itself becomes transformer-selected (needed forgetSelectedWidget()inshortcuts.jsto see the kanban).group._selectedColIdx = colIdx; group._selectedCardIdx = cardIdx;.renderKanban(group);to re-draw with the accent stroke.clickevent is suppressed automatically when thecardRectwas actually dragged (it fires only when mouseup happens close to mousedown without an intervening drag). So no manual click-vs-drag discrimination is required — the existingcardRect.on('dragstart', ...)andcardRect.on('dragend', ...)stay intact.cardRect.on('dragstart', ...)handler, addgroup._selectedColIdx = -1; group._selectedCardIdx = -1;as the first line (aftere.cancelBubble = true). This ensures dragging a card clears any previous selection so it doesn't visually linger on the wrong card after re-render.Dependencies: Step 1.
Step 3: Clear internal selection on unrelated interactions
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.jsrenderKanban(group), add aclick taphandler on thebgKonva.Rect that, when the user clicks the kanban background (i.e. not a card), clears the internal selection:group._selectedColIdx = -1; group._selectedCardIdx = -1;renderKanban(group);to repaint cards without the accent.cancelBubble— the stage click handler should still run and keep the kanban selected via the transformer.createKanban, add to the existinggroup.on('dragstart', ...)callback a line clearing internal card selection at the top:group._selectedColIdx = -1; group._selectedCardIdx = -1;. This handles dragging the entire kanban around.createKanban, after creating the group but before returning, attach a stageclick taplistener that clears the kanban's internal card selection when the user clicks somewhere that is not inside this kanban group. Implementation approach:stageviaWhiteboardCanvas.getStage().stage.on('click.kanban-' + id + ' tap.kanban-' + id, function(e) { ... })— Konva namespaced events let us scope and remove cleanly.e.targetis not a descendant ofgroup, andgroup._selectedCardIdx >= 0, reset the two_selected*Idxfields and callrenderKanban(group).group.on('destroy', function() { stage.off('click.kanban-' + id + ' tap.kanban-' + id); });to clean up the listener when the kanban is deleted.Dependencies: Step 1.
Step 4: Expose
deleteSelectedCard(group)helperFiles:
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.jsdeleteSelectedCard(group):return { ... }block): adddeleteSelectedCard: deleteSelectedCard,.Dependencies: Step 1.
Step 5: Route Delete/Backspace to
deleteSelectedCardin the kanban shortcut branchFiles:
crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.jshandleWidgetShortcut(e, key, widget), inside the existingif (type === 'kanban') { ... }block, add a new case BEFORE the existingEnterhandler:falseon miss: the outerkeydownhandler's switch statement will then run itsdelete/backspacecase and callWhiteboardTools.deleteSelected(), preserving whole-kanban delete when no card is internally selected.Dependencies: Step 4.
Step 6: Manual verification
Files: none
Dependencies: Steps 1–5.
Acceptance Criteria
#495057border).WhiteboardSync.onUpdate._selectedColIdx/_selectedCardIdxare stored on the Konvagroup(not inside_kanbanState), so they do not leak into the serialized form written bysync.js.<input>,<textarea>,contenteditable).Notes
getSelectedWidget()inshortcuts.jscontinue to identify the active widget via the transformer, while the widget itself owns a finer-grained secondary selection state._selectedColIdx/_selectedCardIdxongroupand not_kanbanState:sync.jsserializes_kanbanState.columns/title/colWidth/cardHeightto the server. Putting UI-only state ongroupdirectly avoids persistence and collaborative-edit interference.WhiteboardHistory.snapshotBefore/commitUpdateeither, sodeleteSelectedCardmirrors that behavior. Adding history support for all card-level edits is a separate improvement that should not be bundled into this bug fix.tools.js: its existingstage.on('click tap', ...)handler correctly handles transformer selection of the kanban group when a card is clicked, because we deliberately do NOT cancel bubbling on the cardRect click.Test Results
The implementation only touches JavaScript in
crates/hero_whiteboard_ui/static/web/js/whiteboard/{kanban.js,shortcuts.js}; the Rust workspace was validated for regressions via the standard CI checks.cargo check --workspacecargo test --workspacecargo clippy --workspace -- -D warningscargo fmt --checkThe project has no Rust unit tests exercising the JS modules (the UI crate's
src/lib.rsis empty of tests), so the test suite results reflect regression safety rather than direct coverage of this fix. Manual verification steps against a running UI are listed in the spec's Step 6 and will be performed before merge.Implementation Summary
Per-card selection and keyboard delete are now supported on the Kanban widget, while the existing whole-kanban delete path is preserved as a fallback.
Files changed
crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.js(+64 / -2)crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js(+7)Behavior
#007AFF, width 2)._selectedColIdx,_selectedCardIdx) and is intentionally not placed inside_kanbanState, so it is never serialized bysync.jsand cannot leak to other collaborators.tools.jsstill selects the kanban via the transformer.shortcuts.jslocates the widget through the transformer as before.WhiteboardSync.onUpdate(group)— matching the behavior of the existing 3-dot menu Delete path.WhiteboardTools.deleteSelected().destroyhook so it does not leak after the kanban is deleted.Notes / deliberately out of scope
Test results
cargo check --workspace: passcargo test --workspace: passcargo clippy --workspace -- -D warnings: passcargo fmt --check: passJavaScript-only change; no Rust tests directly exercise it. Manual QA against a running UI follows the verification list in the spec (create card, click to select, press Delete, drag card, click outside, regression-check whole-kanban delete).
Pull request opened: #62
This PR implements the changes discussed in this issue.