WI-037: A name of your own, a date, and a play you can correct
WI-037: A name of your own, a date, and a play you can correct
Four things, all found by actually using what WI-036 shipped.
The bug: logging was a one-shot
After logging a play the component entered a saved state and stayed there. Expo Router keeps a screen mounted in the stack, so going back and returning showed “Logged” from the previous visit with no way to record another play.
Logging the same game twice is supported by design — ADR-0006 keeps every play, because opinion changing over time is the product. The UI made it a dead end anyway. The confirmation is now transient, and resets when the screen loses focus.
Your name, not your id
displayNameFrom fell back to Player <last six of sub> when the token carried no name — and a bare Clerk session token carries only sub unless the session token template is configured otherwise. WI-013 noted that dependency and then built as though the fallback were rare. It was the common case.
For a Google sign-in it was also avoidable: Clerk holds the real name on the user object even when the token does not carry it. nameFromClerk asks, once, only when creating a local row — never on a request that already has one, because a lookup per request would put a round trip in front of everything.
Failure there is silent by design. A missing name deserves a fallback; it does not deserve a failed sign-in.
And since neither source is the person’s own choice, PATCH /me lets them change it, editable in place on the account bar.
Back-dating
sessions.played_at was always stored and POST always accepted it. The form never sent it, so a game played on Tuesday could only be logged as today. A date field now sits behind Add detail; blank means today.
The parser has three outcomes rather than two — blank, invalid, and a date. Blank sends nothing and lets the API default, which is not the same as sending today’s date, and invalid says so rather than silently logging the wrong day. It rejects the future, rejects 31 February, and stores midday rather than midnight so a timezone cannot move the day.
Correcting a play
PATCH /me/sessions/:id. Mistyping an 8 as a 1 should not mean deleting the evening. Rating changes re-project through the existing trigger.
Absent means leave alone; null clears. Those are different and a partial update has to say which.
WI-036’s spec claimed “the route exists” for editing. It did not — only delete did. That sentence is corrected.
Two tests that passed for the wrong reason
Worth recording, because both looked like coverage.
Ownership. Removing the user scope from updateSession still produced a 404 — the route then looked up the editor’s shelf entry, found none, and returned not-found after modifying the row. The test asserted a status code that happened to be right. It now asserts the data is unchanged.
Date format. Loosening the format regex left every enumerated bad input still invalid, because the calendar check caught them instead. The format itself was untested. A second case pins it with real dates in the wrong shape.
Definition of done
- Logging twice in a row works; the confirmation clears itself and on losing focus.
- A Google sign-in gets its real name.
- Anybody can rename themselves; a blank name is refused.
- A play can be back-dated, and an unparseable date is refused rather than guessed.
- A recorded play can be corrected, and only the fields sent change.
- Editing another user’s play changes nothing — asserted on the data, not the status.
- The date format is pinned by inputs only the format can reject.
- Each check proven able to fail.
- Gate green.
Verification
pnpm --filter @tabletop/api testpnpm --filter @tabletop/mobile testpnpm gateNotes
CLERK_SECRET_KEY is now used, having been configured and unused since WI-H04. Verification stays networkless (ADR-0005); this is one call per new user, not per request.
The sticky-state bug is not covered by a test. It is component state, and there is no renderer in this suite — the same gap WI-023 recorded. The fix is real and the reasoning is written down, but nothing stops it regressing. A renderer is worth its own item now that the app has behaviour worth asserting.