perf+ux: cheaper sidebar toggle, virtualised filmstrip, compact toasts

Timeline's items array used to rebuild on every sub-pixel cellSize tick
during the sidebar CSS transition, causing visible jank with thousands
of photos. Row heights now resolve off a ref at virtualizer-measure
time, so items only rebuild when the column count actually changes.
PreviewFilmstrip is horizontally virtualised (~15 cells in the DOM
instead of N), cutting preview open latency on large libraries. Also
honor the user's explicit right-sidebar collapse (don't auto-reopen on
photo selection) and shrink the sonner toasts to a tighter form factor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-15 15:24:32 +02:00
parent d72a218b46
commit 66b3bc5e1f
4 changed files with 149 additions and 58 deletions

View File

@@ -29,6 +29,11 @@ import { TooltipProvider } from '@/components/ui/tooltip'
function MainApp() {
const [leftSidebarOpen, setLeftSidebarOpen] = useState(true)
const [rightSidebarOpen, setRightSidebarOpen] = useState(true)
// Respect the user's manual collapse of the metadata panel. Once they
// close it explicitly (via `i` hotkey or the sidebar toggle button),
// selecting a new photo should NOT force it back open. Cleared when
// they open it manually again.
const rightCollapsedByUser = useRef(false)
const viewMode = usePhotoStore((state) => state.viewMode)
const activePhotoId = usePhotoStore((state) => state.activePhotoId)
const currentSection = useFilterStore((s) => s.currentSection)
@@ -45,9 +50,24 @@ function MainApp() {
}, [currentSection])
useEffect(() => {
setRightSidebarOpen(!!activePhotoId)
if (!activePhotoId) {
setRightSidebarOpen(false)
return
}
// User explicitly collapsed the panel — don't undo that just because
// they picked a different photo.
if (rightCollapsedByUser.current) return
setRightSidebarOpen(true)
}, [activePhotoId])
const toggleRightSidebar = () => {
setRightSidebarOpen((prev) => {
const next = !prev
rightCollapsedByUser.current = !next
return next
})
}
// Bidirectional sync of filter store with URL query params.
useFilterUrlSync()
@@ -59,7 +79,7 @@ function MainApp() {
// Set up global keyboard shortcuts
useKeyboardShortcuts({
onToggleLeftSidebar: () => setLeftSidebarOpen(!leftSidebarOpen),
onToggleRightSidebar: () => setRightSidebarOpen(!rightSidebarOpen),
onToggleRightSidebar: toggleRightSidebar,
getFirstPhotoId: () => allPhotos?.[0]?.id ?? null,
})
@@ -96,7 +116,7 @@ function MainApp() {
leftSidebarOpen={leftSidebarOpen}
rightSidebarOpen={showRightSidebar}
onToggleLeftSidebar={() => setLeftSidebarOpen(!leftSidebarOpen)}
onToggleRightSidebar={() => setRightSidebarOpen(!rightSidebarOpen)}
onToggleRightSidebar={toggleRightSidebar}
/>
)}
{!isSettings && <DiscardActionBar />}