From 79ec5114821439dde3c37d423eab8bf60e099cd6 Mon Sep 17 00:00:00 2001 From: dtoro Date: Mon, 18 May 2026 00:03:38 +0200 Subject: [PATCH] fix(timeline): clear selection after keyboard archive/delete/approve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X (archive/restore), Delete, and S (approve) keyboard handlers in gridKeyNav advanced focus and invalidated the photos query but never cleared the selection — so the archived/deleted/approved UIDs stayed in the SvelteSet and kept their rings on tiles that hadn't unmounted yet. A subsequent Ctrl-click would then pile new UIDs on top of the stale set, leaving the user uncertain which photos a follow-up action would actually target. The BulkActionBar button path already cleared selection for the same reason; mirror that here. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/actions/gridKeyNav.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/web/src/lib/actions/gridKeyNav.ts b/web/src/lib/actions/gridKeyNav.ts index 274626a..cdfe907 100644 --- a/web/src/lib/actions/gridKeyNav.ts +++ b/web/src/lib/actions/gridKeyNav.ts @@ -225,6 +225,13 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) { // archived/restored — relevant when the cull targets came from a // multi-selection rather than the single focused tile. focusAfter(ids); + // Drop the now-stale selection set. The archived UIDs are about + // to leave the timeline on refetch, but the SvelteSet membership + // keeps the selection ring on them until then — confusing for + // the user and a footgun if they Ctrl-click to add more and end + // up re-archiving the same photos. The BulkActionBar button path + // clears for the same reason; mirror it here. + clearSelection(); invalidatePhotos(ids); const label = target ? `Archived ${ids.length}` : `Restored ${ids.length}`; toast.success(label); @@ -259,6 +266,8 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) { toast.error(err instanceof Error ? err.message : 'Delete failed'); return; } + focusAfter(ids); + clearSelection(); invalidatePhotos(ids); toast.success(`Deleted ${ids.length}`); } @@ -277,6 +286,12 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) { return; } const { updated, errors } = await batchEdit(ids, (id) => approvePhoto(id)); + // Approve moves photos out of the review pile, so the same + // stale-selection trap as archive/delete applies — advance focus + // past the approved set and drop the now-irrelevant selection + // before invalidate refetches the (smaller) view. + focusAfter(ids); + clearSelection(); invalidatePhotos(ids); if (errors.length) { toast.error(`Kept ${updated.length}; ${errors.length} failed`, {