fix: assorted UI polish from review pass

- FilterPill: drop the inline value text from the active state. Pills
  now stay the same width whether or not a filter is set; the popover
  is the canonical place to read the value, and the title attribute
  surfaces it on hover.
- TopBar: remove the search input — search lives in the filter bar now.
- FilterBar: add a search input on the left, with the pill cluster
  centered between it and a flex-shrink-0 Clear-all on the right.
- LeftSidebar / HeapsPanel: count badges use a fixed-width slot
  (h-5 min-w-[24px], tabular-nums) so counts line up in the same
  visual column across rows. Empty rows reserve the slot.
- LeftSidebar: pull section counts (All Photos, Rated, Duplicates,
  Discarded) from a new useLibraryStatsQuery hook backed by the
  expanded /library/stats endpoint. Tags count was already wired.
- backend/library: stats endpoint returns per-section counts that
  match the filter the sidebar applies on click.
- Stats invalidation hooked into the standard photo-mutation paths.
- RightSidebar header: h-12 to match TopBar height.
- Timeline sticky date overlay: only show once the natural in-grid
  header has scrolled OUT of the viewport. Avoids the duplicate-label
  flash when both labels would be visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 20:45:30 +02:00
parent 696477eefd
commit bd904aca36
13 changed files with 207 additions and 115 deletions

View File

@@ -338,19 +338,23 @@ export function HeapsPanel() {
</span>
)}
{/* Right-aligned action cluster: active indicator, count,
* target toggle, kebab menu. The flex-1 on the name span
* pushes everything below to the right edge of the row. */}
{/* Right-aligned cluster. Active indicator + count are
* always visible; set-active and kebab appear on hover
* to the RIGHT of the count, displacing it slightly so
* the count column lines up with the rest of the
* sidebar in the resting state. */}
{isActive && (
<Target
className="h-3 w-3 flex-shrink-0 text-primary"
aria-label="Active heap (T target)"
/>
)}
{heap.photo_count > 0 && (
<span className="flex-shrink-0 rounded bg-surface-offset px-1.5 py-0.5 text-xs text-text-muted">
{heap.photo_count > 0 ? (
<span className="flex h-5 min-w-[24px] flex-shrink-0 items-center justify-center rounded bg-surface-offset px-1.5 text-xs tabular-nums text-text-muted">
{heap.photo_count}
</span>
) : (
<span className="h-5 min-w-[24px] flex-shrink-0" aria-hidden="true" />
)}
{!isActive && (
<button