feat(gallery): sort by earliest post date across all posts (new default)
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:
@@ -43,7 +43,7 @@ describe('gallery store: composable filter', () => {
|
||||
expect(scroll).toContain('sort=oldest')
|
||||
})
|
||||
|
||||
it('omits sort=newest and sends no filter params when empty', async () => {
|
||||
it('omits the default sort and sends no filter params when empty', async () => {
|
||||
const s = useGalleryStore()
|
||||
const urls = []
|
||||
stubFetch((url) => { urls.push(url); return { status: 200, body: EMPTY } })
|
||||
@@ -197,7 +197,7 @@ describe('gallery store: composable filter', () => {
|
||||
describe('filterToQuery / cloneFilter', () => {
|
||||
it('serializes faceted params incl. platform sentinel and flags', () => {
|
||||
const q = filterToQuery({
|
||||
tag_ids: [3], artist_id: null, media_type: null, sort: 'newest',
|
||||
tag_ids: [3], artist_id: null, media_type: null, sort: 'posted_new',
|
||||
platform: '__unsourced__', untagged: true, no_artist: false,
|
||||
date_from: '2024-01-01', date_to: null,
|
||||
})
|
||||
@@ -207,7 +207,7 @@ describe('filterToQuery / cloneFilter', () => {
|
||||
expect(q.no_artist).toBeUndefined()
|
||||
expect(q.date_from).toBe('2024-01-01')
|
||||
expect(q.date_to).toBeUndefined()
|
||||
expect(q.sort).toBeUndefined() // newest is the default → omitted
|
||||
expect(q.sort).toBeUndefined() // posted_new is the default → omitted
|
||||
})
|
||||
|
||||
it('serializes similar_to', () => {
|
||||
|
||||
Reference in New Issue
Block a user