Skip to content

Enrich books and products once, from their text plus the cover - #705

Merged
fredrivett merged 4 commits into
mainfrom
fredrivett/single-cover-enrichment
Nov 14, 2026
Merged

fredrivett merged 4 commits into
mainfrom
fredrivett/single-cover-enrichment

Conversation

@fredrivett

@fredrivett fredrivett commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

What & why

A book or product saved with a cover was enriched twice. classify-url queued enrich-item (from the title and description) and analyze-image (the cover) together, and analyze-image then queued a second enrich-item built only from the cover's objects and OCR. Tags and the text vector are last-writer-wins, so the cover pass usually won: a product's search vector came from its cover image rather than its title and description, and the first tag generation and embedding were wasted spend.

  • A book or product with a cover now queues only analyze-image, which takes the item's text as extraSourceText and blends it with the cover's objects and OCR — the same approach analyze-media-cover already takes for tweets (see the comment in handle-twitter-url.ts). A book or product without a cover enriches from its text alone, as before.
  • When item text is present, enrich-item generates the tags from the blend (one gpt-4o-mini call, the same as the text-only enrich this removes) instead of taking the cover's vision tags alone. The cover's objects and OCR lead the blend, so a long description is what the token limit cuts. A plain image upload still uses its vision tags.
  • The cover is an enhancement on a book or product, so once its analysis has failed for good, analyze-image's onFailure enriches the item from its text and the item still completes. It skips that fallback when a re-capture has since replaced the cover.
  • analyze-image's catch now only marks an item failed if it hasn't completed, like enrich-item and analyze-media-cover, so a cover-analysis failure (e.g. on reprocess) can't turn a finished item "Failed".

Checklist

  • User-facing change → added/updated a PostHog event (and a funnel/insight for a new flow), or N/A — N/A: no new action or flow; it changes what enrichment is built from
  • User-facing or service change → docs updated (README features/roadmap/services, .env.example, in-app help, CONTRIBUTING.md, onboarding checklist), or N/A — N/A: no feature, service or setup change
  • New error paths report via captureServerException / the error boundary, or N/A — N/A: no new error paths
  • New behavior is covered by tests, or N/A — classify-url.test.ts (one enrichment per product with a cover, blending its text; text-only for a book without one) and a new analyze-image.integration.test.ts against real Postgres (blended source text and tags; vision tags kept for plain images; the text fallback, and its skip for a replaced cover; a failure never regresses a completed item)
  • User-facing change → demo video attached (record-pr-demo skill); refactor through a user flow → parity run, or N/A — N/A: the change is inside the Trigger.dev pipeline, which isn't configured in the demo stack, so there's nothing to record; the visible effect is in tags and search results
  • If this fixes a recurring defect, considered a guardrail via the learn skill, or N/A — considered: the one-enrichment-per-save rule is now documented on analyze-image and the new triggerEnrichment helper; no deterministic check fits a "don't queue both" rule without over-fitting

Notes

  • Verified: the new tests fail on the previous code and pass now; bun run check:fix clean; the full unit suite passes.
  • Not verified: nothing ran through a real Trigger.dev deployment (not available locally), so the queued runs were checked through the task code, not end to end.
  • Mark items failed only once no retry is left #706 is stacked on this branch and moves the failed-status write to after the last retry.

A book or product with a cover queued enrich-item (text) and analyze-image
together, and analyze-image queued a second enrich-item from the cover. Tags
and the text vector are last-writer-wins, so the cover pass usually replaced
the text one, and the first tag generation and embedding were wasted.

Now a cover-bearing book or product queues only analyze-image, which takes the
item's text as extraSourceText and blends it with the cover's objects and OCR
(as analyze-media-cover does for tweets). A cover-less one enriches from its
text alone.

analyze-image's catch no longer turns a completed item failed, matching
enrich-item and analyze-media-cover.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1daa2-36bf-7085-8fc3-5fd53d723953
@vercel

vercel Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
abode Ignored Ignored Preview Nov 14, 2026 11:52pm UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread app/trigger/classify-url.ts Resolved
Comment thread app/trigger/analyze-image.ts Resolved
Comment thread app/trigger/analyze-image.ts Resolved
- A book or product's enrichment now generates its tags from the item's text
  blended with the cover, instead of the cover's vision tags alone.
- The cover's objects/OCR lead the blend, so a long description is what the
  token limit cuts.
- Once a cover analysis has failed for good, analyze-image's onFailure enriches
  the item from its text, so a failed cover doesn't leave it without tags.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1daa2-36bf-7085-8fc3-5fd53d723953

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread app/trigger/analyze-image.ts Resolved
A re-capture replaces the cover and deletes the old one, so an older
analyze-image run then fails; its text fallback would re-enrich the item from
the old capture's text over the newer run's. Only fall back while the run's
cover is still the item's cover.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1daa2-36bf-7085-8fc3-5fd53d723953
fredrivett pushed a commit that referenced this pull request Nov 14, 2026
Merges fredrivett/single-cover-enrichment (analyze-image's text fallback once
its cover has failed for good), keeping that fallback alongside the shared
failure hooks: analyze-image reports the error and enriches from the item's
text instead of marking it failed when the run carried item text.

From cubic's review of #706:
- itemFailureHooks always leaves a completed item as it is. User-initiated
  runs set the item to processing first, so a completed item means a later
  stage or a background re-run (admin reprocess, cover swap) failed; the
  keepCompleted option is gone.
- Failure reports carry the payload's url/fileKey again.
- isPermanentFailure treats every SafeFetchError (redirect cap, invalid
  redirect, oversized body) as permanent.
- import-pdf's upload-failure test checks catchError on the error run threw.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1daa2-36bf-7085-8fc3-5fd53d723953

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread app/trigger/analyze-image.ts Resolved
The stale-run guard compared the run's fileKey with coverFileKey, so a product
cover picked through PATCH (which queues no enrichment) skipped the text
fallback and left the item without tags. Skip it only when the run's file is
no longer one of the item's captured files, which is what a re-capture does.
@fredrivett
fredrivett merged commit 5e3bd31 into main Nov 14, 2026
14 checks passed
@fredrivett
fredrivett deleted the fredrivett/single-cover-enrichment branch November 14, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants