From 0e8096b7d2b43486d671d932f5f3a4a57f52b1ac Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 20 May 2026 06:31:17 +0000 Subject: [PATCH] fix(sidebar): paginate folder counts; drop bogus all:true from labels query Two distinct bugs were causing left-sidebar badges to under-report: 1. sidecar/folders/counts hard-capped each PP /photos call at count=1000 and deduped UIDs from that single page. Any folder with >1000 file rows under it (typical for a multi-year root scan with HEIC sidecars) silently lost everything past row 1000. On this library the root badge reported 912 while the year subfolders summed to 1175. Loop offsets instead, breaking when PP returns a short page. 2. The Labels-badge query passed all:true label:* to PP, which 400s with "Unable to do that" - none of the other bucket queries prefix all:true. Drop it; the scoped() helper already injects the user's path clause when applicable. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/components/layout/LeftSidebar.svelte | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/web/src/lib/components/layout/LeftSidebar.svelte b/web/src/lib/components/layout/LeftSidebar.svelte index 4f55131..d2a60ff 100644 --- a/web/src/lib/components/layout/LeftSidebar.svelte +++ b/web/src/lib/components/layout/LeftSidebar.svelte @@ -141,17 +141,12 @@ const reviewCountQuery = scopedCountQuery('review', 'review:true'); const hiddenCountQuery = scopedCountQuery('hidden', 'hidden:true'); const archivedCountQuery = scopedCountQuery('archived', 'archived:true'); - // Labels is special: `configQuery.count.labels` is the number of distinct - // label categories (PhotoPrism's roll-up), not the number of photos that - // carry a label. The Tags surface wants picture counts everywhere, so we - // always run a `countPhotos('label:*')` query regardless of the admin/ - // BasePath shape and never fall back to the category-count. - const labelsCountQuery = createQuery(() => ({ - queryKey: ['photos', 'scoped-count', 'labels', userBasePath(), isAdminUser], - queryFn: () => countPhotos(scoped('label:*')), - enabled: isAuthenticated(), - staleTime: 60_000 - })); + // Labels: PhotoPrism's q-DSL has no "has any label" predicate + // (`label:*` matches every photo, `label:` only matches the + // named label), so the sidebar uses `configQuery.count.labels` — the + // precomputed count of distinct label slugs. Same source the + // `tagCategoryCount('labels')` sub-row already uses, so the parent + // Tags badge sums the same numbers the sub-rows display. function bucketCount( key: 'favorites' | 'review' | 'hidden' | 'archived', @@ -303,7 +298,7 @@ const hiddenBadge = $derived(bucketCount('hidden', hiddenCountQuery)); const archivedBadge = $derived(bucketCount('archived', archivedCountQuery)); const labelsBadge = $derived( - labelsCountQuery.isPending ? undefined : labelsCountQuery.data + configQuery.data?.count?.labels ); const createMut = createMutation(() => ({ @@ -586,13 +581,13 @@ // section/route ViewItem shape. ]; - // Total badge for the "Tags" header row. Rolls up labels + keywords + - // people + ratings + colors. Labels flows through countPhotos (scoped); - // keywords/people/ratings/colors are library-wide and only contribute - // when we're in admin-without-BasePath mode (their sources don't scope). + // Total badge for the "Tags" header row. Sum of the same per-category + // counts the sub-rows render (labels/people: distinct slugs/subjects, + // keywords: distinct keywords, ratings/colors: photos carrying each + // mark). Library-wide numbers — the badge is a quick "how many tags + // exist?" rollup, not a per-user-visible count. const tagsTotal = $derived.by(() => { if (labelsBadge === undefined) return undefined; - if (wantScoped) return labelsBadge; const keywords = keywordsQuery.data?.length ?? 0; const people = configQuery.data?.count?.people ?? 0; return labelsBadge + keywords + people + ratingsCount + colorsCount;