Skip to content

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/services scaffolded + tested: auth-service, user-service, ServiceError, exception mapper in apps/api error handler — routes = pure HTTP glue (session 18, ADR-0024); 35 unit tests colocated (session 20)
  • ✅ pnpm 11 migration: allowBuilds in pnpm-workspace.yaml, CI=true in 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.md with @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:push to 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 the authenticated role, defense-in-depth — the API bypasses RLS)
  • ✅ Auto-create UserPreferences on signup (nested Prisma write in register route)

2.3 Rate Limiting & CORS ​

  • ✅ @fastify/rate-limit with strict limits on auth endpoints
  • ✅ @fastify/cors configured for dev + prod origins

2.4 Docker & CI/CD (session 22) ​

  • ✅ apps/web production server: node .output/server/index.mjs, deployed behind Traefik at decksmith.<domain>/ (session 24, ADR-0027)
  • ✅ apps/api runs node dist/index.js (compiled — tsx runtime dropped)
  • ✅ Dockerfile for apps/api (multi-stage) — 1.76GB → 380MB via Prisma 7 prisma-client generator + pnpm deploy --no-optional; boot + /api/health verified
  • ✅ Dockerfile for apps/web (multi-stage) — nitro v3-beta still leaves react externalized and untraced (Cannot find module 'react', nitrojs/nitro#4171: Base UI's use-sync-external-store shim). Unblocked by shipping a pruned prod node_modules (pnpm deploy --prod) next to .output, same technique as the API image (session 24, ADR-0027). Revisit for a bare self-contained .output when the upstream Vite/rolldown fix — or nitro's cjsRequireRewrite (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) → scp deploy/compose.yml + SSH docker 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.yml pulls the GHCR images (api + web + statics) on a single subdomain decksmith.<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) gets tsconfig.build.json + build script + exports pointing to dist/
  • ⬜ Turborepo watch pipeline: pnpm dev:api recompiles deps on source change (not needed as-is: pnpm dev:api runs tsx --conditions=source, reading deps' TS source directly — no dist watch)
  • ✅ apps/api migrated to node dist/index.js (remove tsx runtime 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-plugin uploads hidden maps 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.json bump, 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 (table CardFace + images {front,back} en JSON + champ layout), 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, table CardFace (clé (oracleId, faceIndex), cascade), Card.typeLine nullable, convention CardPrint.imageUris{front,back} — db-reviewer passé, db:push appliqué (PR #82)
  • ✅ DTOs API (packages/schema) : CardResponseSchema (colorIdentity/layout/faces[]), CardFaceSchema, CardImagesSchema {front, back?} (PR #82)
  • ✅ packages/scryfall scaffolded (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 in schema)
  • ✅ Card normalization (Scryfall → Card + CardPrint + CardFace) — normalizeCard returns a { card, print, faces } bundle of local Normalized* types (no Prisma coupling); per-face vs shared image detection; all image sizes kept (snake→camel)
  • ✅ isCollectibleCard filter (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/defense on Card + CardFace; keywords/producedMana on Card) + CardPrint.finishes replacing foil/nonfoil. Prisma migration + db-reviewer + db:push → schema DTOs → scryfall raw schemas + normalization + tests. Follow-up #85 (collection/deck foil boolean → finish enum). GIN index on Card.keywords deferred to 3.3
  • ✅ Bulk data download client (streaming) — getBulkDataInfo (metadata) + fetchBulkStream (dump bytes) + streamNormalizedCards (async generator via @streamparser/json-whatwg, backpressure, onInvalidRow skip+report). Network isolated from parsing; 3 bricks, no Prisma/batch/cron (that's 3.2). Also fixed the silently-broken scryfall build (tsconfig node preset + .js extensions) → 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/worker réel (bullmq + ioredis), factory de connexion Redis (maxRetriesPerRequest: null), docker-compose.yml dev (Redis seul), script dev:backend (Redis + api + worker en parallèle via turbo). Boot vérifié en local.
  • ✅ scryfall-card-sync job + daily cron — Queue + Worker BullMQ, upsertJobScheduler (cron 0 6 * * *), concurrency 1 (pas de chevauchement), retries idempotent-safe. Déclencheur one-shot sync:once (sans Redis) pour l'ingestion manuelle.
  • ✅ Incremental update handling — check getBulkDataInfo().updatedAt vs SyncState (comparaison par instant), skip si inchangé.
  • ✅ Sync-state persistence (SyncState — source/status/lastDumpUpdatedAt/lastSyncedAt/ lastCardCount/lastError ; ADR-0031, migration db:push).
  • ✅ Batch upsert (Card/CardPrint/CardFace) — groupChunk pur (dédup par oracleId, ordre FK) + upsertChunk (transaction par chunk, upsert sur clés naturelles = idempotent). chunkAsyncIterable dans packages/utils.
  • ✅ Fix Scryfall JSONL — l'API a migré default_cards vers du gzip JSONL (.jsonl.gz, jsonl_download_uri/compressed_size) ; client 3.1 adapté (DecompressionStream + parsing JSONL via readline). Chemin de lecture prouvé en réel.
  • ✅ Validation end-to-end du chemin d'écriture — OK (2026-08-10). sync:once a upserté 34 526 cartes dans Supabase (vérifié), 81 lignes ignorées (edge layouts type reversible_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 bulk ON CONFLICT → #96.
  • ✅ Déploiement du worker en prod (4ᵉ image Docker api/web/statics/worker + Redis conteneur interne, internal only, requirepass + AOF + volume, fail-closed REDIS_PASSWORD guard, depends_on: service_healthy) — PR #111, ADR-0031. Live + vérifié (worker Up, redis healthy, 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 (jointures EXISTS) pesés.
  • ✅ Migration Prisma : Card.rarities / Card.finishes / Card.firstReleasedAt + CardPrint.releasedAt / CardPrint.setName (nullable/array → backfill au prochain sync) — db:push appliqué ; DTOs (CardResponseSchema, CardPrintResponseSchema) mis à jour.
  • ✅ Raretés Scryfall complètes : special + bonus ajoutées partout (RaritySchema, domain Rarity, RarityBadge + tokens rarity-special/bonus, story) — l'agrégat rarities[] 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_at remontés du dump → NormalizedPrint.setName/releasedAt (parse ISO→Date déterministe) → écrits par le worker sans changement ; tests colocalisés (nominal + date absente).
  • ✅ Sync niveau 2 (agrégats) : passe SQL post-load — UPDATE cards … FROM deux 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-shot aggregate:once. Peuple rarities/finishes/firstReleasedAt. Mergé #114 ; backfill exécuté (rarities/finishes 100 %, first_released_at 100 % 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 CONFLICT par table (bulkUpsert{Cards,Faces,Prints} via Prisma.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'un running bloqué) — comble le trou « le cron tourne-t-il ? » vs « dernier succès ». db:push appliqué.

Endpoints:

  • ✅ DTOs (packages/schema) : CardSearchQuerySchema (filtres/tri/pagination, makeCsvSchema via z.preprocess) + CardSearchResponseSchema (via makePaginatedSchema générique), CardWithPrintsSchema (dans card-print.ts, évite le cycle card↔card-print), CardPrintsQuery, CardAutocompleteQuery/Response (#122)
  • ✅ Indexation Postgres (#122) : GIN sur colors/rarities/sets côté Prisma + index d'expression full-text pondéré (name A > type B > texte C) + trigram pg_trgm sur lower(name) en SQL brut (sql/card-search-indexes.sql), rejoué automatiquement par db:push (idempotent ; migrate diff confirme que db push ne drop pas les index d'expression/trigram)
  • ✅ Couche service (packages/services/card-service, retourne des valeurs métier — ADR-0024, le mapping DTO vit côté route) : searchCards (full-text @@ + ts_rank, filtres dynamiques en Prisma.sql, image du print le plus récent via LEFT JOIN LATERAL, { rows, total }), getCardWithPrints, getCardPrints, autocompleteCards (trigram) + util makeContainsPattern ; smoke-testé en réel (#122)
  • ✅ Routes apps/api : GET /cards/search · /cards/:id · /cards/:id/prints · /cards/autocomplete — publiques (données de référence MTG, non user-owned) + card-mapper.ts (valeur métier Prisma → DTO : narrow des types Json/array, dates ISO, drop de l'agrégat sets) + toPaginated (builder d'enveloppe colocalisé au schéma) + CARD_NOT_FOUND → 404 (était un 500 latent) (#124)
  • ✅ useCardSearch dans packages/query + module client cards (search/autocomplete/getCard/getCardPrints) + toQueryString (util générique isomorphe, arrays→CSV) ; keepPreviousData pour la pagination ; migration des tests api-client vers le serveur MSW partagé (#125). Phase 3.3 complète.

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, /search page, 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/tokens wired (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 (Phase 3.3, #125 — paginated, keepPreviousData)

4.4 Auth UI ​

  • ✅ Login + Register pages (session 15)
  • ✅ Forgot Password page (session 15)
  • ✅ useLogin, useRegister, useForgotPassword mutation hooks in packages/query (session 15)
  • ✅ ApiClientProvider wired in apps/web/__root.tsx (session 15)
  • ✅ @source for packages/web-ui in apps/web/globals.css (session 15)
  • ✅ Per-route document titles (makePageHead() helper in lib/head/) + adaptive SVG favicon (amber dark / violet light via prefers-color-scheme) (session 16)
  • ✅ Cookie-based language persistence — SSR-safe, no FOUT; createServerFn loader reads cookie server-side, parseLangFromCookieString shared across server and client (session 16)
  • ✅ ThemeControl component in apps/web — i18n label + ThemeToggle primitive (session 16)
  • ✅ LanguageControl — cookie write on switch, mounted pattern removed (session 16)
  • ✅ Auth page footer — Trans + Heart icon + GitHub/Docs/Storybook links, fully translated EN/FR (session 16)
  • ✅ ::selection accent coloring in globals.css — amber dark / violet light via CSS vars (session 16)
  • ✅ Storybook stories for apps/web components (ThemeControl, LanguageControl) in Components/App/ (session 16)
  • ⬜ Email confirmation + password reset flow (reset-password page — blocked on OAuth/deep-link)
  • ✅ Auth guard for protected routes — _authenticated pathless layout, beforeLoad $getMe SSR guard, dashboard moved under _authenticated/, redirectTo search param on /login (session 17)

4.5 packages/web-ui Foundation ​

  • ✅ apps/storybook scaffold: 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 (elaborate prop ─◈─)
  • ✅ 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/utils scaffolded: noop function with colocated tests
  • ✅ Surface — bare elevation primitive (surface / raised variants, 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/tokens worldclass audit: dead TS layer deleted, tokens.css is 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 + useArmedState hook (PR #36)
  • ✅ Icon sizing centralized: ICON_IN_CONTROL, ICON_INLINE tables + ADR-0021 (PR #36)
  • ✅ Toast, Drawer — Base UI components complete (PR #38)
  • ✅ Design tokens from packages/tokens applied (semantic token classes across all components)
  • ✅ packages/domain scaffolded: MtgColor, ColorIdentity, SnowMana, VariableMana types + 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 under Components/MTG/… (PR #32)
  • ✅ Skeleton — 4 shapes (text / control / block / circle), motion-safe:animate-pulse, aria-hidden, dimension from caller className (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), initialTheme prop, anti-FOUC inline script eliminated; ThemeToggle (Switch with Sun/Moon thumb icon, --accent-icon token) (session 16)
  • ✅ theme-cookie.ts pure function + 6 colocated unit tests (session 17)
  • ✅ useMediaQuery(query) — useSyncExternalStore, reactive, SSR-safe; useBreakpoint() semantic shortcut (isMobile / isTablet / isDesktop); BREAKPOINTS const (session 16)
  • ✅ useKeyboardShortcut(shortcuts, handler) — wraps tinykeys, ref-stabilized callback, SSR-safe (session 16)
  • ✅ TextLink — styled <a>, default / subtle variants, exports textLinkVariants for AppLink reuse; AppLink in apps/web wraps TanStack Router Link (session 16)
  • ✅ Text size="xs" added to typography scale (session 16)
  • ✅ --accent-icon token (#5b4fcf both 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/schema Zod validators with error codes (PASSWORD_TOO_SHORT, USERNAME_INVALID_FORMAT, HEX_COLOR_INVALID, SLUG_INVALID, etc.) (session 19)
  • ✅ packages/i18n scaffolded: shared translation package (auth/common/errors namespaces, EN + FR); apps/web/src/locales/ deleted — all strings now in packages/i18n; multi-namespace init in apps/web/src/i18n.ts; get-field-error extended with optional t param 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 + Trans component added session 16
  • ✅ Accept-Language NOT needed in apps/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) ​

  • ⬜ CraftGuideArticle seed 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>/docs behind 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/tokens pour 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.md per session (/session.end)
  • ⬜ Check dependency updates at session start (/session.start)
  • ⬜ Create ADRs for new architectural decisions

Built with VitePress