From adeee64a2d39209b6a424abf600208aec75f6d34 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 30 May 2026 14:51:58 -0400 Subject: [PATCH] feat(ui): batch initial showcase/gallery loads into smaller chunks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Showcase and gallery were fetching the full initial batch (60 / 50 items) in a single API call. Operator-flagged 2026-05-30: items should stream in as small batches so they render progressively rather than blocking on one big response. - showcase store: PAGE 60 → 5, INITIAL_BATCHES = 12 (5 × 12 = 60, same total). loadInitial() fetches PAGE chunks in sequence; shuffle() delegates. fetchPage() still returns one PAGE-sized chunk so infinite-scroll also pulls 5 per trigger. - gallery store: loadMore() limit 50 → PAGE (5), INITIAL_BATCHES = 10. loadInitial() loops loadMore() up to INITIAL_BATCHES times, stopping early when nextCursor becomes null (end of data). - ShowcaseView: onMounted calls loadInitial() instead of fetchPage(). Gallery/setTagFilter/setPostFilter already routed through loadInitial. Net visual effect: each ~5-item batch lands and renders independently; combined with MasonryGrid's animateFromIndex logic, the showcase now cascades batches in as they arrive instead of one big pop-in. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/stores/gallery.js | 17 +++++++++++++++-- frontend/src/views/ShowcaseView.vue | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/stores/gallery.js b/frontend/src/stores/gallery.js index 0c4ca77..1a8245a 100644 --- a/frontend/src/stores/gallery.js +++ b/frontend/src/stores/gallery.js @@ -2,6 +2,13 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useApi } from '../composables/useApi.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 + export const useGalleryStore = defineStore('gallery', () => { const api = useApi() @@ -21,7 +28,13 @@ export const useGalleryStore = defineStore('gallery', () => { images.value = [] dateGroups.value = [] nextCursor.value = null - await loadMore() + // 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() + } } async function loadMore() { @@ -30,7 +43,7 @@ export const useGalleryStore = defineStore('gallery', () => { error.value = null const myId = ++inflightId try { - const params = { limit: 50, ...activeFilterParam() } + const params = { limit: PAGE, ...activeFilterParam() } if (nextCursor.value) params.cursor = nextCursor.value const body = await api.get('/api/gallery/scroll', { params }) if (myId !== inflightId) return // stale response diff --git a/frontend/src/views/ShowcaseView.vue b/frontend/src/views/ShowcaseView.vue index a091d84..63de41f 100644 --- a/frontend/src/views/ShowcaseView.vue +++ b/frontend/src/views/ShowcaseView.vue @@ -70,7 +70,7 @@ function onKeydown(e) { } onMounted(() => { - if (store.images.length === 0) store.fetchPage() + if (store.images.length === 0) store.loadInitial() window.addEventListener('keydown', onKeydown) }) onUnmounted(() => window.removeEventListener('keydown', onKeydown))