ui: rework selection/heap visuals, contextual shortcut hints, inline scan activity

- Selection now reads as a blue ring + tint with a springy scale-down,
  hover stays a subtle gray ring so keyboard-driven and mouse-driven
  states are tellable apart.
- Heap membership is signalled with a green tint only (no badge, no
  ring, no scale).
- Discard/restore is optimistic and non-yanking: photos stay in the
  grid greyed out until the next reload, X toggles based on the
  current state, and the same treatment applies in preview.
- Filmstrip mirrors the grid styling (selection blue, heap green,
  discarded grey).
- Preview close restores the LAST viewed photo as the focused/selected
  one in the grid.
- Right sidebar collapses on view change and re-opens when a photo is
  in focus; Esc clears active selection so the panel collapses too.
- Keyboard hints panel is context-aware (grid / preview / discarded
  section), collapsible with H, persisted, and rendered inside the
  preview column above the filmstrip.
- "Pick (P)" renamed to "Select (S)" everywhere.
- Needs review moved into the Flag pill dropdown.
- Fixed vertical videos overflowing the preview column (min-h-0).
- Replaced the bottom-right ScanProgress popover with an inline
  spinner next to the FOLDERS sidebar header (and on the specific
  folder row being scanned). ScanProgress is now a headless
  invalidator; useScanActivity exposes the live status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 23:45:36 +02:00
parent a6eb406052
commit 8529771122
17 changed files with 501 additions and 408 deletions

View File

@@ -494,7 +494,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
</div>
</div>
{/* Flag — Pick + Discard */}
{/* Flag — Select + Discard */}
<div>
<label className="mb-1 block text-xs text-text-muted">Flag</label>
<div className="flex gap-2">
@@ -519,7 +519,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
}
>
<ShoppingBasket className="h-3 w-3" />
{isInActiveHeap ? 'Picked' : 'Pick'}
{isInActiveHeap ? 'Selected' : 'Select'}
</button>
<button
onClick={() => updateMutation.mutate({ is_discarded: !isDiscarded })}