Report the latest failed suggestion batch on the deck
CI / ci (push) Successful in 3m3s
CI / ci (pull_request) Successful in 2m45s

The deck reported only a pending or running batch, so a Discover page
opened after a batch failed (or was interrupted by a restart) had no job
to follow and showed "All caught up" or the remaining cards with no
error. GET /api/v1/suggestions/deck now also returns failed_batch: the
newest batch when it failed, read in the same snapshot after orphaned
batches are failed, so at most one of active_batch and failed_batch is
set and a later batch supersedes the failure. The page follows the
failed batch's job like the active one, so its failure shows with the
existing Retry, which asks for a new batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 05:29:10 -04:00
co-authored by Claude Opus 5.5
parent 6d36a7a10c
commit b611d1bbce
11 changed files with 213 additions and 44 deletions
+3
View File
@@ -78,6 +78,9 @@ type Querier interface {
// Returns sql.ErrNoRows until the row exists.
GetIntegrationsGoogle(ctx context.Context) (IntegrationsGoogle, error)
GetJob(ctx context.Context, id string) (Job, error)
// The newest batch, whatever its status. Returns sql.ErrNoRows when there
// is none.
GetLatestSuggestionBatch(ctx context.Context) (SuggestionBatch, error)
GetPhoto(ctx context.Context, id string) (Photo, error)
// Finds stored image bytes by content hash. The schema does not make sha256
// unique, so the earliest-created (lowest id) match is returned.
+22
View File
@@ -187,6 +187,28 @@ func (q *Queries) GetActiveSuggestionBatch(ctx context.Context) (SuggestionBatch
return i, err
}
const getLatestSuggestionBatch = `-- name: GetLatestSuggestionBatch :one
SELECT id, "trigger", status, ai_run_id, created_at, updated_at FROM suggestion_batches
ORDER BY id DESC
LIMIT 1
`
// The newest batch, whatever its status. Returns sql.ErrNoRows when there
// is none.
func (q *Queries) GetLatestSuggestionBatch(ctx context.Context) (SuggestionBatch, error) {
row := q.db.QueryRowContext(ctx, getLatestSuggestionBatch)
var i SuggestionBatch
err := row.Scan(
&i.ID,
&i.Trigger,
&i.Status,
&i.AiRunID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getSuggestion = `-- name: GetSuggestion :one
SELECT suggestions.id, suggestions.dinner_id, suggestions.batch_id, suggestions.name, suggestions.reason, suggestions.cited_regular_ids, suggestions.status, suggestions.decided_at, suggestions.created_at, suggestions.updated_at, dinners.id, dinners.name, dinners.normalized_name, dinners.status, dinners.origin, dinners.method, dinners.minutes_total, dinners.difficulty, dinners.cuisine, dinners.is_favorite, dinners.recipe_id, dinners.photo_id, dinners.grounding_status, dinners.updated_by, dinners.created_at, dinners.updated_at, dinners.recipe_url, dinners.grounding_error, dinners.grounding_revision
FROM suggestions