2 Commits
Author SHA1 Message Date
tiennm99 e19b083dc0 feat(online): seat the chat beside the game and score the series
A room screen wide enough for it is two columns now: the game or the
lobby on one side and the room's conversation on the other, so neither
has to be scrolled past to reach the other. A phone keeps one column,
the game first, with the chat folded behind an unread count while a game
is on. One chat panel spans both phases by staying in the same place in
the markup — a remounted panel reopened having read nothing, and handed
the reader the lobby's conversation back as unread mail.

The room also keeps the score of the series. PlayerSlot carries the
games a seat has won since the room opened, credited before the lobby is
broadcast so the players see it the moment a game ends, and shown in the
lobby and beside each player's score on the board. It belongs to the
seat rather than to the room, because vacating one is exactly when the
name on it stops meaning the same person.

The conversation reads as a log — one line per message, "name: text",
each seat in its own colour — rather than as a stack of bubbles, which
stops being legible once four people are talking. ChatMessage carries
the seat that spoke, so a line is coloured by what the server said
rather than by matching display names, and a line whose seat has been
vacated is in nobody's colour.

The board's word button gets a test id of its own: it and the chat's
send button both read "Gửi", and the two are now on screen together.
2026-09-08 11:28:19 +07:00
tiennm99 0b507c3537 feat(online): let the two people in a room talk to each other
Chat belongs to the room rather than to a game, so it works in the lobby
while they agree on one, during it, and in the lobby it ends in.

A player is replayed what was said while they held their seat. That is what
a refresh restores, and it is also the boundary: a room code is pasted into
group chats by design, so somebody who redeems one starts at silence rather
than reading what the last two people said. A seat records where the
conversation stood when it was filled; the room keeps twenty lines and no
more, so a room that lives all day cannot grow.

Text is untrusted input rendered in a stranger's browser, so it goes through
the filter nicknames already used — now with a cap on stacked combining
marks, which that filter admitted. Twenty runes made mark stacking a
curiosity; two hundred make it a glyph cluster tall enough to cover the
board, and it would sit in the history being replayed to everyone who
followed.

Talking is not playing. A chat message does not reset the room's idle clock,
or one open tab could hold a room and its code for the life of the process
by typing into it once every nine minutes. It does not spend the move budget
either, and a line to a player who cannot keep up is dropped rather than
allowed to close their session — losing a line is recoverable, losing a
session mid-game costs them the game. A history is not droppable that way:
it is the frame that corrects a whole panel, and there is nothing behind it.

When a seat is vacated its words stay and its author goes, name included,
and the player who stayed is re-synced rather than left holding a name that
the next person through the door could ask for.
2026-09-08 09:49:54 +07:00