From 505fef5dfcb057a73d047371dc36161f0041e4e1 Mon Sep 17 00:00:00 2001 From: Claudio Date: Sun, 17 May 2026 23:25:43 +0200 Subject: [PATCH] fix(folders): root shows whole library + sidecar counts recurse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two paired fixes for the folder tree on the timeline: * +page.svelte: drop the `Path === ''` post-filter for the root entry. PhotoPrism's indexer always nests photos under YYYY/MM, so "photos whose Path is empty" is always the empty set in practice — the root entry looked broken instead of "whole library". Treat `/` as the unscoped view and rely on subfolder selections (now wildcarded via filters.svelte.ts) for narrowing. * sidecar/handlers_folders.go: the per-folder count fan-out used `q=path:`, the same exact-match operator that just got fixed in the web filter. Result: every year-level folder reported count=0 in the sidebar. Switch to `q=path:"*"` so the count reflects the whole subtree (dedupe by UID still in place). --- sidecar/handlers_folders.go | 20 ++++++++++++++------ web/src/routes/+page.svelte | 12 +++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/sidecar/handlers_folders.go b/sidecar/handlers_folders.go index cd68660..d5a7e09 100644 --- a/sidecar/handlers_folders.go +++ b/sidecar/handlers_folders.go @@ -231,12 +231,20 @@ func handleFolderCounts(pp *ppClient) gin.HandlerFunc { go func() { defer wg.Done() defer func() { <-sem }() - // `path:` is non-recursive in PhotoPrism's q-DSL: matches - // direct children only. `merged=false` returns one row per - // File on disk, so HEIC + companion JPG count twice unless - // we dedupe by UID — which is what the old client-side - // code did, and what we keep doing here. - q := url.QueryEscape("path:" + path) + // `path:` is an exact match in PhotoPrism's q-DSL — + // it matches only photos whose `photo_path` field equals + // , not descendants. The indexer always nests photos + // under YYYY/MM, so an internal tree node like `2024` has + // zero direct children and reports a count of 0 unless we + // recurse. `path:*` is the documented wildcard form and + // matches both `` itself (no harm if empty) and every + // `/...` descendant. + // + // `merged=false` still returns one row per File on disk, + // so HEIC + companion JPG count twice unless we dedupe by + // UID — which is what the old client-side code did, and + // what we keep doing here. + q := url.QueryEscape(`path:"` + path + `*"`) resp, err := pp.call(c.Request.Context(), http.MethodGet, "/api/v1/photos?count=1000&offset=0&merged=false&q="+q, token, nil) if err != nil || !resp.OK { diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 2431659..a50abc5 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -177,12 +177,14 @@ // scoped server-side via the q-DSL and must not be re-filtered here, // or labels / search will silently drop subfolder photos when the // store hasn't fully hydrated from the URL yet. + // + // Root (`/`) historically also restricted to `Path === ''` — i.e. + // photos sitting directly at the originals root with no subfolder. + // PhotoPrism's indexer however always nests photos under YYYY/MM, so + // that view was always empty in practice. Treat root as "the whole + // library" instead; subfolders still scope normally. function applyFolderScope(list: PpPhoto[], f: typeof filters): PpPhoto[] { - if (f.folderPath !== '/') return list; - if (f.section !== 'all-photos') return list; - if (f.heapUid) return list; - if (f.search) return list; - return list.filter((p) => !p.Path); + return list; } const pageCount = $derived(photosQuery.data?.pages.length ?? 0);