Ashesh Srivastava
← home

Canvas — an IDE for image generation

A three-panel workspace where generations run side by side, research happens next to the prompt, and the price shows up before you hit Enter.

0→1Solo buildCost-aware UXAI product engineering
Role
Designed, built, and shipped solo.
Timeline
March to July 2026.
Status
Open-sourced: github.com/as2621-dev/Canvas
Tech
Next.js + TypeScript · Supabase (Postgres) · Trigger.dev job queue · multi-model routing (Gemini + fal.ai) · half-price batch lane · cost reservation & settlement.

Canvas is what an IDE looks like when it's built for image generation instead of code: your library on the left, generation workspaces in the middle, a research panel on the right, and the price of every run printed on the button before you press it.

Where the idea came from

I used to generate images with Higgsfield almost daily, then turn the good ones into video. The loop never changed: type a prompt, wait, switch to ChatGPT to fix the next prompt, switch back, run again. References in a downloads folder, drafts somewhere else, one generation at a time. The waiting was the whole experience.

Developers don't write code in a chat box. They get an IDE — files on the left, editors in the middle, docs on the right, one window. Generating images is work in exactly the same way. So why was I doing it through a prompt box and four tabs?

The whole product is one decision: treat image generation as work, not as chat.

One window, three panels

What's below is a live, clickable demo of the whole platform — three workspaces running side by side, the library's folders showing their images on the left, the research panel's modes on the right. Poke around. The generations in it are real outputs from the app's own database. Read it like an IDE:

A project mid-flight: three workspaces side by side — NBPro on the batch lane, NB2, and Kling3 on video — each with its own prompt, settings, and price on the Generate button. Everything is clickable.open full page ↗

Left: the library

Every project opens with the same three folders — the split source control already makes:

Every folder is a live drop target, so filing an image is one drag. My old system was a downloads folder sorted by date.

Middle: parallel workflows

The panel that exists because of all that waiting. Every workspace has its own model, settings, and queue — in the demo above:

When one lane is generating, you move to the next instead of watching. Each header keeps its own count and running cost (Workspace A — 0 imgs · 0¢), tabs are color-coded, and you can open as many workspaces as you need. Since each workspace picks its own model, parallel lanes double as model comparison: same prompt, two models, side by side. And for images that don't need to land right now, there's a batch-processing API — slower runs, at a 50% discount.

Right: research without leaving

The third panel exists to kill the second tab. Three modes:

The panel itself — click between Search, Ask, and Plan to see each mode, including the agent conversation that goes and finds references.open full page ↗

Results drag straight into a prompt or into Inspiration. Before this, I was the integration layer: search in a browser, screenshot, paste into ChatGPT, carry it all back by hand.

The price is on the button

Every run shows its price before you start it — the button itself reads , and it updates as you change model, quality, or count. Credits that arrive after the decision are what make generation feel like gambling; real money on the button is the fix. It's honest about quality too: the batch lane is half price but capped at 1K, so the size picker for batch simply doesn't offer 4K. The cheap lane tells you what it costs you at the same moment it tells you the price.

How a generation actually runs

Generate posts to an API route that writes a job row; Trigger.dev runs the work; the client subscribes to the job and renders outputs as they land. One submission is one job with N child outputs — the client never tracks images in flight, because client-held state the server can't contradict is how you get spinners that never stop. Cost is reserved at start, settled at completion, and a sweeper reclaims reservations from jobs that die. Self-hosted, on your own Gemini and fal.ai keys, with markup set to zero.

Browser → Next.js → Trigger.dev → providers
One generation, end to end
generation lane — queued in Postgres, run by Trigger.devplanning & search lane — inline from route handlersjob lifecycle — reserve · poll · sweep
Next.js route handlersSRC/APP/APITrigger.dev workerSRC/TRIGGER · NPM RUN TRIGGER:DEVCmd+Enter — the workspace submits the prompt to POST /api/generateThe route reserves a queued generation row in Postgres before any provider callRESERVE ROWThe route fires a Trigger.dev task and returns — generation is a background jobgenerate-image and generate-image-batch call Gemini (gemini-3.1-flash-image-preview / gemini-3-pro-image-preview)generate-image-fal and generate-video call fal.ai (gpt-image-2, seedance, kling 3, grok imagine)The task uploads the output to the generated-images bucket and finalises the rowUPLOADThe client polls GET /api/generate/[generation_id]/status — it never holds job state itselfPOLLsweep-stale-generations runs every 5 minutes and fails rows whose job died hardCRON */5Plan, Ask, and prompt-enhance requests from the planning panel and composerPlan-chat and enhance call Gemini text models inline; plan replies pass an eval and repair loopSearch mode queries from the planning panelThe search route calls Serper.dev Google Images, plus Firecrawl scraping in agentic modeThe browser — project tree · 3 parallel workspaces · planning panelCLIENTThe browserproject tree3 parallel workspacesplanning panelprice on the buttonSerper.dev — google images API · + Firecrawl scrapeSEARCH APISSerper.devgoogle images API+ Firecrawl scrapeGemini API — nano banana 2 / pro · text · 2.5 pro / flashPROVIDERGemini APInano banana 2 / protext · 2.5 pro / flashyour key, billed directfal.ai — gpt-image-2 (image) · seedance · kling 3 · grok imagine (video)PROVIDERfal.aigpt-image-2 (image)seedance · kling 3grok imagine (video)Supabase — Postgres · Auth · Storage (generated- / uploaded- / reference-images)DATASupabasePostgres · Auth · Storage (generated- / uploaded- / reference-images)/api/search — Serper images query, agentic: Firecrawl scrape, LRU cache · 5 min TTL/api/searchSerper images queryagentic: Firecrawl scrapeLRU cache · 5 min TTL/api/plan-chat · /api/enhance — plan & ask · gemini-2.5-pro, enhance · gemini-2.5-flash, eval → repair → retry/api/plan-chat · /api/enhanceplan & ask · gemini-2.5-proenhance · gemini-2.5-flasheval → repair → retryPOST /api/generate — price via lib/pricing.ts, reserve row as queued, fire task · client pollsPOST /api/generateprice via lib/pricing.tsreserve row as queuedfire task · client polls4 generation tasks — generate-image / -batch, -fal / -video, output → generated-images4 generation tasksgenerate-image / -batch-fal / -videooutput → generated-imagesstale-row sweeper — cron */5 · fails zombiesstale-row sweepercron */5 · fails zombies
The full path: routes in canvas/src/app/api, task ids in canvas/src/trigger, prices in src/lib/pricing.ts. The dashed lane — reserve, poll, sweep — is the billing lifecycle.

Decisions

The layout is a handful of deliberate calls. The project tree lives on the left because that is where an IDE keeps its files, so the work you are keeping stays one glance away instead of buried in a feed. The research panel lives on the right so looking something up never means leaving the window where the prompt is. And cost shows up wherever a decision happens: every workspace header carries a running total and the average per image, and the Generate button prints the price of the run before you press it.

That combination is also why Canvas beats the alternatives for real work. Prompt box tools like Higgsfield and Midjourney make you wait in one queue and tell you what you spent afterwards, in credits. ComfyUI gives you real control, but you build the wiring before you get any of it. Canvas puts the files, the research, and the numbers in one window and lets the lanes run side by side. The existing shapes are built around the output. Canvas is built around the work.

What I learned