From c134afe0235f9f4b2571ffec9ce2ca9042c9b198 Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 20 May 2026 12:04:48 +0200 Subject: [PATCH] web(review): confidence-aware date guesser with combined filename + path signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor suggestDateFromPath to combine multiple signals instead of trying patterns in priority order: - Filename Y-M-D corroborated by path Y-M/Y-M-D → HIGH (filename- agrees-path). Fixes the case where a Samsung-style 20240226_xxx.jpg under 2024/02/ was returning the path-only 2024-02-01. - Filename Y-M-D with no path signal → HIGH (filename-only). - 10/13-digit Unix epoch in basename → HIGH (unix-timestamp) — covers WeChat (mmexport...) and FB saves. - Path Y-M-D → HIGH (path-ymd). - Path Y-M only → MEDIUM (path-ym-default-day, synthesised day=01). Sidebar row labels these "(estimated day)" so the user knows. Filename parser now accepts `.` and space separators (covers macOS screenshots, manual 2024.02.26 renames). Path parser accepts `.` too. OriginalName participates as a secondary filename signal when present and different from the on-disk basename. Patterns we explicitly DO NOT parse, to avoid silent date flips: DD-MM-YYYY / MM-DD-YYYY, 2-digit years, bare camera sequence numbers. Add photoNameAndDir(p) helper next to primaryFile so RightSidebar, BulkActionBar, photoActions, and gridKeyNav all derive {fileName, path} the same way — fixes the bug where photo.FileName was undefined on the single-photo detail endpoint and the basename branch was being skipped entirely. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/actions/gridKeyNav.ts | 6 +- .../components/sidebar/RightSidebar.svelte | 35 ++- .../components/timeline/BulkActionBar.svelte | 24 +- web/src/lib/services/photoActions.ts | 13 +- web/src/lib/types/photoprism.ts | 17 ++ web/src/lib/utils/suggestDateFromPath.ts | 227 +++++++++++++++--- 6 files changed, 257 insertions(+), 65 deletions(-) diff --git a/web/src/lib/actions/gridKeyNav.ts b/web/src/lib/actions/gridKeyNav.ts index c890e4f..7f553ae 100644 --- a/web/src/lib/actions/gridKeyNav.ts +++ b/web/src/lib/actions/gridKeyNav.ts @@ -12,6 +12,7 @@ import { } from '$lib/services/photoprism'; import { acceptDateAndKeep, cachedPhoto } from '$lib/services/photoActions'; import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath'; +import { photoNameAndDir } from '$lib/types/photoprism'; import { queryClient } from '$lib/queryClient'; import { filters } from '$lib/stores/filters.svelte'; import { @@ -486,7 +487,10 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) { for (const id of ids) { const p = cachedPhoto(id); if (!p) return; - if (!suggestDateFromPath({ fileName: p.FileName, path: p.Path })) return; + const { fileName, path } = photoNameAndDir(p); + if (!suggestDateFromPath({ fileName, originalName: p.OriginalName, path })) { + return; + } } e.preventDefault(); void acceptDateAndKeep(ids); diff --git a/web/src/lib/components/sidebar/RightSidebar.svelte b/web/src/lib/components/sidebar/RightSidebar.svelte index 6b2c11a..23699d5 100644 --- a/web/src/lib/components/sidebar/RightSidebar.svelte +++ b/web/src/lib/components/sidebar/RightSidebar.svelte @@ -38,7 +38,7 @@ import { isAuthenticated } from '$lib/stores/session.svelte'; import { push as pushUndo } from '$lib/stores/undo.svelte'; import { getMetadataSectionOpen, setMetadataSection } from '$lib/stores/view.svelte'; - import { primaryFile, type PpPhoto } from '$lib/types/photoprism'; + import { photoNameAndDir, primaryFile, type PpPhoto } from '$lib/types/photoprism'; import { COLOR_SWATCHES } from '$lib/utils/tagGroups'; import { suggestDateFromPath } from '$lib/utils/suggestDateFromPath'; @@ -139,15 +139,20 @@ page.url.pathname === '/review' && page.url.searchParams.get('tab') === 'stripped_exif' ); - const dateSuggestion = $derived( - suggestDateFromPath({ fileName: photo.FileName, path: photo.Path }) - ); + const dateSuggestion = $derived.by(() => { + const { fileName, path } = photoNameAndDir(photo); + return suggestDateFromPath({ + fileName, + originalName: photo.OriginalName, + path + }); + }); const showDateSuggestion = $derived( - onExifStrippedTab && !!dateSuggestion && dateSuggestion !== takenAt + onExifStrippedTab && !!dateSuggestion && dateSuggestion.iso !== takenAt ); function applyDateSuggestion() { if (!dateSuggestion) return; - takenAt = dateSuggestion; + takenAt = dateSuggestion.iso; commitTakenAt(); } function commitTakenAt() { @@ -344,17 +349,23 @@ /> - - {#if showDateSuggestion} + + {#if showDateSuggestion && dateSuggestion}
- Suggested from path: {dateSuggestion} + Suggested from path: {dateSuggestion.iso} + {#if dateSuggestion.source === 'path-ym-default-day'} + (estimated day) + {/if} {#if allHaveSuggestion} @@ -372,7 +376,7 @@ {/if}