delete animation, no #inbox tag, preset delete lock on builtins

Card delete animation (public/css/style.css, public/js/modules/board.js):
* @keyframes card-remove: opacity 1→0 + scale(1)→0.85 + translateY(8px),
  250ms ease-in forwards. .card-removing class with pointer-events:none.
* New exported animateCardRemoval(ids) in board.js: adds .card-removing
  to each matching board-card element, resolves after 260ms so the
  caller can await before the API DELETE + _reloadAll.
* Wired into all three delete paths:
  - context-menu.js group delete: animates parent + all children
  - context-menu.js single delete: same path (allCards has one entry)
  - app.js Del/Backspace shortcut: animates all selected cards

Quick capture no #inbox (public/js/app.js):
* Removed the line that auto-injected #inbox into the body of every
  quick-captured card. Cards now land tag-free in the active bucket
  (or unsorted).

Settings: preset delete lock (public/js/modules/theme.js):
* refreshPresetSelect() now disables the delete button (disabled attr
  + opacity 0.35 + cursor not-allowed) when a builtin: preset is
  selected. Re-enabled when a user: preset is selected. The click
  handler already had a startsWith('user:') guard but the button
  wasn't visually locked.

chore: bump version to 0.9.17

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-12 11:25:57 +02:00
1 parent ce6ca73ea0
commit 195b0e343f
6 files changed
+35 -5

No files matched your search

+12
View File
@@ -38,6 +38,18 @@ const draggingCardIds = new Set();
// the all view) or wait for the next visit to the all tab to pulse.
let knownCardIds = null;
/** Animate card removal: adds .card-removing to each matching card
* element and waits for the CSS animation to finish before resolving.
* Call this BEFORE the API delete + _reloadAll so the user sees the
* card shrink/fade out instead of just vanishing. */
export function animateCardRemoval(ids) {
if (!ids || !ids.length) return Promise.resolve();
const els = ids.map(id => board.querySelector(`[data-id="${id}"]`)).filter(Boolean);
if (!els.length) return Promise.resolve();
for (const el of els) el.classList.add('card-removing');
return new Promise(r => setTimeout(r, 260));
}
/** Add a pulse class to matching board cards and strip it after the
* animation. `cls` controls the variant: 'card-pulse-new' (3×, for
* relay-synced cards) or 'card-pulse-local' (1×, for cards the user
+3 -1
View File
@@ -397,7 +397,9 @@ export function showContextMenu(x, y, card) {
}
}
});
// Delete children first, then parent (avoids orphan flicker)
// Animate out, then delete children first, then parent
const { animateCardRemoval } = await import('./board.js');
await animateCardRemoval(allCards.map(c => c.id));
for (const c of children) await api(`api/cards/${c.id}`, { method: 'DELETE' });
await api(`api/cards/${card.id}`, { method: 'DELETE' });
await window._reloadAll();
+8
View File
@@ -258,6 +258,14 @@ function refreshPresetSelect() {
sel.appendChild(userGroup);
}
if (cur && sel.querySelector(`option[value="${CSS.escape(cur)}"]`)) sel.value = cur;
// Disable delete button when a built-in preset is selected
const delBtn = $('design-preset-delete');
if (delBtn) {
const isUser = sel.value?.startsWith('user:');
delBtn.disabled = !isUser;
delBtn.style.opacity = isUser ? '' : '0.35';
delBtn.style.cursor = isUser ? '' : 'not-allowed';
}
}
function getSelectedPreset() {