openpencil/apps/web
Fini f7e776de4f fix(ai): image search pipeline picks up role:image-placeholder frames
The `add_image_placeholder_v0` / `_v1` element tools and JSONL payloads
that mimic them emit a `frame` carrying `role: 'image-placeholder'` (a
gray slate-100 box + centered icon_font child + optional label) — NOT
an `image` node. The auto-search pipeline only filtered on
`type === 'image'`, so every placeholder produced via element tools
silently bypassed the search hook. Latest GPT-5.5 food-app run shipped
8 placeholder frames; zero got auto-filled and the design landed with
all dashed-border icons instead of real photos.

Pipeline now:
- `isImagePlaceholderFrame` predicate identifies placeholder frames.
- `collectImageSearchTargets` returns mixed `{node, kind}` pairs
  ('image' for `type==='image'` with placeholder src, 'placeholder-frame'
  for the role-keyed frames). Skips descending into placeholder
  children (icon_font + label get wiped on fill anyway).
- `enqueueImageForSearch` accepts both shapes; queue items track `kind`.
- `processQueue` re-checks the right invariant per kind, and on success
  uses `updateNode(id, { fill: [{type:'image',url,mode:'crop'}], children: [] })`
  for placeholder frames (vs `updateNode(id, { src })` for image nodes).
  Clearing children prevents the icon/label from rendering on top of
  the searched photo.
- Streaming path (insertStreamingNode line 382) intentionally still
  gates on `type === 'image'` — placeholder frames stream their
  children separately, so enqueueing mid-stream would race with the
  late-arriving icon. Placeholder frames are only enqueued via the
  post-tree `scanAndFillImages` scan (orchestrator-tail + dispatcher
  per-subtask), where the full tree is already in the doc.

`extractQueryForNode` looks for `imageSearchQuery` first, falls back
to a non-default `name`, then mines the optional
`role: 'image-placeholder-label'` text child for a hint. Generic
default still works ("placeholder") if nothing useful is on the frame.

7 new tests cover `isImagePlaceholderFrame` and
`collectImageSearchTargets` (placeholder + image mix, no descent into
placeholder children, missing root id).
2026-05-05 03:57:30 +08:00
..
public V0.5.0 (#67) 2026-03-22 09:44:04 +08:00
server Merge feat/rust-ification into v0.8.0 (Step 0 Rust workspace bootstrap) 2026-05-04 21:00:00 +08:00
src fix(ai): image search pipeline picks up role:image-placeholder frames 2026-05-05 03:57:30 +08:00
CLAUDE.md V0.7.0 (#95) 2026-04-11 23:25:13 +08:00
components.json V0.7.0 (#95) 2026-04-11 23:25:13 +08:00
dev.ts V0.7.2-bugfix (#109) 2026-04-14 21:42:56 +08:00
package.json Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00
tsconfig.json V0.7.2-bugfix (#109) 2026-04-14 21:42:56 +08:00
vite.config.ts Merge origin/v0.8.0 into feat/rust-ification 2026-05-03 21:00:00 +08:00