fix: bulk label apply, instant archive removal, per-photo state, drop sidebar counts

Issue 1 — colors/labels not applying in bulk:
- sidecar validColors only accepted 4 of the 8 UI swatches, so teal/blue/
  purple/pink returned "invalid color" and rolled back the whole bulk txn.
  Add teal, blue, purple, pink to validColors.
- Add invalidateFacets() and call it on the success path of bulk marks,
  patchTargets, and single-photo edits so the Colors/Ratings/Notes facet
  sections refresh immediately instead of waiting out staleTime.

Issue 2 — archived photos linger in the grid:
- Add a UI-only removedIds set to the bulkAction store; archive/delete/
  restore/keep call markRemoved() on success so tiles vanish instantly,
  cleared once the server-reconcile refetch lands (no cache eviction).

Issue 3 — per-photo progress state:
- Wire startBulk/doneBulk/failBulk into all metadata applies, bulk
  (BulkMetadataSidebar) and single (RightSidebar), so colors/ratings/
  notes/dates/keywords show the spinner -> check -> X overlay.

Issue 4 — remove Left-sidebar count badges:
- Drop count badges from root folder, Archive, heaps, Notes, and the
  folder tree, plus the now-dead count queries and unused imports. Facet
  drill-panel counts are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 00:21:04 +02:00
parent a13e171295
commit ccf2c6b7c7
8 changed files with 161 additions and 251 deletions

View File

@@ -26,7 +26,14 @@
import { filters } from '$lib/stores/filters.svelte';
import { push as pushUndo } from '$lib/stores/undo.svelte';
import { isAuthenticated } from '$lib/stores/session.svelte';
import { startBulk, setDetail, doneBulk, failBulk } from '$lib/stores/bulkAction.svelte';
import {
startBulk,
setDetail,
doneBulk,
failBulk,
markRemoved,
clearRemoved
} from '$lib/stores/bulkAction.svelte';
import { EmptyState, InlineLoader } from '$lib/components/feedback';
import { Layers } from 'lucide-svelte';
@@ -137,9 +144,15 @@
throw e;
} finally {
busy = false;
void qc.invalidateQueries({ queryKey: ['photos'] });
void qc.invalidateQueries({ queryKey: ['marks'] });
void qc.invalidateQueries({ queryKey: ['review-groups'] });
const settled = Promise.all([
qc.invalidateQueries({ queryKey: ['photos'] }),
qc.invalidateQueries({ queryKey: ['marks'] }),
qc.invalidateQueries({ queryKey: ['review-groups'] })
]);
// Clear the optimistic-removal overlay only once the refetch has
// landed, so tiles never flash back in before the fresh (archived-
// filtered) page replaces the old one.
if (bulk) void settled.then(() => clearRemoved(bulk.ids));
}
}
@@ -159,6 +172,8 @@
} else {
toast.success(`Kept ${ids.length}`, { id: tid });
}
// Approved photos leave the review section — hide them immediately.
markRemoved(ids);
focusAfter(ids);
clearSelection();
}, { ids, label: 'Keeping', doneLabel: `Kept ${ids.length}` });
@@ -181,6 +196,7 @@
await withBusy(async () => {
try {
await batchArchive(ids);
markRemoved(ids);
pushUndo(`Archived ${ids.length}`, async () => {
await batchRestore(ids);
void qc.invalidateQueries({ queryKey: ['photos'] });
@@ -206,6 +222,7 @@
await withBusy(async () => {
try {
await batchDelete(ids);
markRemoved(ids);
focusAfter(ids);
clearSelection();
toast.success(`Deleted ${ids.length}`, { id: tid });
@@ -222,6 +239,7 @@
await withBusy(async () => {
try {
await batchRestore(ids);
markRemoved(ids);
pushUndo(`Restored ${ids.length}`, async () => {
await batchArchive(ids);
void qc.invalidateQueries({ queryKey: ['photos'] });