Commit Graph
10 Commits
Author SHA1 Message Date
nils cb79da89fb Owner feedback, server-side aggregation, grid keyboard culling, XMP sources
ci / docker (push) Successful in 11m52s
- photographer verdict/rating on photos (migration 0005), PUT endpoints
  returning the fresh per-photo aggregate; FeedbackAggregate in models is
  the single home of the merge policy (max rating, accept beats reject,
  owner counts), used by album detail, XMP export and vote responses
- feedback rows carry share_id; per-client filter pills (incl. 'you')
  scope filters/counts/overlays by id — labels are display-only
- XMP export modal with per-source checkboxes (?shares=…&own=…), share
  validation, id dedup, and basename merging so RAW+JPEG pairs share one
  sidecar instead of losing feedback to an unmatchable name
- grid keyboard culling: arrow cursor (clamped, outline after first use),
  Space opens / closes the viewer, Enter/S toggle select, P/X/U and star
  keys act on the cursor photo; keyboard votes never auto-navigate
- lightbox freezes the visible list while open, so voting a photo out of
  the active filter no longer closes the viewer mid-run
- perf: memoized per-scope derivation map, content-visibility on grid
  cells, lightweight /pending poll decoupled from vote patches, aggregate
  recompute in one SQL statement, out-of-order response guard
- structure: AlbumPage split into components (Modal, UploadZone +
  ActivityOverlay with failed-count, SharesPanel, XmpModal), shared
  useEscape with typing guard, expiry year guard in endOfDayIso
2026-07-18 01:15:15 +02:00
nils 46dd8df55c Album filters: star threshold as interactive stars control
ci / docker (push) Successful in 17s
Replaces the three fixed star chips with the Stars component as a
min-average selector, combinable with the verdict chips
2026-07-17 18:59:26 +02:00
nils b5512aa7e5 Album view: filter by client feedback; mobile share-panel layout
ci / docker (push) Successful in 8m10s
- filter bar over verdicts and average star rating (All / thumbs /
  undecided / 3+ / 4+ / 5) with live counts; gallery, lightbox,
  selection, ZIP download and bulk delete all operate on the filtered
  view
- share rows and the create form stack vertically on small screens
2026-07-17 17:37:23 +02:00
nils 7167af8984 Mobile: real swipe physics, pinch zoom, touch-first lightbox
ci / docker (push) Successful in 17s
- swipe sideways to browse (track follows the finger, FLIP settle),
  flick up/down to vote Tinder-style with ghost fly-off and the next
  photo zooming in behind; votes and navigation commit synchronously on
  release — animations are pure decoration, so closing or keypresses
  mid-animation can't drop or duplicate an action
- two fingers pinch-zoom the photo (springs back, never votes)
- voting the last photo fades the modal out over the gallery
- gesture engine extracted to useSwipe: per-move motion via direct DOM
  writes (no React render per touchmove), named threshold constants
- touch devices: selection checkmarks always visible, nav arrows yield
  to swiping, finger-sized vote/rating targets, safe-area padding
2026-07-17 17:18:51 +02:00
nils 037b68fc9d Fix review findings on bulk delete, share editing, selection
ci / docker (push) Successful in 9s
- expiry editor commits on blur/Enter with explicit clear button — no more
  per-keystroke PATCHes transiently expiring live links
- shift-range walks the filtered view, anchors reset on clear, updaters
  kept pure (StrictMode-safe)
- bulk delete surfaces errors, 404s on zero deletions, and shares one
  delete+cleanup path with single and album delete (s3::photo_prefix is
  the only prefix source)
- expiry end-of-day convention extracted; share rows update from the
  PATCH response instead of reloading the list
- Gallery container always renders, restoring the simple observer effect
2026-07-17 16:42:28 +02:00
nils 5cb00f3ef0 Bulk delete, editable share links, gallery filter fix
ci / docker (push) Successful in 9s
- shift-click selects ranges in the gallery; selection bar gains
  'Delete N' with a single confirm (POST /api/photos/delete)
- PATCH /api/shares/{id}: allow_download and expiry editable in place,
  token and client feedback preserved
- Gallery: re-attach ResizeObserver via callback ref — after a filter
  with zero matches the gallery stayed blank at width 0
2026-07-17 16:30:26 +02:00
nils 30d0b3064e Client accept/reject votes with keyboard-driven culling
ci / docker (push) Successful in 13s
- verdicts table (per link, like ratings), PUT verdict endpoint, typed Verdict enum
- share page: thumbs up/down, verdict filter with counts, view-scoped selection bar
- lightbox: per-page shortcut table (P/X/U, 1-5/0, S), ? help overlay, action
  toast when a keyboard vote auto-advances
- shared useLightbox hook; single keydown subscription reading live state via ref
- album view: per-link thumbs and vote counts; share list shows accept/reject totals
- feedback queries deduped and run concurrently; verdict counts in one scan
2026-07-17 15:55:46 +02:00
nils 6259ca84d8 Content dedup on both sides, stored checksums, spool-free zips, upload stats
ci / docker (push) Successful in 12s
- sha256+crc32 hashed during upload streaming; unique index per album
- duplicate content returns the existing photo (race-safe via 23505)
- client hashes locally (WebCrypto) and skips the transfer entirely for
  content the album already has
- zip downloads stream S3->response directly using the stored crc32;
  pre-hash photos spool once and self-heal (crc via zip, sha via reprocess)
- upload UI: overall progress bar, bytes, live speed, ETA
2026-07-17 14:41:07 +02:00
nils a6819809a7 Gallery: true justified layout (row-height scaling, no crop, no spacer) 2026-07-17 14:12:57 +02:00
nilsandClaude 16d2a56a78 Initial release: self-hosted client photo gallery
ci / docker (push) Successful in 13s
Rust (axum + sqlx) API and worker sharing a Postgres-backed job queue
(SKIP LOCKED, heartbeat, reaper, typed statuses), S3 storage with derived
keys and a fully private bucket, OIDC photographer login with per-request
allowlist checks, client share links with argon2 passwords and lockout,
cookie-based image authorization with sliding expiry, hand-rolled
spec-compliant streaming ZIP downloads with exact Content-Length,
React + Vite gallery frontend, single Docker image, Helm chart for
external S3 + Postgres, and Gitea CI.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 13:12:42 +02:00