Photo backfill UI: fix-recipe failures and Find missing photos (Maestro 7a468d2d, Feature 23 task 2)
CI / ci (push) Successful in 9m38s
CI / ci (pull_request) Successful in 9m30s

Settings → Regular dinners gains a "Find missing photos" trigger that
calls POST /api/v1/settings/photo-backfill (202 {id}) and follows the
backfill_dinner_photos job with useJob, showing progress, a summary of
the outcomes, or why it stopped. When the job ends, the dinners, plans
and home queries are read again so found photos show everywhere.

The fix-recipe sheet now shows the dinner's grounding_error as a typed
reason and offers "Search again" (the same backfill), reporting this
dinner's outcome; it is not offered for recipe_unusable, since the
backfill never replaces a pasted link. Regular now parses
grounding_error.

Task: 7a468d2d-4a9b-45f2-b626-a7bed5f671df

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 21:58:52 -04:00
co-authored by Claude Opus 5.5
parent 6127c5c2d1
commit 3882f670ca
8 changed files with 544 additions and 12 deletions
+10 -2
View File
@@ -20,8 +20,10 @@ export type Difficulty = (typeof DIFFICULTIES)[number]
export type GroundingStatus = (typeof GROUNDING_STATUSES)[number]
// Regular is the part of a dinner the Regulars screens read. photo_id is
// the stored photo's id, from photo_url. pending is true while an
// optimistic add waits for the server; its id is not the server's.
// the stored photo's id, from photo_url. grounding_error is why its
// grounding failed, a reason code, set only while grounding_status is
// failed. pending is true while an optimistic add waits for the server;
// its id is not the server's.
export type Regular = {
id: string
name: string
@@ -31,6 +33,7 @@ export type Regular = {
is_favorite: boolean
photo_id: string | undefined
grounding_status: GroundingStatus
grounding_error: string | null
pending?: true
}
@@ -122,6 +125,10 @@ export function parseDinner(where: string, body: unknown): Regular {
is_favorite: body.is_favorite,
photo_id: photoId(where, body.photo_url),
grounding_status: oneOf(where, 'grounding_status', GROUNDING_STATUSES, body.grounding_status),
grounding_error: nullable(body.grounding_error, (v) => {
if (typeof v !== 'string') throw new Error(`${where}: grounding_error is not a string`)
return v
}),
}
}
@@ -225,6 +232,7 @@ export function useAddRegular() {
is_favorite: false,
photo_id: undefined,
grounding_status: 'pending',
grounding_error: null,
pending: true,
}
await changeOptimistically(queryClient, (list) => [...list, optimistic])