diff --git a/frontend/src/components/timeline/PhotoThumbnail.tsx b/frontend/src/components/timeline/PhotoThumbnail.tsx
index 908c7f4..21ac608 100644
--- a/frontend/src/components/timeline/PhotoThumbnail.tsx
+++ b/frontend/src/components/timeline/PhotoThumbnail.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef } from 'react'
-import { Star, Check, X, RefreshCw } from 'lucide-react'
+import { Star, Check, Trash2, RefreshCw } from 'lucide-react'
import clsx from 'clsx'
import { photos as photosApi } from '../../services/api'
import type { Photo } from '../../types/photo'
@@ -28,9 +28,11 @@ export function PhotoThumbnail({ photo, size, isSelected, onClick, onDoubleClick
const baseUrl = photosApi.getThumbnailUrl(photo.id, 'medium')
const thumbnailUrl = retryCount > 0 ? `${baseUrl}?retry=${retryCount}` : baseUrl
- // Calculate aspect ratio for proper sizing (default to 1:1 if dimensions unknown)
- const aspectRatio = (photo.height && photo.width) ? photo.height / photo.width : 1
- const displayHeight = size * Math.min(aspectRatio, 1.5) // Cap height at 1.5x width
+ // Square cells (Lightroom-style grid). Variable-aspect cells previously
+ // overflowed their row because TanStack Virtual estimates row height as a
+ // single fixed value — portraits in a landscape row would overlap the row
+ // below. With object-cover the image still fills the cell, just cropped.
+ const displayHeight = size
const clearRetryTimer = () => {
if (retryTimerRef.current !== null) {
@@ -169,8 +171,8 @@ export function PhotoThumbnail({ photo, size, isSelected, onClick, onDoubleClick
{photo.is_picked && (
)}
- {photo.is_rejected && (
-
+ {photo.is_trashed && (
+
)}