Rework the room rail: status, actions, leaving, and a Room that is gone #243

Closed
opened 2026-10-05 08:17:36 +00:00 by root · 11 comments
root commented 2026-10-05 08:17:36 +00:00 (Migrated from gitlab.core-project.space)

Imported from GitHub amaurycoudr/chess-project#243, opened by @amaurycoudr on 2026-10-05 08:17 UTC.
Closed on GitHub on 2026-10-05 13:37 UTC as completed.


Problem Statement

UI feedback on the online game's side rail:

  • The status line is unclear. It holds one string, so a degraded connection hides whose Turn it is. It mixes seat-relative wording ("Your move") with Color wording ("Black resigned") for the same Player. Its height changes with text length and with the pending-Offer block under it, so the rail jumps.
  • Actions are stacked vertically, and on desktop they are a column of unlabelled icons.
  • Chess actions (draw, resign) and Room actions (copy the link, restart, leave) are mixed together.
  • "Leave the game" acts immediately, although it frees the Seat for good.
  • When the opponent leaves, the remaining Player gets nothing clear:
    • after an explicit Leave, the status says "Your move" forever and the Offers vanish unexplained;
    • after a closed tab, it says "Opponent disconnected, reconnecting…", then after a client-side minute "Opponent left the game". That is false, since the Seat is kept for the whole TTL.
  • When the Room is evicted or lost (server sleep or redeploy), the change stream stops silently, the board freezes, and the first action only shows a toast.

Solution

  • Status block with a fixed height.
    • A headline: a dot of the Color to move, then the Turn or the result.
    • A detail line, always reserved, for check, opponent presence and the Player's own pending Offer.
    • Seated Players read seat-relative wording everywhere. Spectators keep Color wording.
  • Two horizontal action bars.
    • Game actions under the status: Offer a draw, Resign. Once the Game is over, they are replaced by a primary "Play again".
    • Room actions in the rail header beside the Room code: Copy invite link, Offer a restart, Leave.
    • Icon buttons get tooltips on desktop and labels on mobile.
    • "Withdraw" for the Player's own Offer moves from the status into the game bar.
  • Leaving asks first. A seated Player confirms in a modal; a Spectator leaves directly.
  • Opponent presence tells the truth.
    • "Waiting for an opponent" when the other Seat was never taken.
    • "Your opponent lost connection." while they are disconnected, with no switch to "left".
    • "Your opponent left." with [Copy invite link] [Leave] once they freed their Seat. The Player can keep moving.
    • When someone takes the freed Seat: a toast "A new opponent joined", and Offers come back.
  • A gone Room is terminal. A blocking modal says "This room has closed", with [Back home] and [New game].

User Stories

  1. As a seated Player, I want the headline to show whose Turn it is with a Color dot, so that I can tell at a glance.
  2. As a seated Player, I want check, opponent presence and my pending Offer on a separate detail line, so that none of them hides the Turn.
  3. As a seated Player, I want the status block to keep the same height whatever it says, so that the rail and the actions do not jump.
  4. As a seated Player, I want results worded relative to me ("You resigned", "Your opponent resigned", "Checkmate, you win", "Checkmate, you lose"), so that I don't translate Colors back into me.
  5. As a Spectator, I want results and Turns in Color wording, so that the line stays neutral.
  6. As a Room creator alone in the Room, I want "Waiting for an opponent" instead of "Your move", so that I know to share the link.
  7. As a seated Player, I want the game actions (Offer a draw, Resign, or Play again once over, plus Withdraw for my own Offer) in one horizontal bar under the status, so that they read as one group.
  8. As a seated Player, I want the Room actions (Copy invite link, Offer a restart, Leave) in the rail header beside the Room code, so that they are kept apart from chess actions.
  9. As a desktop user, I want icon-only actions to show their label in a tooltip, so that I know what each one does.
  10. As a seated Player, I want to confirm before leaving, in a modal saying my Seat will be freed and I can't come back, so that I don't leave by mistake.
  11. As a Spectator, I want Leave to act immediately, so that watching costs nothing to stop.
  12. As a seated Player whose opponent closed their tab, I want "Your opponent lost connection." for as long as they are away, so that I am not told they left when their Seat is still theirs.
  13. As a seated Player whose opponent clicked Leave, I want "Your opponent left." with Copy invite link and Leave, so that I know the Seat is free and what I can do.
  14. As a seated Player whose opponent left, I want to keep making my Moves, so that the Game is not frozen while I wait.
  15. As a seated Player, I want a toast "A new opponent joined" and my Offers back when someone takes the freed Seat, so that I know the Game is live again.
  16. As anyone in a Room that no longer exists on the server, I want a blocking modal "This room has closed" with Back home and New game, so that I am not left on a frozen board.

Seams

  • RoomView through the room harness and MSW (front view tests): stories 1–12 and 14–16.
  • The Room domain class (API unit tests): the vacated Seat state in the snapshot. It appears after leave, returns to taken after join, and never shows on a never-taken Seat. This seam backs stories 13 and 15 server-side.
  • apps/e2e browser suite: story 13 end to end. A Player clicks Leave and the other sees "Your opponent left."

Implementation Decisions

  • Contract.
    • The Seat view replaces occupied: boolean with a three-state field: never taken / taken / vacated.
    • "Vacated" means its last occupant left through room.leave. A later join makes it taken again.
    • connected stays.
    • Add Vacated Seat to CONTEXT.md.
  • Presence.
    • Opponent presence becomes: waiting (never taken), present, lost connection (taken, disconnected), left (vacated).
    • The 60-second client timer is removed.
    • This contradicts ADR 0016's "Opponent left is a 60-second client timer" bullet. ADR 0016 needs an amendment, in its own PR.
  • Status text. The room status describer returns a headline and an optional detail instead of one string. The live-region urgency rules carry over: a result is assertive, check is assertive, opponent left is assertive.
  • Actions.
    • The rail gains a header slot for Room actions.
    • The vertical layout of FloorAction gives way to a horizontal bar.
    • The refused-action rule stays: absent, not disabled.
    • "Offer a restart" moves to the Room actions. "Play again" at game end is the same restart Offer.
  • Leave confirmation. ConfirmedAction gains a leave action, shown only to seated Players.
  • Gone Room.
    • Two sources: the change stream's ROOM_NOT_FOUND refusal, and any write refused with it.
    • Both put the view into a room-closed state that renders the blocking modal. The board stays visible behind it.
  • Tooltip. A design-system Tooltip built on Base UI, like the Dialog.
  • Applicable ADRs: 0015, 0016 (amended), 0017, 0018, 0019.
  • New copy goes into the i18n catalogue from the language-provider spec.

Testing Decisions

  • Prior art.
    • room-presence.test.tsx and room-offers.test.tsx (view).
    • room.test.ts and room-offer.test.ts (API Room).
    • presence.spec.ts (e2e).
  • Edge cases.
    • An opponent leaves with an Offer live: it is withdrawn or auto-declined, and the Player sees the resolution toast and the left panel.
    • A Seat is vacated and then retaken by a new opener.
    • A Room gone while the stream was down: the modal appears on the next write.
    • Check and lost connection together: both show, neither hides the Turn.
    • A Spectator never sees the leave modal or a seat-relative string.
  • Update the existing presence e2e expectations from the old strings.

Out of Scope

  • An indicator for the viewer's own lost connection (the silent SSE retry loop).
  • Spectator-facing presence messages beyond the current behaviour.
  • Treating Leave as a Resignation: decided against, so the Seat is freed and the Game continues.
  • Persisting Rooms across server restarts.

Further Notes

  • Spans several PRs; run to-tickets on it. A natural split:
    1. the status block and wording;
    2. the action bars, tooltip and leave modal;
    3. the vacated Seat contract and presence;
    4. the gone-Room modal.
  • Depends on the language-provider spec (#242) landing first.
> _Imported from GitHub [amaurycoudr/chess-project#243](https://github.com/amaurycoudr/chess-project/issues/243), opened by [@amaurycoudr](https://github.com/amaurycoudr) on 2026-10-05 08:17 UTC._ > _Closed on GitHub on 2026-10-05 13:37 UTC as completed._ <!-- github-issue:243 --> --- ## Problem Statement UI feedback on the online game's side rail: - The status line is unclear. It holds one string, so a degraded connection hides whose Turn it is. It mixes seat-relative wording ("Your move") with Color wording ("Black resigned") for the same Player. Its height changes with text length and with the pending-Offer block under it, so the rail jumps. - Actions are stacked vertically, and on desktop they are a column of unlabelled icons. - Chess actions (draw, resign) and Room actions (copy the link, restart, leave) are mixed together. - "Leave the game" acts immediately, although it frees the Seat for good. - When the opponent leaves, the remaining Player gets nothing clear: - after an explicit Leave, the status says "Your move" forever and the Offers vanish unexplained; - after a closed tab, it says "Opponent disconnected, reconnecting…", then after a client-side minute "Opponent left the game". That is false, since the Seat is kept for the whole TTL. - When the Room is evicted or lost (server sleep or redeploy), the change stream stops silently, the board freezes, and the first action only shows a toast. ## Solution - **Status block with a fixed height.** - A headline: a dot of the Color to move, then the Turn or the result. - A detail line, always reserved, for check, opponent presence and the Player's own pending Offer. - Seated Players read seat-relative wording everywhere. Spectators keep Color wording. - **Two horizontal action bars.** - Game actions under the status: Offer a draw, Resign. Once the Game is over, they are replaced by a primary "Play again". - Room actions in the rail header beside the Room code: Copy invite link, Offer a restart, Leave. - Icon buttons get tooltips on desktop and labels on mobile. - "Withdraw" for the Player's own Offer moves from the status into the game bar. - **Leaving asks first.** A seated Player confirms in a modal; a Spectator leaves directly. - **Opponent presence tells the truth.** - "Waiting for an opponent" when the other Seat was never taken. - "Your opponent lost connection." while they are disconnected, with no switch to "left". - "Your opponent left." with [Copy invite link] [Leave] once they freed their Seat. The Player can keep moving. - When someone takes the freed Seat: a toast "A new opponent joined", and Offers come back. - **A gone Room is terminal.** A blocking modal says "This room has closed", with [Back home] and [New game]. ## User Stories 1. As a seated Player, I want the headline to show whose Turn it is with a Color dot, so that I can tell at a glance. 2. As a seated Player, I want check, opponent presence and my pending Offer on a separate detail line, so that none of them hides the Turn. 3. As a seated Player, I want the status block to keep the same height whatever it says, so that the rail and the actions do not jump. 4. As a seated Player, I want results worded relative to me ("You resigned", "Your opponent resigned", "Checkmate, you win", "Checkmate, you lose"), so that I don't translate Colors back into me. 5. As a Spectator, I want results and Turns in Color wording, so that the line stays neutral. 6. As a Room creator alone in the Room, I want "Waiting for an opponent" instead of "Your move", so that I know to share the link. 7. As a seated Player, I want the game actions (Offer a draw, Resign, or Play again once over, plus Withdraw for my own Offer) in one horizontal bar under the status, so that they read as one group. 8. As a seated Player, I want the Room actions (Copy invite link, Offer a restart, Leave) in the rail header beside the Room code, so that they are kept apart from chess actions. 9. As a desktop user, I want icon-only actions to show their label in a tooltip, so that I know what each one does. 10. As a seated Player, I want to confirm before leaving, in a modal saying my Seat will be freed and I can't come back, so that I don't leave by mistake. 11. As a Spectator, I want Leave to act immediately, so that watching costs nothing to stop. 12. As a seated Player whose opponent closed their tab, I want "Your opponent lost connection." for as long as they are away, so that I am not told they left when their Seat is still theirs. 13. As a seated Player whose opponent clicked Leave, I want "Your opponent left." with Copy invite link and Leave, so that I know the Seat is free and what I can do. 14. As a seated Player whose opponent left, I want to keep making my Moves, so that the Game is not frozen while I wait. 15. As a seated Player, I want a toast "A new opponent joined" and my Offers back when someone takes the freed Seat, so that I know the Game is live again. 16. As anyone in a Room that no longer exists on the server, I want a blocking modal "This room has closed" with Back home and New game, so that I am not left on a frozen board. ## Seams - **`RoomView` through the room harness and MSW** (front view tests): stories 1–12 and 14–16. - **The `Room` domain class** (API unit tests): the vacated Seat state in the snapshot. It appears after `leave`, returns to taken after `join`, and never shows on a never-taken Seat. This seam backs stories 13 and 15 server-side. - **`apps/e2e` browser suite**: story 13 end to end. A Player clicks Leave and the other sees "Your opponent left." ## Implementation Decisions - **Contract.** - The Seat view replaces `occupied: boolean` with a three-state field: never taken / taken / vacated. - "Vacated" means its last occupant left through `room.leave`. A later `join` makes it taken again. - `connected` stays. - Add **Vacated Seat** to `CONTEXT.md`. - **Presence.** - Opponent presence becomes: waiting (never taken), present, lost connection (taken, disconnected), left (vacated). - The 60-second client timer is removed. - This contradicts ADR 0016's "Opponent left is a 60-second client timer" bullet. **ADR 0016 needs an amendment, in its own PR.** - **Status text.** The room status describer returns a headline and an optional detail instead of one string. The live-region urgency rules carry over: a result is assertive, check is assertive, opponent left is assertive. - **Actions.** - The rail gains a header slot for Room actions. - The vertical layout of `FloorAction` gives way to a horizontal bar. - The refused-action rule stays: absent, not disabled. - "Offer a restart" moves to the Room actions. "Play again" at game end is the same `restart` Offer. - **Leave confirmation.** `ConfirmedAction` gains a `leave` action, shown only to seated Players. - **Gone Room.** - Two sources: the change stream's `ROOM_NOT_FOUND` refusal, and any write refused with it. - Both put the view into a room-closed state that renders the blocking modal. The board stays visible behind it. - **Tooltip.** A design-system Tooltip built on Base UI, like the Dialog. - Applicable ADRs: 0015, 0016 (amended), 0017, 0018, 0019. - New copy goes into the i18n catalogue from the language-provider spec. ## Testing Decisions - **Prior art.** - `room-presence.test.tsx` and `room-offers.test.tsx` (view). - `room.test.ts` and `room-offer.test.ts` (API `Room`). - `presence.spec.ts` (e2e). - **Edge cases.** - An opponent leaves with an Offer live: it is withdrawn or auto-declined, and the Player sees the resolution toast and the left panel. - A Seat is vacated and then retaken by a new opener. - A Room gone while the stream was down: the modal appears on the next write. - Check and lost connection together: both show, neither hides the Turn. - A Spectator never sees the leave modal or a seat-relative string. - Update the existing presence e2e expectations from the old strings. ## Out of Scope - An indicator for the viewer's own lost connection (the silent SSE retry loop). - Spectator-facing presence messages beyond the current behaviour. - Treating Leave as a Resignation: decided against, so the Seat is freed and the Game continues. - Persisting Rooms across server restarts. ## Further Notes - Spans several PRs; run `to-tickets` on it. A natural split: 1. the status block and wording; 2. the action bars, tooltip and leave modal; 3. the vacated Seat contract and presence; 4. the gone-Room modal. - Depends on the language-provider spec (#242) landing first.
root commented 2026-10-05 08:17:36 +00:00 (Migrated from gitlab.core-project.space)

GitHub issue relations, imported as "related" links (GitLab CE has no sub-issues or blocking links).

> _GitHub issue relations, imported as "related" links (GitLab CE has no sub-issues or blocking links)._ <!-- github-relations --> - Sub-issues: #244, #245, #246, #247, #248
root (Migrated from gitlab.core-project.space) closed this issue 2026-10-05 13:37:22 +00:00
root commented 2026-10-13 08:39:14 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #244

mentioned in issue #244
root commented 2026-10-13 08:39:14 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #245

mentioned in issue #245
root commented 2026-10-13 08:39:14 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #246

mentioned in issue #246
root commented 2026-10-13 08:39:14 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #247

mentioned in issue #247
root commented 2026-10-13 08:39:15 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #248

mentioned in issue #248
root commented 2026-10-13 08:41:53 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #244

marked this issue as related to #244
root commented 2026-10-13 08:41:53 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #245

marked this issue as related to #245
root commented 2026-10-13 08:41:53 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #246

marked this issue as related to #246
root commented 2026-10-13 08:41:53 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #247

marked this issue as related to #247
root commented 2026-10-13 08:41:54 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #248

marked this issue as related to #248
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amaurycoudr/chess-project#243
No description provided.