Functional: the in-game add-friend handshake aimed at an auto-match opponent who is
secretly a pooled robot now records the request per (game, seat) in a new
robot_friend_requests table -- never against the shared robot account -- mirroring the
robot_blocks pattern. The shared account stays out of friendships, the "requested" state
is pinned to the seat (not leaked across the player's other games), the robot ignores it,
and a background reaper drops the row 7 days after its game finishes. The outgoing-requests
list carries these per-game rows so the seat control stays disabled across reloads. No
withdraw UI, per owner decision.
Cosmetics:
- Lobby: an in-progress game tints the viewer's own score number green when leading or
tied, red when trailing (reusing --ok/--danger); scores now render in seat-number order,
matching the over-the-board scoreboard.
- Stats: the per-variant best move is laid out on two lines -- the variant label, then the
score (right-aligned in its column) and the word tiles (left-aligned) below it.
- Dark theme: the played-tile background is a touch darker so a player-placed tile reads
with more contrast (light theme unchanged).
Docs (ARCHITECTURE, FUNCTIONAL + _ru mirror, backend README, UI_DESIGN) updated in the
same change.
Lobby: group the my-games list into your-turn / opponent-turn / finished
(empty sections hidden), ordered by last activity (your-turn oldest-first,
the other two newest-first), as a compact line-separated list. gameDTO and
FB GameView gain last_activity_unix (turn start while active, finish time
once finished); a pure lib/lobbysort.ts holds the grouping/ordering.
Friends: the in-game 'add to friends' item is now server-derived via a new
GET /user/friends/outgoing (+ friends.outgoing op), returning addressees with
a pending OR declined request (both read as 'request sent'), so it is correct
across reloads; it shows a disabled '✓ in friends' once accepted. It
live-updates when the opponent answers: RespondFriendRequest now publishes
friend_added (accept) / friend_declined (new notify sub-kind, decline) to the
original requester, whose open game re-derives its friend state.
Tests: lobbysort unit test; gateway outgoing + last_activity transcode tests;
backend integration ListOutgoingRequests + respond-publishes-to-requester;
e2e updated for the new lobby section labels + a non-friend active opponent.
Docs: ARCHITECTURE notify catalog, FUNCTIONAL(+ru) lobby/friends, PLAN.