Captions were being baked into the image by the FLUX model, causing
text to get cut off at the edges. Now the LLM generates scene and
caption separately — the scene drives image gen (with explicit
"no text in the image" instruction), and the caption is inserted
as a styled <figure>/<figcaption> HTML block at the top of the post.
Changes:
- rewrite_via_llm() returns array{scene, caption} instead of string
- build_article_prompt() and build_source_prompt() return {prompt, caption}
- Style suffix v4: removes "caption text" instruction, adds "no text" directive
- Image pipeline destructures prompt/caption, stores caption in attachment meta
- prepend_caption_to_post() inserts figure+figcaption HTML into post content
- Deduplicated generate_image_a/b into shared generate_single_image()
- One-time v4 migration auto-updates existing style suffix on deploy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: both attach_generated_images() in post-assembler and the
retroactive handler in executor passed null as source_data to the image
pipeline. The pipeline's Image B branch requires non-null source_data
to fire, so Image B was silently skipped on every article since the
feature was built.
Fix: added get_source_data_for_image() to Source_Collector to retrieve
the original Reddit post title/content by source_ids. Post-assembler
now pulls source_ids from the Article_Idea; executor pulls them from
the _prautoblogger_source_ids post meta saved by the publisher.
Also added a regression test proving Image B is skipped when source_data
is null, so this handoff gap will be caught if re-introduced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap cost_usd accumulation in is_wp_error() guard so WP_Error
returns from generate_image_a/b don't crash on array access
- Add PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX to test bootstrap
- Add is_wp_error, sanitize_title, esc_html__ mocks to ImagePipelineTest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove dead logger mock (pipeline uses static Logger::instance(), not injected)
- Add wp_strip_all_tags Brain Monkey stub in setUp (removed from bootstrap)
- Remove willReturn(true) on void log_image_generation mock
- Fix wp_generate_uuid4 mock to use justReturn instead of returnArg
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Completes the image + A/B experiment workstream by integrating
image generation into the post publishing pipeline.
New classes:
- class-image-pipeline.php: Orchestrates A/B generation (article-driven
Image A → featured; source-driven Image B → post meta). Each image
independently fallible; article publishes even if both fail.
- class-image-prompt-builder.php: Synthesizes visual prompts from article
title+content and Reddit source data. Appends CEO-locked 90s infomercial
style suffix. Keeps prompts concise for FLUX.1 quality.
- class-image-media-sideloader.php: Downloads image bytes, creates temp
file, sideloads via media_handle_sideload(), sets alt text and metadata,
cleans up temp files.
Changes to existing files:
- Publisher.attach_generated_images(): Runs image pipeline post-creation,
sets _thumbnail_id (Image A) and _prautoblogger_image_b_id (Image B).
Errors logged but don't block post publication.
- Settings: prautoblogger_image_enabled (toggle, default off).
Tests:
- ImagePipelineTest.php: Orchestration, cost tracking, graceful failures.
- ImagePromptBuilderTest.php: Prompt generation, length limits.
Docs:
- ARCHITECTURE.md: New step 6b in data flow, new files in file tree.
- CHANGELOG.md: Commit 1a/1b breakdown.
Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>