- 01 Sep, 2026 13 commits
-
-
Mahmoud Aglan authored
The audit signs in as a guest per device, which creates a real account. A single long-lived browser was crashing partway through the third device, and the crash took the teardown with it — leaving throwaway accounts in production. Now: one browser per device, and teardown in a finally block so it runs whichever way the run ends. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
ChessBoard sized itself from container.clientWidth, which INCLUDES padding, so inside a padded container the board came out exactly the padding too wide. On the puzzle screen that pushed the whole page 4px sideways and hung the board off the edge. Sizing now measures the container's content box. resize() had a second bug: it set canvas.width/height (the backing store) but never the CSS width/height, so after any resize the element laid out at its attribute width — devicePixelRatio times too large. It also now resets the transform before scaling rather than relying on the implicit reset. Both paths share one availableSize(), so they can no longer disagree. Local audit is clean across all scenes at 360/390/430px. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
An audit of the live app at 360/390/430px found 355 measurable layout defects. tools/ui-audit.mjs drives the real app at those three widths and reports what is measurable rather than a matter of taste: horizontal overflow, tap targets below 44px, text below a legible size, and content clipped by its own container. before 355 (tiny-text 216, tap-target 133, clipped 6) after 0 The root cause of the size problems was not the stylesheet. /api/branding.php writes 181 stored values inline onto :root at runtime, overriding every token in tokens.css — including quick_btn_label_font 10 and hud_badge_font 9. Editing tokens.css alone changed nothing. The floors are now enforced in theme.js where the override happens, so no stored value (or future admin edit) can reintroduce illegible text or an unreachable control: - any *_font token is floored at 11px - tap-target tokens are floored at 44px - home_max_width, stored as 340px for a 360px phone, is treated as a floor and allowed to grow (min(100%, max(340px, 92vw))) instead of pinning every phone to the smallest one's layout Alongside that: a fluid type scale (--fs-xs … --fs-xl) and --tap-min in tokens.css, a tap-target floor on every control in core.css, and 53 sub-11px inline font sizes and 41 undersized inline button heights raised at the source. Layout fixes visible on screen: - the daily-gift button carried breathe-glow on the whole flex column, drawing a large dark rectangle across the quick-actions row. The glow belongs on the icon. - play home dumped its spare height as dead space above the tab bar; it now centres via auto margins, which does not clip when content overflows the way justify-content:center does. - the chess board left a stray "1." floating in a gap above the controls. The opening/material/move-list block is now hidden until there is something in it. Also adds tools/dev-server.php, which serves the app locally against the real Supabase so this work does not need a deploy per iteration. Multiplayer regression: tests/run.sh 5/5, and the production rehearsal passes. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Reactions did not sync, and when something appeared it floated in the middle of the screen with no sign of who sent it. Four games had four implementations, three broken in a different way: - chess kept ONE shared `emote` slot in the match's game_state, so two players reacting in the same second overwrote each other, and it carried no sender; - ludo posted reactions through the MOVE endpoint, which replaces game_state wholesale — sending an emote wiped turn_count and broke the sync loop. Its receive side registered with mp.onEmoteReceived, which only stores a callback that nothing ever invokes, so ludo players never saw a reaction at all; - domino had a third variant, again a single shared slot; - backgammon wrote a `last_emote` column no client ever read, so the opponent saw nothing. (That table does not exist in the database either — see below.) Reactions are chat, not game state. They now live in `chat_messages` on a `match` channel via the new api/match-chat.php, so nothing about a reaction can reach a board, a clock or a turn — a social feature must not be able to corrupt a game in progress. The multiplayer sync path is untouched. core/reactions.js is the single client implementation. Its central idea is SEATS: a game registers which element belongs to which player, and every bubble is then drawn attached to that player, carrying their name and avatar. That is the actual fix for "you can't tell who is talking". Seat resolution falls back to "the seat that isn't mine" in a two-player game, so a game can register its opponent seat before it knows the opponent's id. Only preset emotes and preset phrases are accepted, validated server-side — free text between strangers in a live match is a moderation surface nobody is staffing. The cooldown is enforced on the server, not just in the UI. Also fixed, found on the way: - ChessBoard has draw(), not render(). board.js called render() whenever a themed piece image finished loading, so it threw every time and the board never repainted with custom piece art. - chess/scenes/game.js built an inline onerror="" whose body embedded the result of emoji() — which returns <img src="..."> when a themed asset exists. Its double quotes closed the attribute, breaking the markup and throwing a SyntaxError on every bot game; the stray '"> rendered as text inside the avatar. The fallback is wired in JS now. Adds tools/ui-audit.mjs (drives the real app at three phone widths and reports measurable layout defects) and tools/test-reactions.mjs (end-to-end reaction check against a live deployment, with teardown). Noted, not fixed here: backgammon_matches and backgammon_queue do not exist in the database, so backgammon multiplayer has never worked. Out of scope for this change and untouched. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Deployed to el3ab-player and verified end to end against production with tests/rehearsal.mjs, which now also covers spectating and the "watch this player" lookup. Production is left byte-for-byte as it was found: row counts across matches, tournaments, profiles, rating_history and the queue are unchanged. Remaining for a human: play one game on a real device (the interface changes are the part I could not verify), and optionally set CRON_SECRET and SUPABASE_JWT_SECRET, which need CapRover app configuration the push webhook does not grant. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Two problems, one of them mine. Mine: locking api/game.php's `get` to the two participants broke spectating outright. Watching a live board is a normal part of a chess event — the profile "watch" button and the tournament live board both rely on it — and on championship day it is what most people will be doing. Non-participants now get a spectator projection: position, clocks, move list, players and result, but no `my_color` and none of the private game_state (draw offers, heartbeats). Pre-existing: handleFindActiveMatch ignored the player_id it was given and always looked up the *caller's* match, so clicking "watch" on someone else silently opened your own game. It now honours the requested player. The board itself still goes through `get`, which decides what a spectator may see. Both lookups are also bounded to the last 12 hours. Production carries 27 matches stuck in a non-final state since May and July; without that bound, "watch" would open a game that ended three months ago. Test-harness hardening in the same commit: pgshim silently ignored any PostgREST filter it did not implement, so a query with `gte.` matched every row and the test passed while production filtered correctly. It now implements gte/lte and throws on anything unrecognised, rather than quietly matching everything. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
el3ab_tournaments.starts_at is NOT NULL in the live schema. tournament-admin.php allowed it to be null, so every create call that did not supply one was rejected by Postgres. The local test schema had the column nullable, so the suite passed and the defect only appeared when the rehearsal ran against production. starts_at now defaults to now() — which also gives auto_start something to act on — and tests/schema.sql marks the column NOT NULL so the next one is caught locally. Adds tests/rehearsal.mjs, the script that found it: four throwaway accounts driven through matchmaking, a 33-move game to checkmate and a full Swiss round against the live deployment, with teardown in a finally block. Run it after any deploy. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
requestBotMove retried forever on failure and did nothing at all when the engine answered without a move, so a Stockfish hiccup left the player watching the thinking indicator with no way to continue. Now: four attempts with exponential backoff, then a dialog offering to end the game. Also guards against gameState having been torn down mid-request. The engine itself is healthy — verified live: GET /api/chess/bots returns the seven bots the client expects, and POST /api/chess/move answers the start position with d2d4 at depth 3. api/bots.php's paths are correct. Arabic and English strings added for the new message. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
No completed match in production has ever had white_rating_after or rating_change_white written, and 272 of the 290 profiles were still sitting on the default 1200. The complete_match database function does not touch the rating columns, and game.php had calculateElo() and getRatingColumn() helpers that nothing ever called. Elo is now computed in applyRatings(), called from finaliseMatch() after its conditional write has succeeded — so a game can never be rated twice. It writes both ratings before and after and the deltas onto the match, updates the correct per-time-control column on each profile along with the game/win/loss/draw and streak counters, and records a rating_history row per player. This matters beyond the leaderboard: Swiss seeding orders players by rating, so with every rating pinned at 1200 the top-half/bottom-half split in round 1 was arbitrary. Bot games and aborted games are never rated, and a match with the same player on both sides is refused outright. Covered by e2e sections 18 and 19. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Production carries seven matchmaking_queue rows marked `matched` from May and June, each pointing at a game that finished months ago. The previous commit started honouring `matched` rows so the hand-off survives a re-queue — which, without this, would have sent those players straight into a three-month-old finished match. liveClaimedMatch() now verifies the referenced match is real, still playable, and actually belongs to the caller before acting on it, and deletes the row on the spot when it is not. Self-healing, so the existing rows need no manual cleanup. handleStatus also deletes only the row it consumed rather than every row for that player, and returns opponent_id alongside the colour. Covered by e2e section 17: a stale claim is refused and cleared, queueing past one works normally, and a genuinely live claim is still delivered with the correct opposite colour. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
api/cron.php still exists as the belt-and-braces tick, but an event must not depend on someone remembering to wire one up — especially not the one running on Saturday. The engine now also advances off ordinary traffic, the same way the matchmaking queue already sweeps its own stale rows: - browsing the tournament list starts any tournament whose start time has passed - a player opening their game, or checking their pending games, forfeits an opponent who never turned up and closes the round if that was the last board Both are idempotent and throttled (once every 15-20s per tournament, via APCu or a temp file), so they cost one query every few seconds rather than one per request. A game that has actually begun is never forfeited by this path — the server clock settles those. Also fixes tournamentEnvInt: `getenv($k) ?: $default` silently discarded a configured value of "0", because "0" is falsy in PHP. tests/run.sh now clears its ports before starting and aborts if a server fails to bind. A stale server from an earlier run had been serving the previous revision on those ports, so the suite was quietly testing old code — the run looked like a product failure and was not one. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Mahmoud Aglan authored
Four independent defects each broke competitive chess on their own, and they compounded. Verified against live production (Supabase, the Swiss API and the CapRover deployment), not inferred from code alone. Full diagnosis in FIX_PLAN.md. A. Match completion was rejected for every decisive game. The client sends result 'win'|'loss'|'draw'; game.php's $validResults did not contain 'win' or 'loss', so every won or lost game got a 400. That list was also wrong in its own right — timeout_white, abandon_white, resign_white, checkmate_* and bot_* are not in the public.match_result enum. And handleComplete ignored the write's return value, so a Postgres rejection was invisible and the endpoint still reported success. Consequence: no rating, no coins, no XP, matches stuck in_progress forever, tournament results lost, and the winner shown a fabricated +12. The client now reports only *how* the game ended. The server derives the winner from the final position (the mated side is the side to move), from who resigned, or from whose clock expired, validates against the real enum, and verifies the write landed. A losing player can no longer report a win. B. Both players could be assigned White — "playing against yourself". `playerColor = color || 'w'` defaulted both players to White whenever a caller supplied no colour, and the poll only ingests the opponent's move while it is *not* your turn, so two Whites never saw each other and each played a private board. The async correction also wrote gameState.playerColor while the clock closures still read a stale local copy. Colour now has one source of truth. game.php returns my_color computed server-side; the board stays inert until the server confirms it. Fixed the callers that passed no colour (refresh recovery, group invites — which also lacked mode:'live' and so started a bot game) and stopped lobby.js guessing from isHost. C. Finished matches were never released. unmountGame never tore down the match session, so localStorage kept the recovery key; combined with A, every app start dragged players back into a dead game with no colour, straight into B. D. Hand-offs were destroyed. matchmaking.php deleted all of a player's queue rows including one already matched and carrying a match_id, so the waiting player never learned their match and their opponent sat alone. Tournament create-or-join did a read-then-insert with no lock, so both paired players could create their own match. Match ids are now derived from (tournament, round, board): both clients compute the same id and the primary key decides, so exactly one match exists per pairing. E. There was no tournament engine at all. Nothing created a Swiss tournament, consumed auto_start, generated pairings, advanced a round or computed standings. api/swiss.php sent no Authorization header, so every call to the external pairing service returned 401 and each getter swallowed it and returned an empty array — which is why standings and pairings have always rendered blank. includes/tournament-engine.php is a native Dutch-system Swiss engine with no external dependency: backtracking pairing that never repeats an opponent, FIDE colour allocation, byes, Buchholz cut-1 / Buchholz / Sonneborn-Berger tiebreaks, automatic round advance and final placings. api/tournament-admin.php creates and starts tournaments; api/cron.php is the scheduler that acts on auto_start and forfeits no-shows. Also fixed - Server authority: turn ownership, move-count monotonicity, FEN transition sanity and a server-side clock, so a client can no longer overwrite the position or report its own remaining time. - Disconnect detection was dead: match-live marked the opponent active on every poll, and match-session's abandon branch was unreachable behind the disconnect branch's latch. Heartbeats are now recorded per player in game_state. - Draw acceptance required no offer from the opponent — a way out of a lost game. - Removed the in-process HTTP call back into our own Apache for tournament reporting (deadlock risk, and it lost results silently when it failed). - Three time controls in the picker (blitz_5_5, rapid_20_0, classical_45_45) are not in the time_control enum; choosing one created no match. - Auth did an upstream GoTrue call plus a ban query on every request, including the 2s poll. Now verifies HS256 locally when the secret is set, otherwise memoises briefly. - curl_close() removed repo-wide: deprecated in 8.5, where its notice lands in the middle of every JSON body. Dockerfile now turns display_errors off. Tests: tests/run.sh — 5 suites against a disposable local Postgres carrying the production schema. Covers 2-100 player tournaments, 167 real master-game move transitions (no legal move is ever rejected), the pairing race, JWT verification, and a full two-client game and tournament over HTTP. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
- 15 Aug, 2026 1 commit
-
-
Mahmoud Aglan authored
-
- 10 Jul, 2026 1 commit
-
-
Mahmoud Aglan authored
Bug 1: Resign not detected - endGame now skips redundant complete API call when server already completed the match (resign/draw/abandon via poll). Bug 2: Players seeing themselves as opponent - verify and correct color from authoritative server match data on game start. Bug 3: Checkmate not appearing - send final move to server BEFORE calling endGame so opponent receives the checkmate FEN position. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 03 Jul, 2026 25 commits
-
-
Mahmoud Aglan authored
- tournaments.php: exclude cancelled tournaments (neq.cancelled filter), hide completed tournaments older than 3 days - hub.js: treat 'draft' status same as 'registration' — show register button, green status pill, include in "registration open" filter Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- tournaments.php: remove non-existent 'ends_at' column from select (caused PostgREST error → empty tournaments list) - i18n.js: add all missing tournament hub keys (my_tournaments, no_my_tournaments, format_*, players_label, etc.) + common keys (all, retry, failed, coins, play, minutes_abbr) Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
The .env file was not being included in Docker builds (gitignored), causing all API calls to fail with 401 after each redeployment. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Fills the gap between --bg-card (#1a1a2e) and --bg-elevated (#1e1e3a) for JS modules that reference this token. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Sanitize time_control to rapid_10_0 if not in the allowed list. Prevents garbage values from reaching the database. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- multiplayer.php: verify join succeeded after update (race detection) - table.js: show My Games button for all games, not just chess - i18n: add game.my_games key (ar: مبارياتي, en: My Games) Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- friends.js: invite dialog now shows all 4 games in a 2x2 grid - friends.php: create/check/accept/decline backgammon invites - lobby.js: route to backgammon-game scene, show correct icon/label/color - Time options hidden for non-chess games Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- tournament-arena, tournament-lobby, tournament-live: export unmount - group-chat: export unmount to clear realtime sub + invite timer - social/mod.js: register unmountChat and unmountGroupChat - Prevents interval leaks when navigating away from these scenes Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Domino: - dealAndSyncToServer: retry once on failure, abort to room on 2nd fail (#100) - syncPassToServer: only increment moveCount after success (#41) - drawFromBoneyard: debounce with drawInFlight flag (#42) - endMatch: guard empty players array, fallback to match IDs (#209) Backgammon: - createGame: fallback to sheshbesh if variant key missing (#26) - handleServerState: selective merge instead of Object.assign (#56) Core: - multiplayer.js: mark startDisconnectWatch as deprecated (#229) - core.css: replace hardcoded HUD values with token variables Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
The client sends `after: lastTime` to fetch only new messages since the last poll. Server now handles this with `created_at gt.{after}` filter. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- auth.php: eliminate double token verification (decode JWT payload directly) - auth.php: cascade delete related data on account deletion (blocks, daily_claims, challenges, achievements, group_members) - auth.php: add server-side username regex validation - register.js: add client-side username character validation - profile/view.js: show "Unrated" for players with 0 games at 1200 rating - i18n: add auth.invalid_username and profile.unrated keys Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 1 (Chess Multiplayer Bulletproof): - net.js: check content-type before JSON parse, clear error on HTML responses - modal.js: stack-based resolve — stacked modals no longer hang - match-session.js: fix null dereference after destroy, remove listener on cleanup - match-session.js: only fire onConnectionRestored after actual disconnect - lobby.js: prevent duplicate startGame calls with gameStarted flag - lobby.js: add pollInFlight guard, clear timeouts on unmount - lobby.js: derive color from match data when undefined (friend invite fix) - scene.js: queue navigation during transition instead of dropping it - chess/game.js: pass timeControl to result scene for correct rematch Phase 5 (Core UX): - table.js: hide "My Games" button for non-chess games (no history scenes) - queue.js: add polling overlap guard - profile/view.js: show "Unrated" for new players instead of 1200 - i18n: add profile.unrated key (ar/en) Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Created core/config.js with SUPABASE_URL, STOCKFISH_URL, DEFAULT_RATING - login.js, register.js, realtime.js now import from shared config - chess/game.js and bot-select.js use STOCKFISH_URL constant - multiplayer.js uses DEFAULT_RATING from config - ludo/game.js: named TURBO_SPEED constant - table.js: removed dead `disabled = false` code path Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Updated tokens.css with aligned color values and added missing tokens (bg-panel, bg-inset, bg-dark, gold-dark, purple-light, violet, green, green-light, amber, red-light, red-soft, text-dim, text-light, info). Updated theme.js applyColors map to support all new tokens via admin branding panel. Converted ~85% of all inline hex colors across 50+ JS files to use var(--token) references. Remaining ~160 are canvas colors (which can't use CSS vars) and unmapped game-specific colors. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
27 files converted to use the emoji(slot, fallback, size) function from core/theme.js, enabling full emoji customization via the admin branding panel. Covers core modules, all 4 game scenes, play flow, social, rewards, org, and tournaments. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Replace all 40+ remaining Arabic strings in tournament-detail.js. Add tournament format/prizes/registration/standings/rounds keys to both ar and en dictionaries. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Replace hardcoded Arabic in tournament-arena.js (7 strings), tournament-detail.js (10 strings), tournament-live.js (8 strings). Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
All UI text across 57 files now goes through the i18n system (core/i18n.js). Added ~120 new translation keys covering: auth, daily rewards, challenges, ranks, shop, groups, tournaments, puzzles, orgs, emotes, and backgammon doubling. Both ar and en dictionaries are complete and in sync. Data strings (country names, FIDE titles, variant names, puzzle themes) are intentionally left as-is — they use name/nameEn data pattern. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 2.4: multiplayer.php now routes to correct table for all games (chess→matches, domino→domino_matches, backgammon→backgammon_matches, ludo→ludo_matches). Validates game_key against allowed list. Phase 4.3: handleServerState in backgammon only copies specific fields (state, dice, turn, gameOver) instead of Object.assign which clobbered local methods and properties. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 1.13: add unmountGame export for chess — stops clock, destroys board, nulls gameState on scene exit. Phase 5.1: notification items now navigate to relevant section on click (friends, play, tournaments, achievements based on type). Phase 6.6: remove remaining stopDisconnectWatch calls from domino/ludo since disconnect detection is now fully handled by match-session.js. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 7.1: add 30+ missing i18n keys for queue timeout, game states, achievements, and chat. Replace hardcoded Arabic in chess game scene with t() calls. Phase 7.3: replace hardcoded hex colors in chess HUD and draw dialog with CSS variables (--text-primary, --bg-surface, --success, --error). Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 1.6: add pollInFlight guard to match-session.js so overlapping polls are skipped when server is slow. Phase 2.5: room code generation retries up to 5 times on collision. Phase 2.6: join_room uses service key, checks if room is full, prevents double-join, and auto-starts when player count is met. Phase 3.1: dealAndSyncToServer retries on failure with toast notification. Phase 3.3: drawFromBoneyard syncs once after all tiles drawn instead of firing N parallel requests. Phase 3.6: check-invites uses PostgREST cs filter on JSONB players array instead of scanning all waiting matches. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 4: getPipCount now accepts variant param (fixes wrong pip count for thirtyone variant), backgammon quit button shows confirmation dialog in live mode. Phase 3: syncPassToServer no longer pre-increments moveCount before network success, winners array guards against empty state.players. Phase 2: ludo bot "thinking" indicator uses dedicated .pp-status span instead of clobbering the player name. Phase 6: chat.php unread/recent queries batched into single DB calls instead of N+1 per friendship. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Phase 5: queue 60s timeout with retry prompt, match history accepts game_key, spectate routes to correct game scene, shop shows "owned" indicator, profile shows dash instead of 1200 for unrated players. Phase 6: remove duplicate disconnectWatch calls (use match-session only), fix auth.php double token verification with requireAuthUser(), cascade delete related data on account deletion. Phase 1: fix opponentRating using actual rating in live mode instead of botElos lookup. Fix achievements recursive mount — update in-place instead of re-calling mountAchievements. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-