Skip to content

feat: AI-generated theme cover images (Flux Schnell + R2)#63

Merged
meninoebom merged 3 commits into
mainfrom
feature/theme-cover-images
Apr 21, 2026
Merged

feat: AI-generated theme cover images (Flux Schnell + R2)#63
meninoebom merged 3 commits into
mainfrom
feature/theme-cover-images

Conversation

@meninoebom
Copy link
Copy Markdown
Owner

Closes #62

Summary

  • Writer clicks "Generate cover image" on a theme, sees 4 Flux Schnell candidates in a 2×2 grid, picks one → image is re-uploaded to R2 and saved on the theme.
  • Feed renders a 56px thumbnail next to the theme body (only if image_url is set).
  • Permalink page renders a 240px image above the body.
  • Prompt builder uses a fixed style suffix so all generated images feel cohesive: flat oil painting, three-color palette, contemporary museum-quality, figurative, tone between gravity and play.

Verified locally

  • Frontend typecheck passes
  • Backend imports cleanly
  • Alembic migration applied
  • Live Replicate call succeeds — generated 4 candidates against a real prompt; URLs in issue feat: AI-generated theme cover images (Flux Schnell + R2) #62 thread if you want to eyeball the style before merging
  • Full writer-UI flow (generate → pick → commit → feed shows thumbnail) — Brandon to verify in dev

Test plan

  • uv run dev — backend starts without error
  • cd frontend && npm run dev — frontend starts
  • Log in as writer, open an existing theme in the editor
  • Click "Generate cover image" — verify 4 images appear within ~5s
  • Click one — verify the thumbnail renders above (not 4 candidates anymore), image_url persisted
  • Visit home feed — verify thumbnail renders next to that theme
  • Visit /themes/{id} permalink — verify larger image above body
  • Click "Regenerate" — verify new 4-up grid appears, can pick a different one
  • Click "Remove" — verify image is cleared, feed thumbnail disappears
  • Unset REPLICATE_API_TOKEN and click Generate — verify clean 503 error in UI (not a crash)

Notes for reviewer

  • Pre-existing alembic drift (dropped index idx_breadcrumb_parent_id, removed column subscriber.confirmed) was detected during autogenerate and stripped from this migration. Needs a follow-up cleanup PR.
  • go_fast=true is Flux Schnell's default — seeds are non-deterministic. This is fine for the "get 4 fresh options" UX but means you can't reproduce a specific generation by freezing a seed.
  • Replicate temp URLs expire in ~1 hour — the /image commit endpoint downloads + re-uploads to R2 for permanent storage. Same R2 setup as the existing /uploads endpoint.

🤖 Generated with Claude Code

Closes #62

Adds manual cover-image generation for themes via Replicate's Flux Schnell.
Writer clicks "Generate", picks from 4 candidates in a grid, chosen image
is re-uploaded to R2 for a permanent URL. Displayed as a 56px thumbnail
in the feed and 240px square on the permalink page.

Prompt builder uses a fixed style suffix (flat oil, three-color palette,
contemporary museum-quality, figurative) to keep all generated images
visually cohesive. Tags feed in as mood, theme body as scene evocation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 20, 2026

🚅 Deployed to the breadcrumbs-pr-63 environment in Breadcrumbs

Service Status Web Updated (UTC)
Breadcrumbs Web App Server ✅ Success (View Logs) Web Apr 21, 2026 at 12:10 am

@railway-app railway-app Bot temporarily deployed to Breadcrumbs / breadcrumbs-pr-63 April 20, 2026 23:51 Destroyed
Nudge Flux toward depicting diverse figures — people of color, varied
ages, varied body types — when humans appear in generated scenes.
Without this, Flux defaults heavily toward a narrow demographic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to Breadcrumbs / breadcrumbs-pr-63 April 20, 2026 23:54 Destroyed
Address review findings on PR #63:

Critical:
- SSRF: validate source_url against Replicate host allowlist + reject
  non-https + disable redirect following in commit_image_to_r2
- R2 env-var validation: extract app/storage.py with assert_r2_env() and
  put_object() helpers; fail with 500 if R2 misconfigured instead of
  silently persisting relative URLs
- Replicate error surface: catch ReplicateError + httpx.HTTPError
  explicitly, raise ImageGenerationError with proper logging; map to
  503 in the endpoint with useful detail

Important:
- Extract R2 client to app/storage.py — both /uploads and the new
  commit endpoint use the same put_object() helper now
- Hoist app.images imports to module top in api.py
- Remove dead NotImplementedError catch
- Drop image_url from THEME_UPDATABLE_FIELDS to prevent arbitrary URL
  injection via PUT /themes/{id}; add dedicated DELETE /themes/{id}/image
  for clearing
- Reject unknown content-types instead of silently saving as .webp
- Add image magic-byte validation before R2 upload
- Render clearImage error in writer UI
- Structured logging in app/images.py

Tests (33 new):
- 13 unit tests for build_theme_prompt, tags_for_prompt, _is_allowed_source,
  _looks_like_image
- 12 endpoint tests covering 404s, missing token, host allowlist, https
  enforcement, happy paths with replicate mocked, whitelist guard,
  clear endpoint

All 159 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to Breadcrumbs / breadcrumbs-pr-63 April 21, 2026 00:08 Destroyed
@meninoebom meninoebom merged commit a262fd2 into main Apr 21, 2026
3 checks passed
@meninoebom meninoebom deleted the feature/theme-cover-images branch April 21, 2026 06:51
@meninoebom meninoebom mentioned this pull request Apr 22, 2026
2 tasks
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.

feat: AI-generated theme cover images (Flux Schnell + R2)

1 participant