refactor: merge reject into trash as a single soft-trash concept

The Photo model previously had two near-identical "negative culling"
states: is_rejected (a flag) and is_trashed (a flag plus a file move).
Lightroom users typically use one or the other, never both, and the
file-move semantics of the old trash made it harder to undo. Merging
into a single soft is_trashed flag — file stays on disk, restore is a
flag flip, permanent deletion still happens via DELETE /trash/empty.

Backend
- Drop is_rejected from PhotoBase, PhotoResponse, PhotoUpdate, the
  list endpoint filter, and the bulk-action 'reject' branch.
- Add is_trashed to PhotoUpdate so the PATCH path can set it.
- Drop is_rejected Column declaration from the SQLAlchemy model. The
  legacy DB column may persist on existing installs but is no longer
  read or written; SQLAlchemy ignores extra columns.
- Rewrite DELETE /photos/{id} as a soft trash: just sets is_trashed=
  true and trashed_at=now, no shutil.move. Permanent deletion still
  goes through the trash router.

Frontend
- Photo TS type drops is_rejected, gains is_trashed.
- X keyboard shortcut now sets is_trashed=true (was is_rejected); U
  clears both is_picked and is_trashed.
- RightSidebar Reject button → Trash button (Trash2 icon).
- PhotoThumbnail flag overlay shows Trash2 icon for trashed photos
  instead of an X for rejected.
- KeyboardHints relabels X from "Reject" to "Trash".
- filterStore FlagFilter renames 'rejected' → 'trashed'; the params
  builder now sends is_trashed=true for the trashed filter (the list
  endpoint defaults to hiding trashed photos otherwise).
- FilterBar dropdown / URL sync allow-list updated accordingly.

No data migration: existing rejected photos remain as-is (flag stale)
and effectively become unflagged in the new model. Re-trash from the
UI to bring them into the new state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 22:16:10 +02:00
parent f7bf22db29
commit ce2cda0565
9 changed files with 36 additions and 53 deletions

View File

@@ -2,7 +2,7 @@ import { create } from 'zustand'
export type MediaType = 'photo' | 'video' | 'raw' | 'heic'
export type ColorLabel = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple'
export type FlagFilter = 'any' | 'picked' | 'rejected' | 'unflagged'
export type FlagFilter = 'any' | 'picked' | 'trashed' | 'unflagged'
export interface FilterState {
q: string
@@ -77,11 +77,8 @@ export function filtersToParams(f: FilterState): Record<string, string | number>
if (f.ratingMin > 0) params.rating_min = f.ratingMin
if (f.colorLabel) params.color_label = f.colorLabel
if (f.flag === 'picked') params.is_picked = 'true'
else if (f.flag === 'rejected') params.is_rejected = 'true'
else if (f.flag === 'unflagged') {
params.is_picked = 'false'
params.is_rejected = 'false'
}
else if (f.flag === 'trashed') params.is_trashed = 'true'
else if (f.flag === 'unflagged') params.is_picked = 'false'
return params
}