Paste a Moxfield decklist, get card images and prices, an EDHREC "salt" score, a WotC bracket classification, a ramp/draw/removal/wipes composition report, and a short Claude-written review — all streamed into the browser as each piece of work finishes.
It's a demo app for Inngest: one function, several parallel fan-outs, a classification step, streamed over Inngest Realtime.
Most of the interesting code is in src/inngest/functions.ts.
- Scryfall enrichment runs as concurrent
step.runcalls in 8-card batches, so each batch retries and caches independently. - Salt score lookups fan out in parallel with the Scryfall work.
- Four bracket signal checks (Game Changers, tutors, mass land denial, combos) run as four more parallel
step.runlegs, each publishing to the realtime channel as it lands. The classifier step consumes all four once they resolve. - A composition step fetches four Scryfall oracle tags (ramp / draw / removal / board-wipe) and counts each category against conventional Commander deckbuilding targets.
- Each card shows up in the UI the moment its batch resolves; each bracket signal appears in the panel as its step returns. After everything resolves, a final
ai-reviewstep hands the enriched deck to Claude for a paragraph-long review. - Every external call is best-effort: Commander Spellbook or the Scryfall tagger failing downgrades the relevant panel to "unknown" without failing the run.
- Event IDs are
${pageId}:${runId}(seesrc/routes/api/deck/analyze.ts), so client retries collapse to a single run.
- TanStack Start (React 19, Vite, file-based routing)
- Inngest for durable functions and realtime
- Scryfall for card data and oracle tags, EDHREC for salt scores, Commander Spellbook for combo detection
- Claude for the deck review
- Tailwind v4, Biome, Vitest
- Cloudflare Workers via Wrangler
You'll need Node 20+, pnpm, an Anthropic API key, and the Inngest Dev Server.
cp .env.example .env # fill in ANTHROPIC_API_KEY
pnpm install
npx inngest-cli@latest dev # one terminal — http://localhost:8288
pnpm run dev # another — http://localhost:3000Open http://localhost:3000, paste a decklist in Moxfield's "Export → Text" format (4 Lightning Bolt (LEA) 161, one per line), and watch the Inngest dashboard at http://localhost:8288 as cards stream in.
src/
inngest/
client.ts Inngest SDK client
functions.ts analyzeDeck function + realtime channel/topics
schemas.ts Zod event payload schemas
routes/
index.tsx single-page UI
api/
inngest.ts Inngest HTTP handler
deck/analyze.ts POST endpoint that sends the analyze event
realtime/token.ts mints realtime subscription tokens
moxfield/ decklist parser
scryfall/ Scryfall types, stats helpers, oracle-tag lookups
edhrec/ salt score fetcher
brackets/ WotC bracket signal checks + classifier
composition/ ramp/draw/removal/wipe counts vs. deckbuilding targets
pnpm run dev # vite dev server
pnpm run build # production build
pnpm run test # vitest
pnpm run typecheck # tsc --noEmit
pnpm run check # biome lint + format
pnpm run deploy # build + wrangler deployThis is a demo, not production code. Two shortcuts worth knowing about: the realtime token endpoint mints tokens for any pageId with no session check, and the publish loop is staggered so the streaming is actually visible in a demo. Both are called out in comments — see src/routes/api/realtime/token.ts and src/inngest/functions.ts.
MIT — see LICENSE.