Soft Delete & Trash
Safety net for accidental deletes — move tasks to Trash and restore them later.
Overview
Tasks are no longer permanently deleted when you click delete. Instead they're soft-deleted (moved to Trash) and can be restored or permanently removed.
Why
Accidental deletes happen. In a real project management tool, losing a task with all its context (description, comments, history) is unacceptable. Trash provides a safety net.
How It Works
Database
deleted_atcolumn added totaskstable (timestamptz, nullable, default null)- RLS SELECT policy split into two:
- Active tasks:
deleted_at IS NULL(normal board view) - Deleted tasks:
deleted_at IS NOT NULL(trash view)
- Active tasks:
- UPDATE policy allows setting
deleted_at(for soft-delete and restore) - DELETE policy allows permanent removal
Soft Delete Flow
- User clicks "Move to Trash" in task detail sheet
- Confirmation dialog appears
- On confirm:
UPDATE tasks SET deleted_at = now() WHERE id = ... - Task disappears from Kanban board
- Trash count badge updates in sidebar
Restore Flow
- Click Trash in sidebar (bottom-left, above user profile)
- Trash view dialog opens showing all deleted tasks
- Each task shows: title, priority badge, deleted date
- "Restore" button → sets
deleted_at = NULL, task reappears on board - "Delete Permanently" button → actual
DELETE FROM tasks, with confirmation
Empty Trash
- "Empty Trash" button at top of trash view
- Confirmation dialog warns this is irreversible
- Permanently deletes all trashed tasks for the project
UI Location
The Trash button lives in the sidebar (bottom-left, above the user profile section). This keeps it accessible from any page and follows the pattern of tools like Notion and Linear.
A badge shows the count of trashed items when > 0.
The sidebar communicates with the kanban board via custom events:
launchpad:trash-count— board broadcasts count to sidebarlaunchpad:open-trash— sidebar tells board to open trash view
Queries
| Function | Description |
|---|---|
deleteTask(supabase, id) | Soft delete (UPDATE deleted_at = now()) |
restoreTask(supabase, id) | Restore (UPDATE deleted_at = NULL) |
permanentlyDeleteTask(supabase, id) | Hard delete (DELETE) |
getDeletedTasks(supabase, projectId) | Fetch trashed tasks |
Key Files
| File | Purpose |
|---|---|
supabase/migrations/20260222_soft_delete.sql | Migration: deleted_at column + RLS |
src/components/board/trash-view.tsx | Trash dialog UI |
src/components/board/kanban-board-live.tsx | Trash count + event listeners |
src/components/layout/sidebar.tsx | Trash button in sidebar |