diff --git a/CLAUDE.md b/CLAUDE.md index 43c994c..b868968 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,18 @@ @AGENTS.md + +# Barg-e Vasat (Hokm) — start here + +**Read `HANDOFF.md` first** — it is the single source of truth for this project: +what it is, how to run (dev + Docker stack), the architecture, the +client↔server gamification sync rule, feature status, CI/CD + mirror cert +workaround, and the TODO list. + +Quick facts: +- Next.js 16 static-export app at root; .NET 10 SignalR backend in `server/`. +- Git remote `origin` = Gitea (`soroushdes/HokmPlay`, branch `main`); push triggers CI/CD. +- i18n: add every string to BOTH `fa` and `en` in `src/lib/i18n.tsx`. +- Gamification lives in `src/lib/online/gamification.ts` (client) AND + `server/src/Hokm.Server/Profiles/Gamification.cs` (server) — keep them in sync; + the server is authoritative in live mode. +- Verify before committing: `npx tsc --noEmit` · `npx tsx scripts/sim.ts` · + `dotnet build server/Hokm.slnx -c Release` · `npm run build`. diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..f9614d6 --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,103 @@ +# 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 **1500–1600** 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", "7–0 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).