Claim a Guest into an Account, new or existing #365
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
spec
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amaurycoudr/chess-project#365
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
A Player who has been playing without an Account has decided Games owned by their browser's Guest. Once they sign up, or sign in to an Account they already have, those Games still belong to the Guest: they show only in this browser's "My games", not on the Player's other devices, and a cleared browser loses them for good. A Player who signs in halfway through a Game keeps their Seat as the Guest (ADR 0034), so that Game would land on the Guest too. And on a shared browser, the Guest's Games may not be theirs at all, so nothing may move without them saying so.
Solution
An Account claims a Guest, and with it the Guest's Games, only when the Player confirms (ADR 0035). The offer appears wherever the browser holds an unclaimed Guest that owns Games:
A claim is final. Every Game the Guest owns moves to the Account, and so does any Game decided later for a Seat the Guest still holds. The Games then show in "My games" on every signed-in browser of the Account. A Guest can be claimed by one Account only, and the claim cannot be undone. After the claim, the Guest keeps the Seats it already holds but takes no new one. The browser forgets the Guest on sign-out, so the next Player on that browser starts with a fresh Guest. Deleting the Account deletes the Guests it claimed.
User Stories
Seams
rpc.fixtures.tson PGlite, signing in through the Session cookies the Accounts v1 spec adds to that fixture. Covers 8, 10, 11, 14, 16, 17. The server halves of 7, 12 and 15 (theguest.getanswers, one listing row for a self-game) are tested here too, as edge paths of those stories.apps/e2e), one new spec:Covers 9, and walks 1, 3 and 8 end to end.
No Room seat: the Room keeps reporting the Guest holding each Seat, and claims are resolved when the Game is written.
Implementation Decisions
Applies: ADR 0035 (the claim), ADR 0034 (occupants and Game ownership), ADR 0030 (Sessions), ADR 0027 (resource-oriented contract), ADR 0028 (origin guard), ADR 0011 and ADR 0019 (front layout and Query), ADR 0025 (copy). No new ADR. If review moves where the claim lives in the front (below), that placement may need a line in ADR 0011's exceptions instead.
Schema (one migration, on top of the Guest history and Accounts v1 schemas; nothing they own is redefined)
guestsrow: the claiming Account, and when it was claimed. Both are set together or not at all.guestsrow only the first time a Guest owns a decided Game. So a Guest header with no row is a normal, valid, unclaimed Guest with zero Games, such as a Player seated in their first Game. The claim upserts the row by hash, as a Game write does.The claim (games feature module in the API, beside the repository the Guest history spec builds)
A Game decided later
Taking Seats
room.createandseat.takeact as the Account (Accounts v1). Nothing changes.guestsrow is an ordinary Guest and is never re-minted.Contract: a new
guestrouter in@chess/contracts. The Guest arrives in the RPC context from its header, never in an input (ADR 0034).guest.get,GET /guest: the caller's Guest as one ofnone: no header;unclaimed, with how many Games it owns, zero for a Guest with no row;claimed-by-you: claimed by the Account of the caller's Session;claimed-by-other: claimed, and the caller has no Session or a different Account.It is a read, so it needs no Origin (ADR 0028) and changes nothing.
guest.claim,POST /guest/claim: needs a Session, refused the way Accounts v1 refuses an unauthenticated call. It answers how many Games moved. New refusals:GUEST_NOT_FOUND(404): no header;GUEST_ALREADY_CLAIMED(409): claimed by another Account.Both go in the contract's refusal status table.
Self-games: the caller's Color in the "My games" listing, the viewer on the Game read (the owner of a Color, or a reader) and the Color in the Accounts v1 export gain one more value, the owner of both Colors. The listing returns such a Game once. This widens the earlier specs' shapes in place, and only this spec can produce the value.
Account export (Accounts v1 owns the flow): it gains one list, the Guests the Account claimed, each with its claim date and nothing else, never the hash. The claimed Games are already in the export as the Account's Games.
Front
auth/), not ingames/: a vertical never imports a sibling, and the sign-up and sign-in views that offer the claim are there. Itsservices/owns:guest.get;onSettled(front rule 7).unclaimed, the form shows a checkbox, ticked by default, naming the count. After sign-up succeeds with it ticked, the view calls the claim. A failed claim leaves the Account signed in, shows a toast and leaves the banner on "My games". The sign-up procedure itself is not changed.unclaimed, a dialog asks "Add N games to your account?" with "Add them" and "Not now". "Not now" changes nothing, and the dialog shows again at the next sign-in.games/view:unclaimed: the offer, with the count;unclaimed: links to sign up and sign in;claimed-by-you;claimed-by-other.Testing Decisions
rpc.integration.test.tswithrpc.fixtures.ts, and the Guest history and Accounts v1 wire tests built on them;room.harness.tsxandroom.handlers.tsshow the pattern), and the Accounts v1 sign-up and sign-in view tests;resign.spec.tsandroom.fixtures.ts(seatedPair) inapps/e2e, with the Accounts v1 e2e helpers for signing up and in.you;unclaimedwith zero Games; the Games stay readable, ownerless on that side;guest.getin each of its four answers, includingunclaimedwith zero for a header with no row, and a claimed Guest read with a different Account's Session;Out of Scope
Further Notes
to-ticketsif it does not fit one.mentioned in issue #385
mentioned in issue #386
mentioned in issue #387
mentioned in issue #388
marked this issue as related to #385
marked this issue as related to #386
marked this issue as related to #387
marked this issue as related to #388
mentioned in merge request !527
mentioned in merge request !529