feat: offer undo at the moment something is removed

Undo existed but nothing said so. The only route was Cmd/Ctrl+Z, which was
documented in a README nobody reads mid-task, and which does not exist on
a phone at all. On touch there was simply no way to get a deleted task
back.

Deleting a task or clearing completed ones now brings up a short-lived bar
with a real Undo button, so touch has a route for the first time. It also
shows the keyboard shortcut, which is the point: someone learns it once,
here, at the only moment they actually care.

It uses the existing tokens, sits out of the way at the bottom, clears
itself after seven seconds and goes as soon as it is used. The shortcut
badge is hidden where no keyboard exists rather than advertising a chord
nobody can press.
This commit is contained in:
Aculix Technologies
2026-08-16 03:31:08 +05:30
parent 02c8d6f6b0
commit d97550feb8
3 changed files with 116 additions and 2 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ Built with Svelte for speed and simplicity. No overwhelming features, no endless
### Core Functionality
-**Add, complete, and delete tasks** with smooth animations
- ↩️ **Undo** - `Cmd/Ctrl+Z` brings back a deleted task, in its original position
- ↩️ **Undo** - Deleted something by mistake? An Undo button appears, and `Cmd/Ctrl+Z` works too. The task returns to its original position
- 📅 **Today & Tomorrow lists** - Plan ahead with separate task lists
- 🔄 **Unfinished work carries over** - When a new day begins, tasks you didn't finish move to Today; completed ones are cleared away
- ✏️ **Edit a task** - Fix a typo without deleting and retyping it
@@ -92,7 +92,7 @@ The date, storage, rollover, task and undo logic lives in `src/lib/` as plain mo
- **Complete a task**: Click the checkbox next to the task
- **Edit a task**: Click its text. Enter saves, Escape cancels, and clicking away saves too
- **Delete a task**: Click the delete icon on the task (it shows on hover, and is always visible on touch), or press `Delete` with the task focused
- **Undo a delete**: Press `Cmd/Ctrl+Z`. The task returns to where it was
- **Undo a delete**: An Undo button appears for a few seconds after anything is removed. `Cmd/Ctrl+Z` does the same. Either way the task returns to where it was
- **Move a task to Tomorrow**: Click the arrow on the task, or press `Alt+→`. From Tomorrow, the arrow points back and `Alt+←` returns it to Today
- **Reorder tasks**: Drag with a mouse, long-press then drag on touch, or focus a task and press `Alt+↑`/`Alt+↓`
- **Clear input**: Press Escape while the input is focused
+37
View File
@@ -62,6 +62,7 @@
if (index === -1) return;
undoStack.push({ type: 'delete', task: tasks[index], index });
setTasks(taskOps.deleteTask(tasks, id));
showUndo('Task deleted');
}
function clearCompleted() {
@@ -74,6 +75,7 @@
if (removed.length === 0) return;
undoStack.push({ type: 'clearCompleted', removed });
setTasks(taskOps.clearCompleted(tasks));
showUndo(removed.length === 1 ? '1 completed task cleared' : `${removed.length} completed tasks cleared`);
}
const viewingToday = $derived(selectedKey === todayKey);
@@ -129,9 +131,35 @@
tasks = moveTaskToDay(storage, selectedKey, destination, taskId);
}
/**
* Undo used to be reachable only by Cmd/Ctrl+Z, which nothing announced and
* which does not exist on a phone at all. This is the one moment it is worth
* saying something: right after work disappears. It carries the shortcut too,
* so a keyboard user learns it once, here, rather than from a README.
*/
let undoNotice = $state(null);
let undoNoticeTimer = null;
const UNDO_NOTICE_MS = 7000;
const shortcutLabel = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)
? '\u2318Z'
: 'Ctrl+Z';
function showUndo(message) {
undoNotice = message;
clearTimeout(undoNoticeTimer);
undoNoticeTimer = setTimeout(() => { undoNotice = null; }, UNDO_NOTICE_MS);
}
function dismissUndo() {
clearTimeout(undoNoticeTimer);
undoNotice = null;
}
function undo() {
const entry = undoStack.pop();
if (entry) setTasks(applyUndo(tasks, entry));
dismissUndo();
}
function toggleTheme() {
@@ -736,3 +764,12 @@
</div>
</main>
</div>
{#if undoNotice}
<div class="undo-toast" role="status" aria-live="polite" transition:fly={{ y: reduceMotion ? 0 : 12, duration: reduceMotion ? 0 : 200, easing: cubicOut }}>
<span class="undo-message">{undoNotice}</span>
<button class="undo-action" onclick={undo}>
Undo <kbd class="undo-key">{shortcutLabel}</kbd>
</button>
</div>
{/if}
+77
View File
@@ -701,3 +701,80 @@ html {
scroll-behavior: smooth;
}
/* Shown only after something is removed, and only long enough to act on.
It carries a real button because Cmd/Ctrl+Z does not exist on a phone,
so on touch this is the only way back. */
.undo-toast {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: calc(24px + env(safe-area-inset-bottom));
z-index: 200;
display: flex;
align-items: center;
gap: 16px;
max-width: calc(100vw - 32px);
padding: 12px 12px 12px 18px;
border-radius: 12px;
background-color: var(--bg-surface);
border: 1px solid var(--border);
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.16),
0 4px 8px rgba(0, 0, 0, 0.08);
font-size: 14px;
color: var(--text-primary);
}
html.dark .undo-toast {
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.55),
0 4px 8px rgba(0, 0, 0, 0.4);
}
.undo-message {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.undo-action {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
padding: 6px 12px;
border: none;
border-radius: 8px;
background: transparent;
font-family: inherit;
font-size: 14px;
font-weight: 500;
color: var(--accent-text);
cursor: pointer;
transition: background-color 200ms ease;
}
.undo-action:hover {
background-color: var(--hover);
}
/* Teaching the shortcut is the point, so it only shows where one exists. */
.undo-key {
font-family: var(--font-family);
font-size: 12px;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
}
@media (pointer: coarse) {
.undo-key {
display: none;
}
.undo-action {
min-height: 44px;
}
}