Decksmith — Product Roadmap
Status: ✅ Done · 🔄 In progress · ⬜ Not started
Phase 0: Infrastructure
- ✅ Monorepo scaffold (pnpm + Turborepo)
- ✅ CI pipeline (GitHub Actions — format, lint, typecheck, test)
- ✅ TypeScript strict mode
- ✅ Oxlint + Oxfmt (ADR-0013)
- ✅ Shared configs in
packages/config(tsconfig, vitest) - ✅ Prisma schema (16 models) in
packages/db - ✅ Zod schemas (all domains) in
packages/schema - ✅ Fastify server + Zod type provider in
apps/api - ✅ User CRUD routes (
/api/v1/users) - ✅ Database seed script with faker.js (note:
units/sortOrder/notification fields fixed in session 13; auth.users orphan issue remains — see project-state.md) - ✅ 24 ADRs + 10 feature specs
- ✅ Vitest infrastructure (shared config + sample test)
- ✅
.env.example - ✅
packages/servicesscaffolded + tested:auth-service,user-service,ServiceError, exception mapper inapps/apierror handler — routes = pure HTTP glue (session 18, ADR-0024); 35 unit tests colocated (session 20) - ✅ pnpm 11 migration:
allowBuildsinpnpm-workspace.yaml,CI=truein pre-commit hook (session 18)
Phase 1: Documentation & Context System
- ✅ Create
apps/docs/roadmap.md(this file) - ✅ Create
apps/docs/context/project-state.md - ✅ Create
apps/docs/context/decisions-log.md - ✅ Update
CLAUDE.mdwith @import + learning contract + maintenance policy - ✅ Create
apps/docs/skills-and-agents.md - ✅ Create
.claude/WORKFLOW.md - ✅ Create 5 skills:
session.start,session.end,roadmap.update,module.scaffold,spec.sync - ✅ Create 9 subagents:
api-reviewer,db-reviewer,domain-reviewer,frontend-reviewer,ui-reviewer,a11y-reviewer,test-writer,devops-reviewer,cto-advisor - ✅ Configure GitHub MCP
Phase 2: Foundation
2.1 Environment & DB Connection
- ✅ Verify Supabase project + connection string (recreated session 21 — original project deleted after free-tier pause)
- ✅ Run
db:pushto sync Prisma schema to Supabase (re-run 2026-07-18, new project) - ✅ First real local run verified end-to-end:
pnpm dev:api+pnpm dev:web→ register + login + dashboard in browser (session 21) - ✅ Run seed script against live DB (note: seed creates orphaned profiles — use register API instead for real auth testing)
2.2 Auth (spec: user-auth.md)
- ⬜ Enable Supabase Auth + OAuth providers (Google, GitHub)
- ✅ Auth plugin in
apps/api(JWT verification middleware) - ✅ Auth routes: register, login, logout, refresh, forgot-password, reset-password,
GET /me(session 17) - ✅ Zod schemas for auth DTOs in
packages/schema/src/auth/ - ✅ RLS policies for user-owned tables (
users,user_preferences) —packages/db/sql/rls-policies.sql+ ADR-0022; applied to Supabase via the SQL Editor (session 25; 4 policies verified on theauthenticatedrole, defense-in-depth — the API bypasses RLS) - ✅ Auto-create
UserPreferenceson signup (nested Prisma write in register route)
2.3 Rate Limiting & CORS
- ✅
@fastify/rate-limitwith strict limits on auth endpoints - ✅
@fastify/corsconfigured for dev + prod origins
2.4 Docker & CI/CD (session 22)
- ✅
apps/webproduction server:node .output/server/index.mjs, deployed behind Traefik atdecksmith.<domain>/(session 24, ADR-0027) - ✅
apps/apirunsnode dist/index.js(compiled —tsxruntime dropped) - ✅
Dockerfileforapps/api(multi-stage) — 1.76GB → 380MB via Prisma 7prisma-clientgenerator +pnpm deploy --no-optional; boot +/api/healthverified - ✅
Dockerfileforapps/web(multi-stage) — nitro v3-beta still leavesreactexternalized and untraced (Cannot find module 'react', nitrojs/nitro#4171: Base UI'suse-sync-external-storeshim). Unblocked by shipping a pruned prodnode_modules(pnpm deploy --prod) next to.output, same technique as the API image (session 24, ADR-0027). Revisit for a bare self-contained.outputwhen the upstream Vite/rolldown fix — or nitro'scjsRequireRewrite(nitro#4365) — ships published. - ⬜
docker-compose.yml— local dev infra (Postgres + Redis only, apps run natively) - ✅
.dockerignore— per-Dockerfile ignores (apps/api,apps/web,deploy/statics) replaced the shared root file (session 24) - ✅ CI —
.github/workflows/deploy.yml: build api + web + statics images → push GHCR (sha + latest) → scpdeploy/compose.yml+ SSHdocker compose pull && up -d(session 24) - ✅ Reverse proxy — Traefik adopted (ADR-0026 +
apps/docs/deployment/reverse-proxy.md), replaces host-nginx + per-project Certbot. Deployed on the VPS: owns 80/443, label-driven routing, wildcard TLS via ACME DNS-01 (Let's Encrypt prod), dashboard behind IP-allowlist + basic auth. The pre-existing personal site was migrated behind it (real cert, verified). Traefik v3.7+ required (Docker Engine 29 dropped the API version older Traefik used) - ✅ Deploy Decksmith behind Traefik —
deploy/compose.ymlpulls the GHCR images (api + web + statics) on a single subdomaindecksmith.<domain>, routed by path:/api(API),/(web SSR),/docs(VitePress),/design-system(Storybook). Live + verified end-to-end (register/login, same-origin cookies) (session 24, ADR-0026 updated) - ✅ Host docs + Storybook — one nginx image serving both static sites (
/docs,/design-system); GitHub Pages retired; VitePress base/decksmith/→/docs/(session 24)
2.5 Build pipeline (before Phase 3)
Unblocks: proper compiled Docker images, faster cold starts, clean prod/dev parity.
- ✅ Each package (
utils,domain,schema,db,services) getstsconfig.build.json+buildscript + exports pointing todist/ - ⬜ Turborepo watch pipeline:
pnpm dev:apirecompiles deps on source change (not needed as-is:pnpm dev:apirunstsx --conditions=source, reading deps' TS source directly — no dist watch) - ✅
apps/apimigrated tonode dist/index.js(removetsxruntime dependency) - ✅ Docker images updated to use compiled output (
apps/api)
Phase 2.6: Observability & Release (foundations, pre-Scryfall)
Tracked as GitHub milestone "Foundations (pre-Scryfall)". Config for self-hosted services lives on the VPS (
~/infra/*), never in the repo.
- ✅ API docs —
@fastify/swagger(OpenAPI from Zod) + Scalar UI at/api/reference(#71) - ✅ Error tracking — self-hosted GlitchTip (Sentry protocol);
@sentry/node(API) +@sentry/react(web), prod-only, no-op in dev (#72) - ✅ Web source maps —
@sentry/vite-pluginuploadshiddenmaps to GlitchTip per release (token as a BuildKit secret; de-minified stacks) (#79) - ✅ Automated releases — semantic-release at the head of the deploy pipeline: tag + GitHub Release + root
package.jsonbump, version at/api/version+ web footer (ADR-0028, #77). Live at v1.1.0 - ✅ Uptime — Better Stack (external) monitoring
/api/health+ web root - ✅ Infra dashboard — self-hosted Homepage at
dashboard.<domain>behind Traefik basic auth - ⬜ Release-pipeline optim — release job on the critical path adds ~4 min/deploy (backlog, #78)
Phase 3: Scryfall Integration (spec: card-search.md)
3.1 packages/scryfall
- ✅ Scoping + décisions d'archi (2026-07-30) — bulk
default_cards,colors+colorIdentity, multi-faces option B (tableCardFace+ images{front,back}en JSON + champlayout), filtrage non-cartes (on retire digital/oversized/art_series, on garde tokens/emblems), cache repoussé en 3.2. Voir decisions-log. - ✅ ADR-0029 : modélisation cartes multi-faces + champs Scryfall (PR #82)
- ✅ Migration Prisma :
Card.colorIdentity,Card.layout, tableCardFace(clé(oracleId, faceIndex), cascade),Card.typeLinenullable, conventionCardPrint.imageUris{front,back}—db-reviewerpassé,db:pushappliqué (PR #82) - ✅ DTOs API (
packages/schema) :CardResponseSchema(colorIdentity/layout/faces[]),CardFaceSchema,CardImagesSchema{front, back?}(PR #82) - ✅
packages/scryfallscaffolded (package.json, tsconfig.build, single.export) - ✅ Zod schemas for Scryfall API responses — raw snake_case payload validation in
packages/scryfall/src/schemas/(ScryfallCard/ScryfallCardFace/ScryfallImageUris), provider knowledge (not inschema) - ✅ Card normalization (Scryfall →
Card+CardPrint+CardFace) —normalizeCardreturns a{ card, print, faces }bundle of localNormalized*types (no Prisma coupling); per-face vs shared image detection; all image sizes kept (snake→camel) - ✅
isCollectibleCardfilter (pur, testé) — drops digital-only / oversized / memorabilia / art_series; keeps tokens + emblems - ✅ Unit tests for normalization logic — 11 colocated tests (single-face, transform, split; filter drops)
- ✅ Field extension (mini-scope 2026-08-03, extends ADR-0029) — gameplay stats (
power/toughness/loyalty/defenseonCard+CardFace;keywords/producedManaonCard) +CardPrint.finishesreplacingfoil/nonfoil. Prisma migration +db-reviewer+db:push→ schema DTOs → scryfall raw schemas + normalization + tests. Follow-up #85 (collection/deckfoilboolean →finishenum). GIN index onCard.keywordsdeferred to 3.3 - ✅ Bulk data download client (streaming) —
getBulkDataInfo(metadata) +fetchBulkStream(dump bytes) +streamNormalizedCards(async generator via@streamparser/json-whatwg, backpressure,onInvalidRowskip+report). Network isolated from parsing; 3 bricks, no Prisma/batch/cron (that's 3.2). Also fixed the silently-broken scryfall build (tsconfignodepreset +.jsextensions) → CI-build-gate follow-up #91. 3.1 complete.
3.2 Initial Data Sync (apps/worker)
- ✅ ADR : worker → DB — Prisma direct (ADR-0030). Worker = pair backend, écrit les tables de référence uniquement (Card/CardPrint/CardFace), zéro règle métier, préserve le streaming de 3.1.
- ✅ ADR : job queue — BullMQ + Redis auto-hébergé (ADR-0031). Redis en conteneur (compose minimal en dev, service interne en prod), Upstash abandonné (facturation à la commande × polling BullMQ).
- ✅ BullMQ + Redis setup in
apps/worker—@decksmith/workerréel (bullmq + ioredis), factory de connexion Redis (maxRetriesPerRequest: null),docker-compose.ymldev (Redis seul), scriptdev:backend(Redis + api + worker en parallèle via turbo). Boot vérifié en local. - ✅
scryfall-card-syncjob + daily cron —Queue+WorkerBullMQ,upsertJobScheduler(cron0 6 * * *), concurrency 1 (pas de chevauchement), retries idempotent-safe. Déclencheur one-shotsync:once(sans Redis) pour l'ingestion manuelle. - ✅ Incremental update handling — check
getBulkDataInfo().updatedAtvsSyncState(comparaison par instant), skip si inchangé. - ✅ Sync-state persistence (
SyncState— source/status/lastDumpUpdatedAt/lastSyncedAt/ lastCardCount/lastError ; ADR-0031, migrationdb:push). - ✅ Batch upsert (Card/CardPrint/CardFace) —
groupChunkpur (dédup par oracleId, ordre FK) +upsertChunk(transaction par chunk, upsert sur clés naturelles = idempotent).chunkAsyncIterabledanspackages/utils. - ✅ Fix Scryfall JSONL — l'API a migré
default_cardsvers du gzip JSONL (.jsonl.gz,jsonl_download_uri/compressed_size) ; client 3.1 adapté (DecompressionStream+ parsing JSONL viareadline). Chemin de lecture prouvé en réel. - ✅ Validation end-to-end du chemin d'écriture — OK (2026-08-10).
sync:oncea upserté 34 526 cartes dans Supabase (vérifié), 81 lignes ignorées (edge layouts typereversible_card— oracle_id/cmc absents au top-level). Perf : per-row-upsert en transactions (~15 min) ; P2028 contourné (chunk 200 + timeout 60 s) ; vrai fix bulkON CONFLICT→ #96. - ✅ Déploiement du worker en prod (4ᵉ image Docker api/web/statics/worker + Redis conteneur interne,
internalonly, requirepass + AOF + volume, fail-closedREDIS_PASSWORDguard,depends_on: service_healthy) — PR #111, ADR-0031. Live + vérifié (workerUp, redishealthy, scheduler cron enregistré). Cron quotidien 06:00 UTC actif. Phase 3.2 close.
3.3 Card API
Foundations (2026-08-11):
- ✅ ADR-0032 : architecture de la recherche — endpoints par ressource (cartes seul en 3.3 ; global spotlight = fan-out futur documenté) ; dénormalisation ciblée d'attributs print sur
Card(rarities[],finishes[],firstReleasedAt) pour une recherche mono-table, recalculée par le sync (reconstructible) ;set/prix non dénormalisés. A vs B (jointuresEXISTS) pesés. - ✅ Migration Prisma :
Card.rarities/Card.finishes/Card.firstReleasedAt+CardPrint.releasedAt/CardPrint.setName(nullable/array → backfill au prochain sync) —db:pushappliqué ; DTOs (CardResponseSchema,CardPrintResponseSchema) mis à jour. - ✅ Raretés Scryfall complètes :
special+bonusajoutées partout (RaritySchema, domainRarity,RarityBadge+ tokensrarity-special/bonus, story) — l'agrégatrarities[]ne rejette plus ces cartes. Follow-up : unifier les enums MTG dupliqués (schema vs domain) → issue #113. - ✅ Sync niveau 1 (par-print) :
set_name+released_atremontés du dump →NormalizedPrint.setName/releasedAt(parse ISO→Datedéterministe) → écrits par le worker sans changement ; tests colocalisés (nominal + date absente). - ✅ Sync niveau 2 (agrégats) : passe SQL post-load —
UPDATE cards … FROMdeux CTE (array_agg(DISTINCT rarity)+min(released_at)d'un côté,unnest(finishes)de l'autre) en phase finale du job (aggregateCardAttributes) + entrée one-shotaggregate:once. Peuplerarities/finishes/firstReleasedAt. Mergé #114 ; backfill exécuté (rarities/finishes 100 %,first_released_at100 % après re-sync complet). - ✅ Fix OOM du sync (2026-08-12, #115 → #96 fermé) : le per-row upsert fuyait en mémoire (heap ~1,9 GB → OOM crash-loop en prod, sync cassé 3 j). Diagnostiqué par instrumentation (streaming innocenté, Prisma per-row confirmé). Remplacé par un bulk
INSERT … ON CONFLICTpar table (bulkUpsert{Cards,Faces,Prints}viaPrisma.sql/join,gen_random_uuid()pour les id@default(uuid())client-side). Dump complet 3m13s / <512 MB (vs ~15 min + OOM) ; P2028 absorbé par un timeout de transaction à 30 s. Prod redéployée + vérifiée saine. - ✅ Observabilité
SyncState(mergé #116) :lastCheckedAt(heartbeat sur tous les chemins)startedAt(détection d'unrunningbloqué) — comble le trou « le cron tourne-t-il ? » vs « dernier succès ».db:pushappliqué.
Endpoints:
- ⬜
GET /api/v1/cards/search(full-text + filters: color, CMC, rarity, format) - ⬜
GET /api/v1/cards/:id(card detail + prints) - ⬜
GET /api/v1/cards/:id/prints(print-selection modal) - ⬜ Autocomplete endpoint (< 200ms)
- ⬜ Indexation Postgres :
tsvectorgénéré + GIN (full-text), GIN sur les tableaux, prefix/trigram (autocomplete) — SQL brut - ⬜
useCardSearchdanspackages/query(débloque Phase 4.3)
Phase 4: Web Frontend Foundation
4.0 Design System Documentation
- ✅ Visual identity defined (palette, typography, MTG touches)
- ✅ ASCII mocks for all screens (desktop + mobile)
- ✅
apps/docs/design/— README, identity, decisions, 7 screen files - ✅ Search patterns documented (global popover,
/searchpage, deck builder slide-over) - ✅ ADR-0015: Design System Architecture
4.0.5 Fondations — sessions conversationnelles (avant implémentation)
- ✅ Session A — Architecture
packages/tokens(hiérarchie, couleurs, typo, spacing, motion) → decisions-log + ADR-0017 - ✅ Session B — Revue des libs front (routing, data, forms, animation, state, i18n, icons…) → decisions-log + ADR-0018
- ✅ Session C — Définition "composant prêt à l'emploi" (checklist, structure
packages/web-ui) → decisions-log + ADR-0019 - ✅ Session D — Stratégie de test globale (philosophie, outillage par couche, mocks, CI) →
test-strategy.md+ ADR-0006 updated
4.1 apps/web Setup
- ✅ ADR-0016: TanStack Start adoption (SSR/CSR hybrid, no backend code in apps/web)
- ✅ TanStack Start initialized (replaces plain Vite + TanStack Router)
- ✅ TanStack Query configured
- ✅ Tailwind +
packages/tokenswired (shadcn/ui deferred to 4.5) - ✅ Base routes:
/(SSR),/login,/register,/dashboard
4.2 packages/api-client
- ✅ Typed fetch client wrapping all API endpoints
- ✅ Error handling with typed error codes from
packages/schema - ✅
packages/test-utils— MSW server lifecycle,createQueryWrapper, user + preferences factories
4.3 packages/query
- ✅ TanStack Query hooks:
useUser,useUserPreferences - ⬜
useCardSearch(deferred — depends on Phase 3 Scryfall integration)
4.4 Auth UI
- ✅ Login + Register pages (session 15)
- ✅ Forgot Password page (session 15)
- ✅
useLogin,useRegister,useForgotPasswordmutation hooks inpackages/query(session 15) - ✅
ApiClientProviderwired inapps/web/__root.tsx(session 15) - ✅
@sourceforpackages/web-uiinapps/web/globals.css(session 15) - ✅ Per-route document titles (
makePageHead()helper inlib/head/) + adaptive SVG favicon (amber dark / violet light viaprefers-color-scheme) (session 16) - ✅ Cookie-based language persistence — SSR-safe, no FOUT;
createServerFnloader reads cookie server-side,parseLangFromCookieStringshared across server and client (session 16) - ✅
ThemeControlcomponent inapps/web— i18n label +ThemeToggleprimitive (session 16) - ✅
LanguageControl— cookie write on switch,mountedpattern removed (session 16) - ✅ Auth page footer —
Trans+ Heart icon + GitHub/Docs/Storybook links, fully translated EN/FR (session 16) - ✅
::selectionaccent coloring inglobals.css— amber dark / violet light via CSS vars (session 16) - ✅ Storybook stories for
apps/webcomponents (ThemeControl,LanguageControl) inComponents/App/(session 16) - ⬜ Email confirmation + password reset flow (reset-password page — blocked on OAuth/deep-link)
- ✅ Auth guard for protected routes —
_authenticatedpathless layout,beforeLoad$getMeSSR guard, dashboard moved under_authenticated/,redirectTosearch param on/login(session 17)
4.5 packages/web-ui Foundation
- ✅
apps/storybookscaffold: Storybook 10 +@storybook/addon-themes+withThemeByClassName - ✅ Design System token pages: Colors, Typography, Spacing, Radius, Shadows, Motion (PR #26)
- ✅ Semantic shadow tokens:
shadow-popover,shadow-card,shadow-overlay,shadow-accent - ✅ Button (4 variants, 3 sizes, isLoading, startIcon/endIcon, polish: shadow-accent, press effect)
- ✅ IconButton, ButtonGroup, Separator (
elaborateprop─◈─) - ✅ Toggle, ToggleGroup
- ✅ Input, Textarea
- ✅ InputGroup — composite input with inline/block addons, button, error state
- ✅ Field — FieldGroup, FieldLabel (Eyebrow style), FieldDescription, FieldError (TanStack Form)
- ✅ Storybook CI:
@storybook/test-runner+axe-playwright— play functions + a11y on every story - ✅
packages/utilsscaffolded:noopfunction with colocated tests - ✅ Surface — bare elevation primitive (
surface/raisedvariants, configurable padding) - ✅ Card, ButtonCard, LinkCard — semantic card family (static + interactive; a11y: focus ring, press state, keyboard nav)
- ✅ Badge, Tag — status badges + removable Tag with close button
- ✅ Select — dropdown picker (Base UI) with groups, multi-select, error state
- ✅
packages/tokensworldclass audit: dead TS layer deleted,tokens.cssis the single source of truth, font fallbacks hardened, shadow scale comment corrected (ADR-0017 updated) - ✅ Dialog, AlertDialog — Base UI modal + confirmation dialog (PR #36)
- ✅ Floating components: DropdownMenu, ContextMenu, Tooltip, Popover (PR #36)
- ✅ Form primitives: Checkbox, Radio, Switch (PR #36)
- ✅ Kbd, NavigationButton, DeleteButton +
useArmedStatehook (PR #36) - ✅ Icon sizing centralized:
ICON_IN_CONTROL,ICON_INLINEtables + ADR-0021 (PR #36) - ✅ Toast, Drawer — Base UI components complete (PR #38)
- ✅ Design tokens from
packages/tokensapplied (semantic token classes across all components) - ✅
packages/domainscaffolded:MtgColor,ColorIdentity,SnowMana,VariableManatypes +parseManaCost,sortColorIdentity,getColorIdentityName— 30 unit tests (PR #32) - ✅ MTG primitive components:
ManaIcon(pure SVG),HybridManaSymbol(diagonal split pip via SVG clipPath),ManaSymbol(router),ManaCost(pip row from cost string),ColorIdentity(WUBRG-sorted pip row +role="img"aria-label) — Storybook stories underComponents/MTG/…(PR #32) - ✅ Skeleton — 4 shapes (
text/control/block/circle),motion-safe:animate-pulse,aria-hidden, dimension from callerclassName(session 16) - ✅
useLocalStorage<T>— SSR-safe hook, sync write (session 16) - ✅
ThemeProvider,useTheme— initially localStorage-based (session 16); rewritten to cookie-based SSR pattern in session 17 —theme-cookie.ts(THEME_COOKIE,DEFAULT_THEME,VALID_THEMES,parseThemeFromCookieString),initialThemeprop, anti-FOUC inline script eliminated;ThemeToggle(Switch with Sun/Moon thumb icon,--accent-icontoken) (session 16) - ✅
theme-cookie.tspure function + 6 colocated unit tests (session 17) - ✅
useMediaQuery(query)—useSyncExternalStore, reactive, SSR-safe;useBreakpoint()semantic shortcut (isMobile/isTablet/isDesktop);BREAKPOINTSconst (session 16) - ✅
useKeyboardShortcut(shortcuts, handler)— wraps tinykeys, ref-stabilized callback, SSR-safe (session 16) - ✅
TextLink— styled<a>,default/subtlevariants, exportstextLinkVariantsforAppLinkreuse;AppLinkinapps/webwraps TanStack RouterLink(session 16) - ✅
Text size="xs"added to typography scale (session 16) - ✅
--accent-icontoken (#5b4fcfboth modes) for static-violet icon tints (session 16)
Phase 5: Internationalisation (i18n)
Dependency: Phase 4.1 (apps/web initialized)
- ✅ ADR-0025: i18n strategy — API sends codes, client translates (session 19)
- ✅ Replace hardcoded English strings in
packages/schemaZod validators with error codes (PASSWORD_TOO_SHORT,USERNAME_INVALID_FORMAT,HEX_COLOR_INVALID,SLUG_INVALID, etc.) (session 19) - ✅
packages/i18nscaffolded: shared translation package (auth/common/errors namespaces, EN + FR);apps/web/src/locales/deleted — all strings now inpackages/i18n; multi-namespace init inapps/web/src/i18n.ts;get-field-errorextended with optionaltparam for inline field translation (session 19) - ✅ i18n library in
apps/web(react-i18next) with locale files (EN + FR baseline) — done in session 14; cookie-based SSR persistence +Transcomponent added session 16 - ✅
Accept-LanguageNOT needed inapps/api— ADR-0025 decision: API is locale-agnostic, translation is a client concern - ⬜ i18n in
apps/mobile(Expo Localization)
Phase 6: Collection Management (spec: collection.md)
6.1 Collection API
- ⬜ CRUD for collection entries + folders
- ⬜ Domain logic in
packages/domain - ⬜ Tags CRUD + attach/detach
6.2 Collection UI
- ⬜ Inventory page (grid/table/list views)
- ⬜ Add card flow (search → print → quantity/condition)
- ⬜ Saved views, folder nav, tag UI
Phase 7: Deck Management (spec: deck-management.md)
7.1 Deck API
- ⬜ CRUD for decks + sections + cards
- ⬜ Format validation (singleton, color identity, banlists)
- ⬜ Collection coverage calculation
- ⬜ Deck statistics (mana curve, colors, avg CMC)
- ⬜ Public deck sharing
7.2 Deck UI
- ⬜ Deck list + creation flow
- ⬜ Deck builder (sections sidebar + card grid)
- ⬜ Format validation feedback, coverage indicator, stats panel
- ⬜ Public share page
/decks/:id(no auth)
Phase 8: Pricing (spec: pricing.md)
- ⬜ Extend Scryfall sync for prices (TCGplayer USD + Cardmarket EUR)
- ⬜
GET /api/v1/collection/valuation - ⬜
GET /api/v1/decks/:id/cost - ⬜ Pricing UI in collection view + deck builder
Phase 9: PDF Generation (spec: pdf-generation.md)
9.1 packages/pdf
- ⬜ Deterministic PDF layout engine
- ⬜ Paper formats, grid config, margins, cut lines, DPI, double-sided
- ⬜ Unit tests for layout math
9.2 Worker Infrastructure
- ⬜ Redis — self-hosted container (introduit en Phase 3.2, ADR-0031 ; Upstash abandonné)
- ⬜ BullMQ PDF job
9.3 PDF API + UI
- ⬜
POST /api/v1/pdf(enqueue) + GET status + GET download - ⬜ Preview panel, config form, job status polling
Phase 10: 3D Card Viewer (spec: card-details.md)
- ⬜ Three.js card component in
packages/web-ui - ⬜ Rotation, zoom, animated foil shader
- ⬜ Fallback for low-end devices
Phase 11: Craft Guide (spec: craft-guide.md)
- ⬜
CraftGuideArticleseed data - ⬜
GET /api/v1/craft-guide(list + single) - ⬜ Article list + reader UI
Phase 12: Recommendations
- ⬜ Recommendation engine in
packages/domain - ⬜ API routes + feedback endpoints
- ⬜ Recommendations panel in deck builder
Phase 13: Documentation Site
- ✅
apps/docs/with VitePress (docs live here directly) - ✅ Deployed at
decksmith.<domain>/docsbehind Traefik (session 24). GitHub Pages (.github/workflows/docs.yml) retired — replaced by the VPS static image.
Phase 14: Mobile App (apps/mobile)
Dependencies: Phase 6 + 7 stable on web
14.0 Token strategy — conversation préalable
- ⬜ Rediscuter l'architecture de
packages/tokenspour le mobile : migrer vers Style Dictionary (source unique → sorties CSS vars pour web + objets JS pour RN) ou valider que l'export dual manuel (native/) est suffisant. Décision prise en Session A (Phase 4.0.5) : export dual manuel pour l'instant, à réévaluer ici.
14.1 Setup
- ⬜ Initialize Expo + React Native in
apps/mobile - ⬜ Configure
packages/native-ui(React Native components) - ⬜ Auth: Supabase Auth for React Native (
expo-auth-session) - ⬜ Navigation: Expo Router
- ⬜ Shared:
packages/api-client,packages/schema,packages/domain(reused as-is)
14.2 Core Features
- ⬜ Auth flow (login, register, session)
- ⬜ Card search (camera barcode scan → card lookup)
- ⬜ Collection management (add/view/edit entries)
- ⬜ Deck list + view (read-only, edit later)
- ⬜ PDF generation trigger + download
14.3 Mobile-Specific
- ⬜ Push notifications (Expo Notifications) for price alerts, PDF ready
- ⬜ Offline mode for collection browsing
- ⬜ Camera integration for card scanning
Evergreen
- ⬜ Write unit tests as each module is implemented
- ⬜ Update ROADMAP per session (
/roadmap.update) - ⬜ Update
apps/docs/context/project-state.mdper session (/session.end) - ⬜ Check dependency updates at session start (
/session.start) - ⬜ Create ADRs for new architectural decisions