Why: 2026-05-09 user report — the food-app card design landed with
empty colored rectangles where restaurant photos should be (Bella
Italia / Green Bowl / Margherita Pizza). Root cause: the model emitted
plain frames named "Image" / "Photo" / "Cover" with a solid fill as
the card-top image area, instead of using add_image_placeholder_v1
(which sets role: 'image-placeholder'). The auto-search pipeline
strict-checks role and missed all of them, so scanAndFillImages found
nothing to search and the cards stayed solid-colored.
What: new isImageAreaFrameByHeuristic(node) supplements the strict
role check. Conservative match — only fires when ALL of:
- frame node WITHOUT role='image-placeholder' (strict path handles
that)
- name matches /\\b(image|photo|cover|hero|thumbnail|thumb|picture|
banner|poster)\\b/i
- width >= 80 AND height >= 60 (filters tiny color swatches)
- exactly one solid (non-image) fill (gradient = decorative, image
= already filled, both skip)
- 0 or 1 children (an icon child is OK, multi-child = real layout)
collectImageSearchTargets walks both the strict and heuristic paths
and produces the same kind: 'placeholder-frame' target either way, so
the existing query / aspect / search code-path runs unchanged.
10 new tests cover positive matches (Image / Photo / Cover / Hero /
Thumbnail / Banner / Poster), negative for canonical placeholder
(double-counting prevented), unrelated names (Card / Wrapper),
already-filled image fills, gradients, content-rich frames, single-
icon-child acceptance, undersize frames, and non-numeric dimensions.
26 / 26 tests in image-search-pipeline.test.ts pass (was 16; +10).
1098 / 1098 AI service tests pass (unchanged).