feat: surface active heap on photos + sidebar row, drop topbar pill

- PhotoThumbnail: the bottom-right basket badge now expands into a
  name chip when the active heap name is supplied. Truncated to a
  120px max-width so it doesn't eat the thumbnail.
- Timeline: read activeHeap.name from useActiveHeapMembersQuery and
  pipe it down to PhotoThumbnail.
- TopBar: drop the leftover heap pill next to "Mulimago" — the active
  state now lives where the user navigates to it (the heaps row).
- HeapsPanel: the active heap row gets a soft bg-pick/10 wash when
  not also filtered, the basket icon turns text-pick, and a small
  "ACTIVE" pill renders next to the name. Together they make it
  obvious which heap Pick / T target without needing the topbar pill.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 22:27:22 +02:00
parent 9317730885
commit e49f64659e
4 changed files with 47 additions and 33 deletions

View File

@@ -18,6 +18,9 @@ interface PhotoThumbnailProps {
isSelected: boolean
/** True when the photo belongs to the currently active heap. */
isInActiveHeap?: boolean
/** Name of the active heap. When set + isInActiveHeap, the basket
* badge expands into a name chip so the user knows which heap. */
activeHeapName?: string | null
onClick: (e: React.MouseEvent) => void
onDoubleClick?: (e: React.MouseEvent) => void
}
@@ -27,6 +30,7 @@ export function PhotoThumbnail({
size,
isSelected,
isInActiveHeap = false,
activeHeapName = null,
onClick,
onDoubleClick,
}: PhotoThumbnailProps) {
@@ -210,10 +214,15 @@ export function PhotoThumbnail({
<div className="absolute bottom-1 right-1 flex items-center gap-1">
{isInActiveHeap && (
<div
className="flex h-5 w-5 items-center justify-center rounded-full bg-pick text-white shadow-md ring-2 ring-white/90"
title="In active heap"
className="flex h-5 max-w-[120px] items-center gap-1 rounded-full bg-pick px-1.5 text-white shadow-md ring-2 ring-white/90"
title={activeHeapName ? `In heap: ${activeHeapName}` : 'In active heap'}
>
<ShoppingBasket className="h-3 w-3" strokeWidth={2.5} />
<ShoppingBasket className="h-3 w-3 flex-shrink-0" strokeWidth={2.5} />
{activeHeapName && (
<span className="truncate text-[10px] font-medium leading-none">
{activeHeapName}
</span>
)}
</div>
)}
{photo.is_duplicate && (

View File

@@ -196,7 +196,8 @@ export function Timeline() {
// Membership in the active heap (for the basket affordance). Subscribed
// once at this level so we don't have hundreds of thumbnails each
// subscribing to the same query.
const { memberIds: activeHeapMembers } = useActiveHeapMembers()
const { memberIds: activeHeapMembers, activeHeap } = useActiveHeapMembers()
const activeHeapName = activeHeap?.name ?? null
// Build the flat virtualizer items: a mix of group headers and rows of
// photos. Date headers appear when sorted by a date field; tag headers
@@ -510,6 +511,7 @@ export function Timeline() {
size={THUMBNAIL_SIZE}
isSelected={selectedPhotos.includes(photo.id)}
isInActiveHeap={activeHeapMembers.has(photo.id)}
activeHeapName={activeHeapName}
onClick={(e) => {
if (e.shiftKey) {
selectRange(photo.id)