• Mahmoud Aglan's avatar
    feat: one reactions system for every game, on its own channel · 09afc0f8
    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: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    09afc0f8
package-lock.json 42.1 KB