Files
pi-weekly-newspaper/docs/superpowers/specs/2026-04-06-mosaic-cover-design.md
cottongin 8deb71b81a docs: add mosaic cover design spec
Made-with: Cursor
2026-04-06 19:16:12 -04:00

4.2 KiB
Raw Blame History

Mosaic Cover Design

Date: 2026-04-06 Scope: Replace the AI-generated cover background with a mosaic of article images.

Context

The weekly newspaper currently uses Pollinations.ai to generate a background image for the cover based on the dominant category of the week's articles. We want to replace this with a deterministic mosaic of the actual images featured in the week's articles.

Requirements

  • Source: Use the first image (if any) from each article included in the issue.
  • Filtering: Articles without images are skipped.
  • Layout: Column Masonry (2 columns).
  • Sizing: Images are scaled to fit the column width (234px) while preserving their natural aspect ratio. No cropping or letterboxing within the cells.
  • Spacing: 4px gap between images and around the edges.
  • Canvas: 480×800 pixels with a white background (#f5f5f5 to match the programmatic text cover).
  • Single Article: If the issue has only one article (and it has an image), center that single image on the canvas instead of building a mosaic.
  • Fallback: If no articles have images, fall back to the existing text-only cover.
  • Overlays: The existing masthead, date, and headline overlays (_draw_text_overlays) remain unchanged and are drawn over the mosaic.

Architecture & Implementation

1. Image Extraction

  • generate_cover in src/cover.py currently receives headlines and categories. It needs access to the article images.
  • We will modify the call signature of generate_cover to accept a list of image paths (or the Article objects themselves). Passing a list of local image paths image_paths: list[str] is the cleanest approach.
  • In src/routes/publish.py and src/scheduler.py, when preparing the issue, we will query the images table to find the first image for each included article and pass these paths to generate_cover.

2. Mosaic Algorithm (src/cover.py)

  • Create a new function generate_mosaic_cover(output_dir, week_start, week_end, headlines, image_paths).
  • Canvas Setup: Create a 480×800 RGBA image with a (245, 245, 245, 255) background.
  • Single Image Case:
    • If len(image_paths) == 1: Open the image, resize it to fit within 480×800 (using ImageOps.contain or similar aspect-ratio-preserving resize), and paste it centered on the canvas.
  • Mosaic Case:
    • Define columns: 2 columns.
    • Define gap: 4px.
    • Column width: (480 - (3 * 4)) // 2 = 234px.
    • Track the current Y position (height) of each column: col_heights = [4, 4].
    • Iterate through image_paths:
      • Open the image.
      • Calculate new height: new_h = int(img.height * (234 / img.width)).
      • Resize image to (234, new_h).
      • Find the shortest column: col_idx = 0 if col_heights[0] <= col_heights[1] else 1.
      • Check if adding this image exceeds the canvas height (800px). If col_heights[col_idx] + new_h > 800, we can either stop adding images or crop the bottom of the image. Since we want to fill the canvas but not exceed it, we will paste the image and let it be clipped by the canvas bounds. We will stop processing images once both columns exceed 800px.
      • Paste the image at x = 4 + col_idx * (234 + 4), y = col_heights[col_idx].
      • Update column height: col_heights[col_idx] += new_h + 4.
  • Overlays: Call _draw_text_overlays(draw, 480, 800, week_start, week_end, headlines).
  • Save as JPEG.

3. UI and Integration Updates

  • UI (templates/publish.html & templates/settings.html): Change "AI Cover" option to "Mosaic Cover" (value "mosaic").
  • Backend (src/routes/publish.py, src/scheduler.py, src/cover.py): Update logic to route "mosaic" to generate_mosaic_cover. Remove the generate_ai_cover function and Pollinations.ai dependency.
  • Config (config.py): Remove POLLINATIONS_URL.

Files Modified

  • src/cover.py: Add generate_mosaic_cover, remove generate_ai_cover.
  • src/routes/publish.py: Extract image paths, update cover method string.
  • src/scheduler.py: Extract image paths, update cover method string.
  • templates/publish.html: Update UI labels.
  • templates/settings.html: Update UI labels.
  • config.py: Remove unused AI URL.
  • tests/test_cover.py: Update tests for mosaic generation.