From b7f2eb7832c72f450aeb2c934485fb9403f7e0b9 Mon Sep 17 00:00:00 2001 From: dtoro Date: Tue, 21 Apr 2026 21:56:07 +0200 Subject: [PATCH] ui(timeline): drop redundant tints in discarded + active-heap views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the grid is already filtered to discarded photos or to the active heap, every cell would carry the same tint — the grayscale wash or the green overlay stopped signalling anything and just made thumbnails harder to read. Timeline now suppresses both when the corresponding filter is active; the BR icon badges stay for colorblind readability. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/timeline/PhotoThumbnail.tsx | 20 +++++++++++++++---- frontend/src/components/timeline/Timeline.tsx | 12 ++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/timeline/PhotoThumbnail.tsx b/frontend/src/components/timeline/PhotoThumbnail.tsx index 1a040fc..1cf0ca0 100644 --- a/frontend/src/components/timeline/PhotoThumbnail.tsx +++ b/frontend/src/components/timeline/PhotoThumbnail.tsx @@ -74,6 +74,14 @@ interface PhotoThumbnailProps { /** True when the photo belongs to the currently active heap — drives * the green tint overlay. */ isInActiveHeap?: boolean + /** Suppress the desaturate/opacity treatment applied to discarded + * photos. Set by the Timeline when the view is already filtered to + * discarded only — every cell would otherwise carry the same tint. */ + hideDiscardedTint?: boolean + /** Suppress the green "in active heap" tint overlay. Set when the + * view is filtered to the active heap, where every cell would + * otherwise be wash-green. */ + hideActiveHeapTint?: boolean /** Active text-search query, passed in from Timeline rather than * subscribed-to here so we don't have N thumbnails each running a * per-keystroke selector. Empty string disables the highlight. */ @@ -91,6 +99,8 @@ function PhotoThumbnailImpl({ fill = false, isSelected, isInActiveHeap = false, + hideDiscardedTint = false, + hideActiveHeapTint = false, searchQuery = '', onClick, onDoubleClick, @@ -267,9 +277,11 @@ function PhotoThumbnailImpl({ className={cn( 'h-full w-full object-cover transition-opacity duration-200', imageLoaded ? 'opacity-100' : 'opacity-0', - // Discarded photos fade out + desaturate so the trash section - // reads as a trash section, not just another grid view. - photo.is_discarded && 'opacity-50 grayscale' + // Discarded photos fade out + desaturate so they read as + // trash when mixed into a normal timeline. Suppressed when + // the view is already filtered to discarded only (every + // cell would get the same tint — pure noise). + photo.is_discarded && !hideDiscardedTint && 'opacity-50 grayscale' )} onLoad={handleImageLoad} onError={handleImageError} @@ -310,7 +322,7 @@ function PhotoThumbnailImpl({ * both selected AND in the active heap shows both tints. Never * tinted on hover so the keyboard-driven selection stays * distinguishable from the mouse-driven hover. */} - {isInActiveHeap && ( + {isInActiveHeap && !hideActiveHeapTint && (
)} {isSelected && ( diff --git a/frontend/src/components/timeline/Timeline.tsx b/frontend/src/components/timeline/Timeline.tsx index 6873133..ae29ded 100644 --- a/frontend/src/components/timeline/Timeline.tsx +++ b/frontend/src/components/timeline/Timeline.tsx @@ -173,6 +173,8 @@ export function Timeline() { const sortBy = useFilterStore((s) => s.sortBy) const groupBy = useFilterStore((s) => s.groupBy) const currentSection = useFilterStore((s) => s.currentSection) + const flag = useFilterStore((s) => s.flag) + const filterHeapId = useFilterStore((s) => s.heapId) // Subscribed once at this level and passed down to each // PhotoThumbnail as a prop. Previously every thumbnail had its own // subscription, multiplying keystroke renders by the row count. @@ -231,7 +233,13 @@ export function Timeline() { // Membership in the active heap (drives the green tint on each // thumbnail). Subscribed once at this level so we don't have hundreds // of thumbnails each subscribing to the same query. - const { memberIds: activeHeapMembers } = useActiveHeapMembers() + const { activeHeap, memberIds: activeHeapMembers } = useActiveHeapMembers() + // When the view is already filtered to a specific attribute, every + // thumbnail would carry the same indicator — redundant visual noise. + // Pass these down so PhotoThumbnail can skip the tint in those cases. + const hideDiscardedTint = flag === 'discarded' + const hideActiveHeapTint = + filterHeapId != null && activeHeap != null && filterHeapId === activeHeap.id // Stable cell handlers. PhotoThumbnail is wrapped in React.memo so // identity-stable callbacks let it skip re-render on unrelated store @@ -812,6 +820,8 @@ export function Timeline() { fill isSelected={selectedPhotos.includes(photo.id)} isInActiveHeap={activeHeapMembers.has(photo.id)} + hideDiscardedTint={hideDiscardedTint} + hideActiveHeapTint={hideActiveHeapTint} searchQuery={searchQuery} onClick={handleCellClick} onDoubleClick={handleCellDoubleClick}