Kanban: cannot select a card to delete it with Delete key #48

Open
opened 2026-04-22 11:26:09 +00:00 by AhmedHanafy725 · 4 comments
Member

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).
  • The whiteboard-level Delete/Backspace in shortcuts.js routes to WhiteboardTools.deleteSelected() which operates on transformer.nodes() — currently only the kanban group can be transformer-selected.

Fix (sketch)

  • On cardRect click (not drag): mark it as the currently-selected kanban card (e.g. track group._selectedCardIdx / _selectedColIdx, redraw that cardRect with an accent stroke).
  • Clicking elsewhere in the kanban or outside clears the selection.
  • Add a Delete/Backspace key hook (in shortcuts.js widget branch for kanban) that removes the selected card from state, re-renders, syncs.
  • Keep existing whole-kanban group delete working: if no card is selected, Delete continues to remove the whole kanban.
## 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). - The whiteboard-level Delete/Backspace in `shortcuts.js` routes to `WhiteboardTools.deleteSelected()` which operates on transformer.nodes() — currently only the kanban group can be transformer-selected. ## Fix (sketch) - On cardRect click (not drag): mark it as the currently-selected kanban card (e.g. track `group._selectedCardIdx` / `_selectedColIdx`, redraw that cardRect with an accent stroke). - Clicking elsewhere in the kanban or outside clears the selection. - Add a Delete/Backspace key hook (in `shortcuts.js` widget branch for kanban) that removes the selected card from state, re-renders, syncs. - Keep existing whole-kanban group delete working: if no card is selected, Delete continues to remove the whole kanban.
Author
Member

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

  • Clicking a card (without initiating a drag) marks that card as the currently-selected kanban card; the card is redrawn with a visible accent stroke.
  • Only one card can be internally selected at a time, scoped to the kanban group that owns it.
  • Clicking elsewhere inside the kanban (background, column, other card, add buttons) or anywhere outside the kanban clears the internal card selection.
  • Starting a drag on the whole kanban group, or dragging a card between/within columns, also clears the internal card selection.
  • Delete or Backspace, when a card is internally selected on the currently transformer-selected kanban group, removes that card from state, re-renders, and syncs — without removing the whole kanban.
  • If no card is internally selected, Delete/Backspace on a selected kanban continues to behave as today (removes the whole kanban via WhiteboardTools.deleteSelected()).
  • The keyboard branch must only fire when the focus is not in an input/textarea/contenteditable (the existing guard in shortcuts.js already enforces this).
  • History (undo/redo) and sync (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 call WhiteboardHistory.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 a deleteSelectedCard(group) helper.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js - In the kanban branch of handleWidgetShortcut, 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.js

  • In createKanban(x, y, options) (around line 30–35, where group._kanbanState is initialized), initialize two fields on the group itself (not on _kanbanState, so they don't get serialized by sync.js): group._selectedColIdx = -1; and group._selectedCardIdx = -1;. These are ephemeral per-client UI state and must not leak into _kanbanState.
  • In renderCard(group, col, colIdx, card, cardIdx, cx, cy, columns, colW, cardH) (around lines 231–244), when constructing cardRect:
    • Determine isSelected = (group._selectedColIdx === colIdx && group._selectedCardIdx === cardIdx).
    • If selected: set stroke: '#007AFF' and strokeWidth: 2 on cardRect. Otherwise keep the existing stroke: '#495057' / strokeWidth: 1.
    • This has to happen inside renderCard so that a full renderKanban(group) re-draw (triggered by state mutations like card edit/add/move) correctly recomputes the accent based on the current _selectedColIdx/_selectedCardIdx.
  • Keep the existing hover mouseenter/mouseleave handlers on cardRect as-is; they only change fill, not stroke, 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.js

  • In renderCard, add a click tap handler on cardRect that:
    • Does NOT call e.cancelBubble = true — the stage-level click handler in tools.js should still run so the kanban group itself becomes transformer-selected (needed for getSelectedWidget() in shortcuts.js to see the kanban).
    • Sets group._selectedColIdx = colIdx; group._selectedCardIdx = cardIdx;.
    • Calls renderKanban(group); to re-draw with the accent stroke.
  • Konva's synthesized click event is suppressed automatically when the cardRect was 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 existing cardRect.on('dragstart', ...) and cardRect.on('dragend', ...) stay intact.
  • In the existing cardRect.on('dragstart', ...) handler, add group._selectedColIdx = -1; group._selectedCardIdx = -1; as the first line (after e.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.js

  • In renderKanban(group), add a click tap handler on the bg Konva.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.
    • Do NOT cancelBubble — the stage click handler should still run and keep the kanban selected via the transformer.
  • In createKanban, add to the existing group.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.
  • Add a stage-level deselection hook. In createKanban, after creating the group but before returning, attach a stage click tap listener that clears the kanban's internal card selection when the user clicks somewhere that is not inside this kanban group. Implementation approach:
    • Import stage via WhiteboardCanvas.getStage().
    • Use stage.on('click.kanban-' + id + ' tap.kanban-' + id, function(e) { ... }) — Konva namespaced events let us scope and remove cleanly.
    • Inside the handler: if e.target is not a descendant of group, and group._selectedCardIdx >= 0, reset the two _selected*Idx fields and call renderKanban(group).
    • Also hook 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) helper

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.js

  • Add a new private function deleteSelectedCard(group):
    function deleteSelectedCard(group) {
        var ci = group._selectedColIdx;
        var ri = group._selectedCardIdx;
        if (ci < 0 || ri < 0) return false;
        var state = group._kanbanState;
        if (!state || !state.columns[ci] || !state.columns[ci].cards[ri]) {
            group._selectedColIdx = -1;
            group._selectedCardIdx = -1;
            return false;
        }
        state.columns[ci].cards.splice(ri, 1);
        group._selectedColIdx = -1;
        group._selectedCardIdx = -1;
        renderKanban(group);
        refreshProperties(group);
        WhiteboardSync.onUpdate(group);
        return true;
    }
    
  • Return it from the module's public API (the return { ... } block): add deleteSelectedCard: deleteSelectedCard,.

Dependencies: Step 1.

Step 5: Route Delete/Backspace to deleteSelectedCard in the kanban shortcut branch

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js

  • In handleWidgetShortcut(e, key, widget), inside the existing if (type === 'kanban') { ... } block, add a new case BEFORE the existing Enter handler:
    if (key === 'delete' || key === 'backspace') {
        if (WhiteboardKanban.deleteSelectedCard && WhiteboardKanban.deleteSelectedCard(group)) {
            e.preventDefault();
            return true;
        }
        return false;
    }
    
  • Rationale for returning false on miss: the outer keydown handler's switch statement will then run its delete/backspace case and call WhiteboardTools.deleteSelected(), preserving whole-kanban delete when no card is internally selected.

Dependencies: Step 4.

Step 6: Manual verification

Files: none

  • Create a kanban, click a card — card shows blue accent stroke, kanban group also has transformer handles.
  • Press Delete — card is removed, kanban remains. Reload page — card stays deleted (sync persisted).
  • Click kanban background or another card — accent moves/clears appropriately.
  • Click card, then click empty stage area — kanban deselects, card accent clears; pressing Delete does nothing.
  • Click kanban group (not a card) — whole kanban selected, no internal card selected. Press Delete — whole kanban deleted (regression check).
  • Drag card between columns — internal selection clears; card ends up in target column with no leftover accent.
  • Confirm 3-dot menu -> Delete still works (unchanged code path).

Dependencies: Steps 1–5.

Acceptance Criteria

  • Clicking a kanban card outlines it with an accent stroke (distinct from the default #495057 border).
  • Pressing Delete or Backspace while a card is selected removes only that card and persists via WhiteboardSync.onUpdate.
  • Pressing Delete or Backspace when the kanban is selected but no card is internally selected removes the whole kanban (existing behavior preserved).
  • Clicking another card moves the accent; clicking the kanban background clears it; clicking outside the kanban clears it.
  • Dragging the kanban group or dragging a card between/within columns clears the internal selection (no stale accent on a re-rendered card).
  • _selectedColIdx / _selectedCardIdx are stored on the Konva group (not inside _kanbanState), so they do not leak into the serialized form written by sync.js.
  • The 3-dot menu -> Delete path continues to work unchanged.
  • No console errors when typing Delete/Backspace in an input (<input>, <textarea>, contenteditable).

Notes

  • Scope discipline: we're NOT extending selection to multi-card selection, shift-click range selection, keyboard navigation between cards (arrow keys), or history snapshots for card delete. This matches the issue's "Fix (sketch)" scope and avoids over-building.
  • Design choice: internal selection coexists with transformer selection of the kanban group. This lets getSelectedWidget() in shortcuts.js continue to identify the active widget via the transformer, while the widget itself owns a finer-grained secondary selection state.
  • Why store _selectedColIdx/_selectedCardIdx on group and not _kanbanState: sync.js serializes _kanbanState.columns/title/colWidth/cardHeight to the server. Putting UI-only state on group directly avoids persistence and collaborative-edit interference.
  • The existing "Delete" item in the 3-dot card menu does not currently call WhiteboardHistory.snapshotBefore / commitUpdate either, so deleteSelectedCard mirrors that behavior. Adding history support for all card-level edits is a separate improvement that should not be bundled into this bug fix.
  • No changes needed to tools.js: its existing stage.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.
## 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 - Clicking a card (without initiating a drag) marks that card as the currently-selected kanban card; the card is redrawn with a visible accent stroke. - Only one card can be internally selected at a time, scoped to the kanban group that owns it. - Clicking elsewhere inside the kanban (background, column, other card, add buttons) or anywhere outside the kanban clears the internal card selection. - Starting a drag on the whole kanban group, or dragging a card between/within columns, also clears the internal card selection. - Delete or Backspace, when a card is internally selected on the currently transformer-selected kanban group, removes that card from state, re-renders, and syncs — without removing the whole kanban. - If no card is internally selected, Delete/Backspace on a selected kanban continues to behave as today (removes the whole kanban via `WhiteboardTools.deleteSelected()`). - The keyboard branch must only fire when the focus is not in an input/textarea/contenteditable (the existing guard in `shortcuts.js` already enforces this). - History (undo/redo) and sync (`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 call `WhiteboardHistory.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 a `deleteSelectedCard(group)` helper. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js` - In the kanban branch of `handleWidgetShortcut`, 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.js` - In `createKanban(x, y, options)` (around line 30–35, where `group._kanbanState` is initialized), initialize two fields on the group itself (not on `_kanbanState`, so they don't get serialized by `sync.js`): `group._selectedColIdx = -1;` and `group._selectedCardIdx = -1;`. These are ephemeral per-client UI state and must not leak into `_kanbanState`. - In `renderCard(group, col, colIdx, card, cardIdx, cx, cy, columns, colW, cardH)` (around lines 231–244), when constructing `cardRect`: - Determine `isSelected = (group._selectedColIdx === colIdx && group._selectedCardIdx === cardIdx)`. - If selected: set `stroke: '#007AFF'` and `strokeWidth: 2` on `cardRect`. Otherwise keep the existing `stroke: '#495057'` / `strokeWidth: 1`. - This has to happen inside `renderCard` so that a full `renderKanban(group)` re-draw (triggered by state mutations like card edit/add/move) correctly recomputes the accent based on the current `_selectedColIdx/_selectedCardIdx`. - Keep the existing hover `mouseenter`/`mouseleave` handlers on `cardRect` as-is; they only change `fill`, not `stroke`, 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.js` - In `renderCard`, add a `click tap` handler on `cardRect` that: - Does NOT call `e.cancelBubble = true` — the stage-level click handler in `tools.js` should still run so the kanban group itself becomes transformer-selected (needed for `getSelectedWidget()` in `shortcuts.js` to see the kanban). - Sets `group._selectedColIdx = colIdx; group._selectedCardIdx = cardIdx;`. - Calls `renderKanban(group);` to re-draw with the accent stroke. - Konva's synthesized `click` event is suppressed automatically when the `cardRect` was 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 existing `cardRect.on('dragstart', ...)` and `cardRect.on('dragend', ...)` stay intact. - In the existing `cardRect.on('dragstart', ...)` handler, add `group._selectedColIdx = -1; group._selectedCardIdx = -1;` as the first line (after `e.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.js` - In `renderKanban(group)`, add a `click tap` handler on the `bg` Konva.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. - Do NOT `cancelBubble` — the stage click handler should still run and keep the kanban selected via the transformer. - In `createKanban`, add to the existing `group.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. - Add a stage-level deselection hook. In `createKanban`, after creating the group but before returning, attach a stage `click tap` listener that clears the kanban's internal card selection when the user clicks somewhere that is not inside this kanban group. Implementation approach: - Import `stage` via `WhiteboardCanvas.getStage()`. - Use `stage.on('click.kanban-' + id + ' tap.kanban-' + id, function(e) { ... })` — Konva namespaced events let us scope and remove cleanly. - Inside the handler: if `e.target` is not a descendant of `group`, and `group._selectedCardIdx >= 0`, reset the two `_selected*Idx` fields and call `renderKanban(group)`. - Also hook `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)` helper Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/kanban.js` - Add a new private function `deleteSelectedCard(group)`: ``` function deleteSelectedCard(group) { var ci = group._selectedColIdx; var ri = group._selectedCardIdx; if (ci < 0 || ri < 0) return false; var state = group._kanbanState; if (!state || !state.columns[ci] || !state.columns[ci].cards[ri]) { group._selectedColIdx = -1; group._selectedCardIdx = -1; return false; } state.columns[ci].cards.splice(ri, 1); group._selectedColIdx = -1; group._selectedCardIdx = -1; renderKanban(group); refreshProperties(group); WhiteboardSync.onUpdate(group); return true; } ``` - Return it from the module's public API (the `return { ... }` block): add `deleteSelectedCard: deleteSelectedCard,`. Dependencies: Step 1. #### Step 5: Route Delete/Backspace to `deleteSelectedCard` in the kanban shortcut branch Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js` - In `handleWidgetShortcut(e, key, widget)`, inside the existing `if (type === 'kanban') { ... }` block, add a new case BEFORE the existing `Enter` handler: ``` if (key === 'delete' || key === 'backspace') { if (WhiteboardKanban.deleteSelectedCard && WhiteboardKanban.deleteSelectedCard(group)) { e.preventDefault(); return true; } return false; } ``` - Rationale for returning `false` on miss: the outer `keydown` handler's switch statement will then run its `delete`/`backspace` case and call `WhiteboardTools.deleteSelected()`, preserving whole-kanban delete when no card is internally selected. Dependencies: Step 4. #### Step 6: Manual verification Files: none - Create a kanban, click a card — card shows blue accent stroke, kanban group also has transformer handles. - Press Delete — card is removed, kanban remains. Reload page — card stays deleted (sync persisted). - Click kanban background or another card — accent moves/clears appropriately. - Click card, then click empty stage area — kanban deselects, card accent clears; pressing Delete does nothing. - Click kanban group (not a card) — whole kanban selected, no internal card selected. Press Delete — whole kanban deleted (regression check). - Drag card between columns — internal selection clears; card ends up in target column with no leftover accent. - Confirm 3-dot menu -> Delete still works (unchanged code path). Dependencies: Steps 1–5. ### Acceptance Criteria - [ ] Clicking a kanban card outlines it with an accent stroke (distinct from the default `#495057` border). - [ ] Pressing Delete or Backspace while a card is selected removes only that card and persists via `WhiteboardSync.onUpdate`. - [ ] Pressing Delete or Backspace when the kanban is selected but no card is internally selected removes the whole kanban (existing behavior preserved). - [ ] Clicking another card moves the accent; clicking the kanban background clears it; clicking outside the kanban clears it. - [ ] Dragging the kanban group or dragging a card between/within columns clears the internal selection (no stale accent on a re-rendered card). - [ ] `_selectedColIdx` / `_selectedCardIdx` are stored on the Konva `group` (not inside `_kanbanState`), so they do not leak into the serialized form written by `sync.js`. - [ ] The 3-dot menu -> Delete path continues to work unchanged. - [ ] No console errors when typing Delete/Backspace in an input (`<input>`, `<textarea>`, `contenteditable`). ### Notes - Scope discipline: we're NOT extending selection to multi-card selection, shift-click range selection, keyboard navigation between cards (arrow keys), or history snapshots for card delete. This matches the issue's "Fix (sketch)" scope and avoids over-building. - Design choice: internal selection coexists with transformer selection of the kanban group. This lets `getSelectedWidget()` in `shortcuts.js` continue to identify the active widget via the transformer, while the widget itself owns a finer-grained secondary selection state. - Why store `_selectedColIdx`/`_selectedCardIdx` on `group` and not `_kanbanState`: `sync.js` serializes `_kanbanState.columns/title/colWidth/cardHeight` to the server. Putting UI-only state on `group` directly avoids persistence and collaborative-edit interference. - The existing "Delete" item in the 3-dot card menu does not currently call `WhiteboardHistory.snapshotBefore` / `commitUpdate` either, so `deleteSelectedCard` mirrors that behavior. Adding history support for all card-level edits is a separate improvement that should not be bundled into this bug fix. - No changes needed to `tools.js`: its existing `stage.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.
Author
Member

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.

Check Result
cargo check --workspace pass
cargo test --workspace pass (0 executed, 1 doctest ignored)
cargo clippy --workspace -- -D warnings pass
cargo fmt --check pass

The project has no Rust unit tests exercising the JS modules (the UI crate's src/lib.rs is 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.

## 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. | Check | Result | |---|---| | `cargo check --workspace` | pass | | `cargo test --workspace` | pass (0 executed, 1 doctest ignored) | | `cargo clippy --workspace -- -D warnings` | pass | | `cargo fmt --check` | pass | The project has no Rust unit tests exercising the JS modules (the UI crate's `src/lib.rs` is 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.
Author
Member

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

  • Clicking a Kanban card marks it internally selected and re-renders it with a blue accent stroke (#007AFF, width 2).
  • The internal selection state is stored directly on the Konva group (_selectedColIdx, _selectedCardIdx) and is intentionally not placed inside _kanbanState, so it is never serialized by sync.js and cannot leak to other collaborators.
  • Internal selection coexists with the transformer selection of the kanban group: the card-click handler does not cancel bubbling, so tools.js still selects the kanban via the transformer. shortcuts.js locates the widget through the transformer as before.
  • Pressing Delete or Backspace while a card is internally selected removes only that card, re-renders, refreshes the property panel, and calls WhiteboardSync.onUpdate(group) — matching the behavior of the existing 3-dot menu Delete path.
  • Pressing Delete or Backspace when a kanban is transformer-selected but no card is internally selected continues to delete the whole kanban via WhiteboardTools.deleteSelected().
  • Internal selection is cleared on: clicking the kanban background, starting a drag on the whole kanban group, starting a drag on a card, clicking outside the kanban (stage-level namespaced listener), and card deletion. The stage listener is removed in the destroy hook so it does not leak after the kanban is deleted.

Notes / deliberately out of scope

  • History (undo/redo) is not wired into this card-delete path. The existing 3-dot menu Delete also does not snapshot history, so the new path mirrors that for consistency. Adding proper history support for all card-level edits is a separate, broader improvement.
  • No multi-card selection, shift/ctrl-click range selection, or arrow-key navigation between cards were added; the issue's fix sketch did not ask for these.

Test results

  • cargo check --workspace: pass
  • cargo test --workspace: pass
  • cargo clippy --workspace -- -D warnings: pass
  • cargo fmt --check: pass

JavaScript-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).

## 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 - Clicking a Kanban card marks it internally selected and re-renders it with a blue accent stroke (`#007AFF`, width 2). - The internal selection state is stored directly on the Konva group (`_selectedColIdx`, `_selectedCardIdx`) and is intentionally not placed inside `_kanbanState`, so it is never serialized by `sync.js` and cannot leak to other collaborators. - Internal selection coexists with the transformer selection of the kanban group: the card-click handler does not cancel bubbling, so `tools.js` still selects the kanban via the transformer. `shortcuts.js` locates the widget through the transformer as before. - Pressing Delete or Backspace while a card is internally selected removes only that card, re-renders, refreshes the property panel, and calls `WhiteboardSync.onUpdate(group)` — matching the behavior of the existing 3-dot menu Delete path. - Pressing Delete or Backspace when a kanban is transformer-selected but no card is internally selected continues to delete the whole kanban via `WhiteboardTools.deleteSelected()`. - Internal selection is cleared on: clicking the kanban background, starting a drag on the whole kanban group, starting a drag on a card, clicking outside the kanban (stage-level namespaced listener), and card deletion. The stage listener is removed in the `destroy` hook so it does not leak after the kanban is deleted. ### Notes / deliberately out of scope - History (undo/redo) is not wired into this card-delete path. The existing 3-dot menu Delete also does not snapshot history, so the new path mirrors that for consistency. Adding proper history support for all card-level edits is a separate, broader improvement. - No multi-card selection, shift/ctrl-click range selection, or arrow-key navigation between cards were added; the issue's fix sketch did not ask for these. ### Test results - `cargo check --workspace`: pass - `cargo test --workspace`: pass - `cargo clippy --workspace -- -D warnings`: pass - `cargo fmt --check`: pass JavaScript-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).
Author
Member

Pull request opened: #62

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_whiteboard/pulls/62 This PR implements the changes discussed in this issue.
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#48
No description provided.