Files
HokmPlay/HANDOFF.md
T
soroush.asadi e450a6a2ed
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 21s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m1s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 1s
docs: add HANDOFF.md (full project state) + point CLAUDE.md at it
Single source of truth for any agent/session continuing the project: run
instructions (dev + Docker stack), architecture, the client<->server
gamification sync rule, full feature status, CI/CD + Nexus HTTP-mirror cert
workaround, gotchas, and the TODO list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:47:51 +03:30

9.4 KiB
Raw Blame History

Barg-e Vasat (برگ وسط) — Project Handoff

Persian Hokm card game for the Iran market. Web/PWA + Android (Capacitor), vs-AI and online multiplayer, with a full gamification economy. This doc is the single source of truth for any agent/session continuing the project.


1. Repo, remotes, run

  • Code root: D:\Projects\hokm (Next.js app at root; .NET backend under server/).
  • Git remote origin = Gitea: https://git.soroushasadi.com/soroushdes/HokmPlay.git, branch main. (No GitHub remote.) Pushing origin main triggers CI/CD (Gitea Actions).
  • Brand: name «برگ وسط» / "Barg-e Vasat" (insider Hokm slang — middle card when weak). Don't rename to "Hokm"; folder/repo stay hokm/HokmPlay.

Run locally (dev)

# frontend (root) — http://localhost:3000
npm run dev
# backend (.NET 10 + SignalR) — http://localhost:5005
cd server && HOKM_USE_SQLITE=1 dotnet run --project src/Hokm.Server/Hokm.Server.csproj

.env.local for live mode: NEXT_PUBLIC_USE_SERVER=1, NEXT_PUBLIC_SERVER_URL=http://localhost:5005. Without NEXT_PUBLIC_USE_SERVER=1 the app runs fully offline against the in-memory mock service.

Run the Docker stack (local, prod-like) — what the user actually tests

Ports are in 15001600 so they coexist with npm run dev/dotnet run:

  • web http://localhost:1500 (nginx serving the static export) → api :1505 (.NET) → :1510 postgres.
cd D:\Projects\hokm
copy deploy\ENV_FILE.example .env   # set JWT_KEY, POSTGRES_PASSWORD; registries default to HTTP Nexus
docker compose build server web
docker compose up -d

Gotcha: after rebuilding the web image, docker compose up -d sometimes says "Running" and keeps the old image. Force it:

docker stop hokm-web && docker rm hokm-web && docker compose up -d --no-deps web

Build / verify (run before committing)

rm -rf .next && npx tsc --noEmit          # types (rm .next first — stale .next/dev breaks typedRoutes)
npx tsx scripts/sim.ts                      # engine + gamification self-test (200 matches + 500 rewards)
dotnet build server/Hokm.slnx -c Release    # server
npm run build                               # next static export

2. Architecture (the important bits)

  • Frontend: Next 16 (App Router, output:"export" → static), React 19, Tailwind v4, Framer Motion, Zustand. RTL Persian default + English; custom i18n in src/lib/i18n.tsx (NOT next-intl) — every string must be added to BOTH the fa and en dicts.
  • Engine: pure-TS Hokm engine/AI in src/lib/hokm/; mirrored as C# (server/src/Hokm.Engine, static class Rules — not Engine, to avoid namespace clash). Validated by scripts/sim.ts (TS) and server/tools/Hokm.Sim (C#).
  • Service seam: all networking goes through OnlineService (src/lib/online/service.ts). Two impls: MockOnlineService (offline) and SignalrService (live). getService() picks via NEXT_PUBLIC_USE_SERVER==="1".
  • Backend: .NET 10 ASP.NET Core + SignalR (server/src/Hokm.Server), EF Core (SQLite dev / Npgsql Postgres prod), JWT. Hub /hub/game; REST under /api/*. Profile stored as a JSON blob (ProfileRow) + coin Ledger. In-memory matchmaking/rooms in GameManager/GameRoom.
  • ⚠️ CRITICAL — keep gamification in sync: src/lib/online/gamification.ts (client) and server/src/Hokm.Server/Profiles/Gamification.cs (server) implement the SAME rules (rating/Elo, coins, XP, achievements, titles). In live mode the server is authoritative — ranked games run server-side and push reward/profile over the hub; client-run (vs-computer/private) games submit a MatchSummary to POST /api/match/result. If you change a rule, change BOTH files identically (ids/goals/coins/metrics/formulas).
  • Zustand stores: ui-store (History-API screen routing via hash), session-store (auth + profile, subscribes hub onProfile), online-store (friends/chat/leaderboard/matchmaking), game-store (the table driver — mode: ai|online, live, turn timer, minimize/resume, forfeit), sound-store, notification-store, celebration-store.

3. Feature status (DONE)

  • Full offline vs-AI game (engine, AI, turn timer + auto-play, disconnect/reconnect sim).
  • Online multiplayer over SignalR: matchmaking (pro skips queue, bots fill after wait), live server-run ranked games, server-authoritative entry/rewards.
  • Economy: coins; ranked entry = stake (win +stake [+kot 40], lose stake); free vs-computer/private rooms. Buy-coins via ZarinPal sandbox (merchant 299685fb-cadf-4dfc-98e2-d4af5d81528d, config-driven). Coin packs: starter 50k/95,000﷼, … Stores (Bazaar/Myket) must use their IAB (/api/coins/iab/verify scaffolded; token verification TODO).
  • XP/levels: every game grants XP, winner ×2; premium (pro) ×1.5; max level 100; curve 100*lvl + 15*lvl². Store sells XP packs (xp1 +200/5k, xp2 +600/12k, xp3 +1500/25k coins; consumable; unlocks level achievements).
  • Achievements: ~100, metric-driven generator (categories: victory/kot/streak/hakem/level/rank/veteran), incl. "7× hakem", "70 sweep". Dedicated AchievementsScreen (tabbed) + Profile summary. Some unlock sticker packs.
  • Cosmetics: avatars, titles (incl. expert/professional/captain/leader ladder), card front+back, reaction packs, sticker packs (custom SVG art incl. crown/seven-zip/streak-fire). Profile photo upload gated at level ≥ 25.
  • Social: friends + chat server-persisted (Social/SocialService, REST + hub friendRequest/social/chat); friend remove needs confirm. Premium chat = animated gold bubbles.
  • Forfeit: request + teammate-confirm (server GameRoom forfeit flow); penalty = lose 2× coins + 0 XP (NO kot, and never mention kot); confirm dialog alerts the penalty.
  • End-of-game roster: MatchPlayersList on the final screen (reward modal + AI match-over) lists everyone; Add-friend button for real non-bot players (seat userId threaded from server).
  • Celebrations: celebration-store + CelebrationOverlay — animated XP count-up, level-up pop, achievement unlock; fires from shop purchases (XP/cosmetic) and unlocks. Reusable via celebrate({...}).
  • UX/UI: "Persian luxury" palette (navy/teal/gold, glass) + UNO-style tactile UX rolled out to Home (hero Play), Shop (detail sheets), Lobby, Matchmaking, Profile, Leaderboard. Primitives in globals.css: .press-3d (tactile press), .safe-top/.safe-bottom/.safe-x (notch), .hud-shadow, .premium-chat. Online count floored at ≥50. Match stays alive on exit (minimize/resume + ResumeGameBar). No fake/periodic notifications (removed as spam).
  • Capacitor Android APK builds (Myket maven mirror at root https://maven.myket.ir; init script pins buildTools 36 + JDK17). See ANDROID.md.
  • CI/CD (Gitea Actions + Nexus mirror) + Docker stack. See DEPLOY.md.

4. CI/CD + mirrors (Iran constraints)

  • .gitea/workflows/ci-cd.yml: api-build (dotnet build slnx + Hokm.Sim), web-check (tsc + next build), deploy (self-hosted; pg_dump backup → rollback tag → build → stop+rm+up --no-deps → health-wait → prune). Set the ENV_FILE repo secret (see deploy/ENV_FILE.example).
  • NuGet/npm go through the Nexus mirror over PLAIN HTTP http://171.22.25.73:8081/repository/{nuget,npm}-group/ — the HTTPS mirror serves a partial cert chain that container trust stores reject (NU1301 PartialChain / npm UNABLE_TO_GET_ISSUER). npm also uses --strict-ssl=false; NuGet HTTP source needs allowInsecureConnections="true". Local Windows dev + Docker base-image pulls work over HTTPS (Windows trust store has the intermediate) — only in-container package feeds use HTTP.
  • Fonts are self-hosted (@fontsource-variable/vazirmatn + plus-jakarta-sans) — next/font/google fetches Google at build time and FAILS on the Iran runner. Do not reintroduce next/font/google.
  • Memory: localhost can be VPN-hijacked (EonVPN) → reach local services via LAN IP if needed.

5. TODO / next

  1. Generate real EF migrations (dotnet ef migrations add Init, DesignTimeDbContextFactory targets Postgres) + point at live Supabase; today the server uses EnsureCreated() (auto-switches to Migrate() once migrations exist).
  2. Deeper game-table UNO restyle (bigger tactile cards, clearer turn/HUD, punchier win/trick feedback) — the last UI surface not yet refreshed.
  3. Store IAB token verification (Cafe Bazaar Poolakey / Myket) — /api/coins/iab/verify is a stub.
  4. Iranian push provider for closed-app notifications (FCM/APNs blocked); in-app + real-time notifications already work.
  5. Optional: colored-chat visibility to OTHER players (needs sender-plan on chat messages); route daily-reward through the celebration overlay.

6. Working notes / gotchas

  • Can't use the headless preview to verify visuals (it pauses animations/screenshots) — verify via builds + ask the user for screenshots. UI changes have been shipped "by the numbers".
  • Server binds 0.0.0.0 in Docker via ENTRYPOINT … --urls http://0.0.0.0:5005 (appsettings Urls=localhost wins over env, so command-line args are used).
  • After schema changes in SQLite dev with EnsureCreated(), delete server/src/Hokm.Server/hokm.db* to recreate.
  • Background dotnet run & from a Git-Bash shell dies when the shell exits; use a tracked background runner.
  • Commit messages end with Co-Authored-By: Claude …. Both messages/-style i18n strings live in src/lib/i18n.tsx (fa+en).