feat(gallery): sort by earliest post date across all posts (new default)
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m24s
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 16s

The gallery's newest/oldest sort keys off image_record.effective_date =
COALESCE(primary post's post_date, created_at). The primary post is often the
repost/download the file came from, so the grid led with download dates rather
than when content was first posted (operator-flagged).

Add a second materialized sort key, earliest_post_date = MIN(post_date) across
ALL of an image's provenance posts (every post it appears in), else created_at —
the original publish date. Mirrors the effective_date pattern so the sort stays a
forward index scan.

- alembic 0071: add earliest_post_date + index (DESC, id DESC); backfill
  created_at baseline then MIN over image_provenance ⋈ post.
- importer: recompute earliest_post_date whenever a dated post is linked (MIN over
  the image's provenance, which now includes the just-added row).
- gallery_service: new sorts posted_new / posted_old key off earliest_post_date;
  cursor + year/month grouping follow the active column transparently.
- api: accept posted_new|posted_old; DEFAULT is now posted_new so the grid leads
  with original publish date. newest/oldest (effective_date) still available.
- frontend: sort dropdown gains "Newest/Oldest post date" (default Newest post
  date); existing effective-date sorts relabelled "Newest/Oldest added".
- tests: service test asserts posted_new/posted_old key off earliest_post_date;
  frontend default-sort omission test updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
This commit is contained in:
2026-07-01 10:46:09 -04:00
parent ccbb5cbc9e
commit c22f37d64d
9 changed files with 176 additions and 15 deletions
+22
View File
@@ -73,6 +73,28 @@ async def test_scroll_returns_newest_first(db):
assert page.images[0].created_at > page.images[-1].created_at
@pytest.mark.asyncio
async def test_scroll_sorts_by_earliest_post_date(db):
"""posted_new/posted_old key off earliest_post_date (original publish across
all of an image's posts), NOT effective_date/created_at — so an old file that
was first posted recently sorts by that recent post date."""
imgs = await _seed_images(db, 3)
now = _now()
# earliest_post_date order deliberately DIFFERS from created_at order (which
# _seed_images makes imgs[0] newest → imgs[2] oldest), proving the sort keys
# off earliest_post_date.
imgs[0].earliest_post_date = now - timedelta(days=1) # middle
imgs[1].earliest_post_date = now - timedelta(days=365) # oldest post
imgs[2].earliest_post_date = now - timedelta(hours=1) # newest post
await db.flush()
svc = GalleryService(db)
newest = await svc.scroll(cursor=None, limit=10, sort="posted_new")
assert [i.id for i in newest.images] == [imgs[2].id, imgs[0].id, imgs[1].id]
oldest = await svc.scroll(cursor=None, limit=10, sort="posted_old")
assert [i.id for i in oldest.images] == [imgs[1].id, imgs[0].id, imgs[2].id]
@pytest.mark.asyncio
async def test_scroll_pagination(db):
await _seed_images(db, 5)