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

104 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```bash
# 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.
```bash
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:
```bash
docker stop hokm-web && docker rm hokm-web && docker compose up -d --no-deps web
```
### Build / verify (run before committing)
```bash
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).