Easydeck
Easydeck
An AI presentation generator built around a constraint I still like: keep planning cheap and editable, make rendering the thing that costs money.
Project lives at: ~/code/planetaryescape/easydeck/
In one paragraph
Easydeck takes a prompt or a rough outline, turns that into a real slide plan, lets the user edit the outline, then generates the actual slide images and exports. The sharp architectural choice is the billing boundary. The app does not spend credits when the user merely clicks "generate." It waits until the outline parser knows how many slides the job will really create, reserves that exact budget, and only settles the spend when slide creation happens.
Why it exists
The obvious problem is "make me a presentation." The less obvious problem is that one-shot deck generation usually steals control at the wrong moment. Easydeck's answer is to put the editable outline in the middle: enough AI to get momentum, enough structure that the user can still steer before the expensive part starts.
What is true in code as of 2026-05-31
Validated against the repo:
- Credits are priced per slide render, not per presentation kickoff.
- Free users currently start with
20starter credits. - Those starter credits live in
topupCredits, not the monthly subscription bucket. availableCreditsexcludes credits already reserved by other in-flight presentations.- Exact reservation happens after outline parsing, when the real slide count is known.
- Slide creation settles the reservation into a spend.
- Monthly resets preserve
topupCredits.
That is a cleaner model than the earlier version I had in my head. The old instinct was "charge when generation starts." The code truth now says "price when the plan is concrete." Much better.
GA hardening update from 2026-05-31
Validated against the current PR head and repo docs:
- Rate limiting uses Upstash when
UPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKENexist. If either is missing, or if Upstash fails, the app falls back to in-memory limits and keeps running. NEXT_PUBLIC_APP_URLis required for checkout/portal flows. In production it must be public HTTPS, not localhost,.local, a single-label host, or a private IP.CONVEX_INTERNAL_SECRETis the shared internal trust boundary for user sync and internal Web <-> Convex calls. User-sync mutations now require it at the argument-validator level.- Clerk webhook tasks only read
CONVEX_INTERNAL_SECRETfor handleduser.created,user.updated, anduser.deletedevents. Other Clerk event types should not fail just because the secret is absent. - Presentations are private by default. Public gallery/share reads strip speaker notes.
- Remote presentation control now uses a
controllerToken. New sessions mint one and public session data strips it. Old active sessions without the token may need restarting if they were created before the field existed. - Convex production backups and production restores both need
--prod; otherwise the CLI defaults can aim at the wrong deployment. - The CI break was a dependency-compatibility bug, not a TypeScript bug: forcing
file-type@22broke Jimp's oldfile-type/core.jsimport. The durable fix was upgrading Jimp instead of keeping the override. - BYOK exists in code for user provider keys, but older names still say BYOD. The env var is still
BYOD_ENCRYPTION_KEY; the current product surface is encrypted user API keys, not a user-owned database.
The new general lesson is not "always degrade gracefully." It is sharper: degrade gracefully for availability guardrails like rate limiting, but fail closed for authority, billing, and public URL safety.
AI model-routing update from 2026-06-23
The 2026-05-31 lessons still held when rechecked against the code. The new drift was in model routing, not credits or privacy.
Current code truth:
- Design-system generation now uses
google:gemini-2.5-flashthroughDESIGN_SYSTEM_GENERATION_MODEL. - Template analysis uses
google:gemini-3-flash. - Document extraction still points at
google:gemini-2.0-flash. - Presentation generation reads runtime model choices from
apps/web/src/lib/ai/operational-models.ts, not frompackages/ai. - There is no docs website or
/docsApp Router surface in this checkout; the docs surface is repo Markdown plus app copy.
The live gateway check on 2026-06-23 confirmed the important bit: google:gemini-2.0-flash still returns model_not_found for this Vercel AI Gateway account, while google:gemini-2.5-flash and google:gemini-3-flash succeed.
The reusable lesson is Model Catalogs Need Runtime Smoke Tests. A model appearing in local metadata or an SDK type union is not enough. Production model choices need a smoke test through the same registry, account, gateway options, and environment the app uses.
Reusable lessons this project surfaced
- Reserve Spend at the Planning Boundary - the right moment to reserve budget is after planning, before execution
- Available Balance Must Exclude Reservations - a balance the user cannot actually spend is not the balance you should gate on
- Generated Docs as Drift Defense - even without a dedicated docs site, README files and pricing pages are still docs surfaces that drift
- Graceful Degradation and Progressive Enhancement - graceful fallback is valid for optional guardrails, not for authority
- Security Overrides Need Compatibility Proof - an audit fix still needs proof that consumers can import what they expect
- Model Catalogs Need Runtime Smoke Tests - model availability is a live integration fact, not just a catalog entry
Documentation drift this project exposed
The 2026-05-31 docs pass found a few different kinds of drift:
- The root README still described the app as "Gemini for image generation" even though the code now spans Seedream, GPT Image 2, and Gemini 3 Pro Image.
- The backend README was still the stock Convex scaffold, complete with
npxcommands in a Bun-only repo. - The pricing UI still had at least one stale "10 free credits" claim while code truth was
20. - Several Playwright specs still point at
/chat, but the current App Router tree no longer exposes a/chatroute. That feels like stale product history, not current product truth. - The email template links to
https://easydeck.app/docs/byod, but this repo has no docs website or/docs/byodApp Router page. The repo now has a BYOK architecture note, but the public route gap is still real.
That mix matters. Some drift lived in docs. Some lived in app copy. Some lived in tests and comments. Same root problem: the old story kept surviving after the architecture moved.
What I would want future-me to remember
Easydeck is not just "AI makes slides." The more interesting part is the boundary discipline around cost, planning, and user control:
- planning first
- reserve exactly once the job is concrete
- show available budget, not fantasy budget
- keep purchased credits separate from resettable credits
That pattern will outlive this app.