perf+ux: cut grid re-renders, coalesce discard, dedup bulk mutations
Frontend cleanup pass driven by the post-shadcn review. Performance - Memoize PhotoThumbnail and route cell click/double-click through stable handlers so heap-membership invalidation no longer re-renders every visible thumbnail. - Cap usePhotosQuery's eager background page-walk at 20 pages with a 50ms inter-page yield — was unbounded (up to 100k photos cold). - Drop the per-thumbnail loading spinner in favour of the existing pulse skeleton; only retry state still surfaces a spinner. UX - Coalesce rapid X/U presses into a single undo entry + one toast (1.2s window) so accidental bursts are easy to back out. - Optimistic rating/color updates with per-id snapshot rollback on error, matching the existing discard pattern. - Section-aware empty timeline state with a Clear-all-filters CTA. - Carry the search-match chip from the grid into the preview header. - Add a basket-icon badge for active heap membership so the green tint isn't the only signal (colorblind-safe). - Standardise error toasts via formatApiError(): FastAPI detail, validation arrays, axios message, with a 'Network Error' filter. Architecture - Extract useBulkPhotoMutations and stop duplicating bulkRating/bulkColor across RightSidebar and useKeyboardShortcuts. - Split RightSidebar (714 -> 448 LOC) and PhotoInfoPanel (952 -> 716) into co-located sub-components: BulkTakenAtEditor, BulkTagsEditor, TagsEditor, TakenAtEditor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
38
frontend/src/lib/apiError.ts
Normal file
38
frontend/src/lib/apiError.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Normalise errors coming out of the axios-backed API client into a
|
||||
* user-facing string. FastAPI puts validation/permission errors on
|
||||
* `response.data.detail`; network timeouts / CORS surface as the axios
|
||||
* `message`. Engine messages like "SyntaxError" or bare "Network Error"
|
||||
* are filtered out in favour of a stable fallback so users never see
|
||||
* raw parser noise in a toast.
|
||||
*/
|
||||
export function formatApiError(err: unknown, fallback = 'Something went wrong'): string {
|
||||
if (!err) return fallback
|
||||
|
||||
const anyErr = err as {
|
||||
response?: { data?: { detail?: unknown; error?: unknown; message?: unknown } }
|
||||
message?: string
|
||||
}
|
||||
|
||||
const fromDetail = anyErr.response?.data?.detail
|
||||
if (typeof fromDetail === 'string' && fromDetail.trim()) return fromDetail
|
||||
// FastAPI validation errors come through as an array of objects.
|
||||
if (Array.isArray(fromDetail) && fromDetail.length > 0) {
|
||||
const first = fromDetail[0] as { msg?: string }
|
||||
if (first?.msg) return first.msg
|
||||
}
|
||||
|
||||
const fromError = anyErr.response?.data?.error
|
||||
if (typeof fromError === 'string' && fromError.trim()) return fromError
|
||||
|
||||
const fromMessage = anyErr.response?.data?.message
|
||||
if (typeof fromMessage === 'string' && fromMessage.trim()) return fromMessage
|
||||
|
||||
// Axios "Network Error" and native runtime errors like SyntaxError are
|
||||
// worse than a stable fallback — filter them out.
|
||||
const msg = anyErr.message
|
||||
if (typeof msg === 'string' && msg.trim() && msg !== 'Network Error') {
|
||||
return msg
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
Reference in New Issue
Block a user