From cf7c562d0f2c3c37b84ba8d674152e54d0e6a17f Mon Sep 17 00:00:00 2001 From: Chris0Jeky Date: Sun, 29 Mar 2026 14:51:01 +0100 Subject: [PATCH 1/4] fix: add confirmation dialog before card deletion (#513) Replace browser-native confirm() with TdDialog in CardModal. The dialog shows the card name, prevents double-submit via isDeleting guard, and is cancellable via Cancel button or backdrop click. Updates tests to drive the new dialog flow instead of spying on window.confirm. --- .../src/components/board/CardModal.vue | 47 +++++++++++++++++-- .../src/tests/components/CardModal.spec.ts | 27 +++++++---- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/frontend/taskdeck-web/src/components/board/CardModal.vue b/frontend/taskdeck-web/src/components/board/CardModal.vue index 6a5b941a4..0adeb8e89 100644 --- a/frontend/taskdeck-web/src/components/board/CardModal.vue +++ b/frontend/taskdeck-web/src/components/board/CardModal.vue @@ -3,6 +3,7 @@ import { onBeforeUnmount, ref, computed, watch } from 'vue' import { useBoardStore } from '../../store/boardStore' import { useSessionStore } from '../../store/sessionStore' import { useEscapeToClose } from '../../composables/useEscapeToClose' +import TdDialog from '../ui/TdDialog.vue' import type { Card, CardCaptureProvenance, Label } from '../../types/board' import type { CardComment } from '../../types/comments' import { normalizeProposalStatus } from '../../utils/automation' @@ -37,6 +38,8 @@ const captureProvenance = ref(null) const captureProvenanceError = ref(null) const loadingCaptureProvenance = ref(false) const loadedCaptureProvenanceCardId = ref(null) +const showDeleteConfirm = ref(false) +const isDeleting = ref(false) const comments = computed(() => boardStore.getCardComments(props.card.id)) const topLevelComments = computed(() => comments.value.filter(comment => !comment.parentCommentId)) @@ -158,15 +161,25 @@ async function handleSave() { } } -async function handleDelete() { - if (!confirm(`Are you sure you want to delete "${props.card.title}"?`)) return +function handleDeleteClick() { + showDeleteConfirm.value = true +} + +function handleDeleteCancel() { + showDeleteConfirm.value = false +} +async function handleDeleteConfirm() { + if (isDeleting.value) return + isDeleting.value = true try { await boardStore.deleteCard(props.card.boardId, props.card.id) + showDeleteConfirm.value = false emit('updated') emit('close') } catch (error) { console.error('Failed to delete card:', error) + isDeleting.value = false } } @@ -593,7 +606,7 @@ onBeforeUnmount(() => {
+ + + + + diff --git a/frontend/taskdeck-web/src/tests/components/CardModal.spec.ts b/frontend/taskdeck-web/src/tests/components/CardModal.spec.ts index e06710bff..b44832ab4 100644 --- a/frontend/taskdeck-web/src/tests/components/CardModal.spec.ts +++ b/frontend/taskdeck-web/src/tests/components/CardModal.spec.ts @@ -290,50 +290,59 @@ describe('CardModal', () => { expect((saveButton?.element as HTMLButtonElement).disabled).toBe(true) }) - it('should show delete confirmation before deleting', async () => { - const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false) - + it('should show delete confirmation dialog before deleting', async () => { const wrapper = mount(CardModal, { props: { card, isOpen: true, labels, }, + attachTo: document.body, }) const deleteButton = wrapper .findAll('button') .find((btn) => btn.text().includes('Delete Card')) await deleteButton?.trigger('click') + await wrapper.vm.$nextTick() - expect(confirmSpy).toHaveBeenCalled() + // Dialog should now be open; deleteCard must NOT have been called yet expect(mockStore.deleteCard).not.toHaveBeenCalled() + // The confirmation dialog should be visible in the DOM + expect(document.querySelector('.td-dialog')).not.toBeNull() - confirmSpy.mockRestore() + wrapper.unmount() }) - it('should call deleteCard when deletion is confirmed', async () => { - const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true) - + it('should call deleteCard when deletion is confirmed in dialog', async () => { const wrapper = mount(CardModal, { props: { card, isOpen: true, labels, }, + attachTo: document.body, }) + // Open the confirmation dialog const deleteButton = wrapper .findAll('button') .find((btn) => btn.text().includes('Delete Card')) await deleteButton?.trigger('click') + await wrapper.vm.$nextTick() + // Click the "Delete" confirm button inside the dialog + const confirmButton = Array.from(document.querySelectorAll('button')).find( + (btn) => btn.textContent?.trim() === 'Delete', + ) + confirmButton?.click() + await wrapper.vm.$nextTick() await wrapper.vm.$nextTick() expect(mockStore.deleteCard).toHaveBeenCalledWith('board-1', 'card-1') expect(wrapper.emitted('close')).toBeTruthy() - confirmSpy.mockRestore() + wrapper.unmount() }) it('should handle label selection', async () => { From c382ebea6fe33188475b57c0fbfb69186943fd95 Mon Sep 17 00:00:00 2001 From: Chris0Jeky Date: Sun, 29 Mar 2026 14:51:06 +0100 Subject: [PATCH 2/4] fix: prevent enter key propagation in column name form (#516) Add @keydown.enter.prevent on the column name input so pressing Enter submits the column creation form instead of bubbling to the board canvas underneath. --- frontend/taskdeck-web/src/views/BoardView.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/taskdeck-web/src/views/BoardView.vue b/frontend/taskdeck-web/src/views/BoardView.vue index 5e827e87b..e87293dae 100644 --- a/frontend/taskdeck-web/src/views/BoardView.vue +++ b/frontend/taskdeck-web/src/views/BoardView.vue @@ -317,6 +317,7 @@ useKeyboardShortcuts([ placeholder="Column name" class="td-column-form__input" autofocus + @keydown.enter.prevent="createColumn" />