Skip to content

App-icon generation: evt-new-fs-id handler + /vibes-icon endpoint#1409

Open
jchris wants to merge 8 commits intomainfrom
jchris/icon-gen
Open

App-icon generation: evt-new-fs-id handler + /vibes-icon endpoint#1409
jchris wants to merge 8 commits intomainfrom
jchris/icon-gen

Conversation

@jchris
Copy link
Copy Markdown
Contributor

@jchris jchris commented Apr 20, 2026

Summary

Revives the b/w app-icon generation that lived in the removed hosting/ worker (commit 7f2eebfd), on the v2 queue + app_settings stack.

  • Types: adds ActiveIcon (stored in app_settings.settings alongside ActiveTitle/ActiveSkills) and EvtIconRepair. Surfaces active.icon via the ensureAppSettings readback.
  • Queue handler: sibling to processScreenShotEvent on evt-new-fs-id; also handles a new evt-icon-repair. Calls LLM_BACKEND_URL in image-modality mode (openai/gpt-5-image-mini, modalities:[text,image]), keyed by the app's active.title (falls back to appSlug). Revives the original prompt: "Minimal black icon on a white background, enclosed in a circle, representing ${category}. Use clear, text-free imagery to convey the category. Avoid letters or numbers." Decoded PNG bytes go through storage.ensure; the CID lands in an ActiveIcon entry on the app-settings row.
  • Dedup: handler skips if ActiveIcon already exists — duplicate evt-new-fs-id deliveries and lazy-repair pings are both idempotent.
  • Endpoint: GET /vibes-icon/:userSlug/:appSlug streams the PNG from storage. On 404 (missing/generating) it enqueues evt-icon-repair for lazy hydration, so any surface rendering the <img> triggers gen.
  • UI: ChatHeaderContent renders <img src="/vibes-icon/{u}/{a}"> left of the title; each row in the RecentVibes sidebar gets the same thumbnail slot. Both hide via onError when 404 comes back, so first-load looks clean and refills on re-render once the icon lands.

Test plan

  • pnpm check (build + lint + test) clean locally — 569 pass / 11 skipped (same flake set as Prompt pre-allocation: LLM-driven title/slug + skills on first prompt #1398, non-deterministic)
  • New chat from home form → within ~20-30s of the code block landing, GET /vibes-icon/:u/:a returns 200 with a black-on-white PNG
  • Header refresh shows the icon left of the title
  • Sidebar "Recent Vibes" rows render their icons
  • Open a pre-existing vibe → 404 fires evt-icon-repair → icon appears after next render
  • Duplicate evt-new-fs-id or rapid reloads → queue handler skips (only one active.icon entry ever lands)
  • Block LLM_BACKEND_URL → CF queue retries with backoff, no phantom active.icon written

Not in scope

  • User-editable icon via settings UI / regenerate button
  • Regen on every code update (only on first upload + 404-driven lazy repair)
  • Sweeper/cron backfill for every historical vibe

🤖 Generated with Claude Code

J Chris Anderson and others added 6 commits April 20, 2026 13:59
…via ensureAppSettings

- ActiveIcon entry shape {type: active.icon, cid, mime} stored in
  app_settings.settings alongside ActiveTitle and ActiveSkills.
- AppSettings.entry.settings.icon projection so ensureAppSettings
  readback surfaces {cid, mime} to the client.
- EvtIconRepair queue event for the lazy-backfill path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both secrets already flow through the deploy action for the queue
consumer worker (actions/deploy/action.yaml:178-179). Expose them on
CFEnv and carry them into QueueCtx.params.vibes.env so upcoming queue
handlers can call the LLM backend for image generation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a sibling queue handler that produces an app icon via the LLM
backend (openai/gpt-5-image-mini, modalities:[text,image]) keyed by the
active.title in app_settings (falls back to appSlug). The decoded PNG is
stored via storage.ensure; the resulting CID lands as an ActiveIcon
entry in app_settings.settings, leaving title/skills untouched.

Dedup: if an ActiveIcon entry already exists for the (userSlug, appSlug,
userId) row, the handler returns Continue without calling the LLM. This
makes duplicate evt-new-fs-id deliveries idempotent and lets the lazy
evt-icon-repair path fire safely on every resume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reads app_settings.settings for the (userSlug, appSlug) row, finds the
ActiveIcon entry, and streams the stored asset bytes via storage.fetch.
Mirrors cid-asset shape but keyed by slugs directly so the UI can
derive src="/vibes-icon/{userSlug}/{appSlug}" from the route params
alone — no extra data threading through the sidebar list response.

Cache-Control is 1h + stale-while-revalidate=24h rather than 1y
immutable, leaving room for a future regenerate flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Instead of wiring lazy backfill into ensureChatId (which only fires on
chat-route loads), the direct-serve endpoint itself enqueues the repair
event on every 404. Any surface that renders an <img src> triggers gen
— sidebar, header, future pages — without a separate client-side ping.

The queue handler's existing idempotency check (skip if ActiveIcon
already present) covers rapid bursts. Two concurrent workers can still
both call the LLM once on first view; acceptable for v1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both surfaces derive `src="/vibes-icon/{userSlug}/{appSlug}"` from route
state, so no extra data threading through openChat or listUserSlugAppSlug
is needed. On 404 the endpoint enqueues a repair event and each <img>
hides itself via onError; the next render after icon-gen completes
picks up the asset via normal browser caching.

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

github-actions Bot commented Apr 20, 2026

Preview Deployment

Preview URL: https://pr-1409-vibes-diy-v2.jchris.workers.dev
Worker: pr-1409-vibes-diy-v2
Commit: 0509f15


Updated on each push to this PR. Worker is deleted when the PR closes.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b22272746a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vibes.diy/api/svc/public/vibes-icon.ts Outdated
Comment thread vibes.diy/api/svc/public/vibes-icon.ts Outdated
Comment thread vibes.diy/api/queue/handlers/evt-icon-gen.ts
Comment thread vibes.diy/pkg/app/components/ChatHeaderContent.tsx
J Chris Anderson and others added 2 commits April 21, 2026 05:47
…le-cid clear

Addresses three review comments on the /vibes-icon endpoint:

- [P1] Only enqueue evt-icon-repair when the app actually exists. Public
  route used to enqueue on every 404 — arbitrary slugs could drive
  unbounded queue churn. Now gates on appSlugBinding lookup.
- [P1] Separate DB read failures from not-found responses. rRow.isErr()
  now returns 500 + ensureLogger Error; only a missing row returns 404.
- [P2] When storage.fetch(iconEntry.cid) returns NotFound, clear the
  stale ActiveIcon entry in app_settings and fall through to the normal
  not-found + repair path. Prevents a deleted/corrupted blob from
  permanently wedging the app with a dangling cid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sticky iconFailed flag meant navigating between vibes in the same
mount carried the first vibe's 404 over to the next. Reset via a tiny
useEffect([userSlug, appSlug]) in both surfaces. Kept the "fail and
show placeholder, retry on next page load" semantics — no client-side
polling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant