App-icon generation: evt-new-fs-id handler + /vibes-icon endpoint#1409
Open
App-icon generation: evt-new-fs-id handler + /vibes-icon endpoint#1409
Conversation
…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>
Preview DeploymentPreview URL: https://pr-1409-vibes-diy-v2.jchris.workers.dev Updated on each push to this PR. Worker is deleted when the PR closes. |
There was a problem hiding this comment.
💡 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".
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Revives the b/w app-icon generation that lived in the removed
hosting/worker (commit7f2eebfd), on the v2 queue +app_settingsstack.ActiveIcon(stored inapp_settings.settingsalongsideActiveTitle/ActiveSkills) andEvtIconRepair. Surfacesactive.iconvia theensureAppSettingsreadback.processScreenShotEventonevt-new-fs-id; also handles a newevt-icon-repair. CallsLLM_BACKEND_URLin image-modality mode (openai/gpt-5-image-mini,modalities:[text,image]), keyed by the app'sactive.title(falls back toappSlug). 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 throughstorage.ensure; the CID lands in anActiveIconentry on the app-settings row.ActiveIconalready exists — duplicateevt-new-fs-iddeliveries and lazy-repair pings are both idempotent.GET /vibes-icon/:userSlug/:appSlugstreams the PNG from storage. On 404 (missing/generating) it enqueuesevt-icon-repairfor lazy hydration, so any surface rendering the<img>triggers gen.ChatHeaderContentrenders<img src="/vibes-icon/{u}/{a}">left of the title; each row in theRecentVibessidebar gets the same thumbnail slot. Both hide viaonErrorwhen 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)GET /vibes-icon/:u/:areturns 200 with a black-on-white PNGevt-icon-repair→ icon appears after next renderevt-new-fs-idor rapid reloads → queue handler skips (only oneactive.iconentry ever lands)LLM_BACKEND_URL→ CF queue retries with backoff, no phantomactive.iconwrittenNot in scope
🤖 Generated with Claude Code