feat(gallery): reveal tiles on image load + single initial fetch
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 27s
CI / backend-lint-and-test (push) Successful in 29s
CI / intimp (push) Successful in 3m43s
CI / intapi (push) Successful in 7m43s
CI / intcore (push) Successful in 8m22s

The 5×10 metadata batching only staggered the cheap layer (JSON);
thumbnails load as independent <img> requests and clustered, so tiles
"popped in together" after a wait. Two changes:

- GalleryItem reveals each tile when ITS OWN thumbnail fires @load (with
  an onMounted complete-check for cached thumbs), playing a showcase-style
  flip-up entrance. Tiles now cascade in natural load order instead of all
  at once. Honors prefers-reduced-motion.
- gallery store does ONE initial fetch (limit=50) instead of 10 serial
  /scroll round-trips. Fewer RTTs, faster first paint; the reveal-on-load
  is what makes appearance progressive now. Infinite scroll pulls 25/trigger.

Tests: GalleryItem gains is-loaded only after @load; loadInitial issues
exactly one scroll request at the initial limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:50:03 -04:00
parent 576e16d14d
commit 56cc253009
4 changed files with 100 additions and 19 deletions
+12 -15
View File
@@ -3,12 +3,15 @@ import { ref, computed } from 'vue'
import { useApi } from '../composables/useApi.js'
import { useInflightToken } from '../composables/useInflightToken.js'
// Operator-confirmed 2026-05-30: fetch PAGE-sized chunks instead of one
// 50-item request so items render as each batch lands. Total initial
// count is unchanged (PAGE * INITIAL_BATCHES = 50). Infinite-scroll also
// pulls PAGE per trigger to keep appends progressive.
const PAGE = 5
const INITIAL_BATCHES = 10
// Initial paint is a SINGLE request (INITIAL_LIMIT). The old 10×serial-
// batch loop (2026-05-30) only staggered METADATA, which isn't the visual
// bottleneck — thumbnails load as independent `<img>` requests, and
// GalleryItem now reveals each tile on its own image `@load`. One fetch is
// far fewer round-trips and faster to first paint; the reveal-on-load is
// what makes appearance progressive. Infinite scroll pulls PAGE per trigger.
// Reworked 2026-06-04.
const PAGE = 25
const INITIAL_LIMIT = 50
export const useGalleryStore = defineStore('gallery', () => {
const api = useApi()
@@ -32,22 +35,16 @@ export const useGalleryStore = defineStore('gallery', () => {
images.value = []
dateGroups.value = []
nextCursor.value = null
// Sequentially fetch INITIAL_BATCHES chunks so items render as each
// batch lands rather than blocking on one big response. Stop early
// when the backend reports no more pages.
for (let i = 0; i < INITIAL_BATCHES; i++) {
if (i > 0 && nextCursor.value === null) break
await loadMore()
}
await loadMore(INITIAL_LIMIT)
}
async function loadMore() {
async function loadMore(limit = PAGE) {
if (loading.value) return
loading.value = true
error.value = null
const t = inflight.claim()
try {
const params = { limit: PAGE, ...activeFilterParam() }
const params = { limit, ...activeFilterParam() }
if (nextCursor.value) params.cursor = nextCursor.value
const body = await api.get('/api/gallery/scroll', { params })
if (!t.isCurrent()) return