Skip to content

WI-039: Your plays, and a way to correct them

WI-039: Your plays, and a way to correct them

Plays have gone in since WI-036 and never come back out. fetchSessions has existed the whole time with nothing calling it, and PATCH/DELETE /me/sessions/:id — built and tested in WI-037 — had no client and no screen.

Logging is one tap by design (ADR-0007), which means most plays arrive with no rating, no notes and today’s date. Correcting one afterwards is not a nicety in that design; it is the other half of it. And because the model keeps every play forever (ADR-0006), a play logged by mistake needs a way out.

In scope

  • app/plays.tsx — the list, signed-in only, refreshed on focus.
  • src/session-row.tsx — an inline editor per row: rating, date, notes, delete.
  • src/played-date.ts — an instant back into a calendar day, and its display form.
  • updateSession / deleteSession on the API client.

Three things that are easy to get wrong

Local components, not UTC

parsePlayedOn stores midday UTC so a typed day survives any real timezone. Reading it back has to use local components:

  • A typed date is midday UTC, the same local day everywhere from UTC-11 to UTC+11 — local recovers it exactly.
  • A one-tap play is stored as now. At 23:30 in London that is already tomorrow in UTC, so a UTC read shows the play on the day after it happened, and prefills an editor that silently moves it.

Under TZ=UTC both readings agree and every test passes either way. CI runs in UTC, so the mobile suite now pins TZ=America/Los_Angeles — otherwise the check is present and worthless.

A blank date means “leave it alone”

In the log form, blank means today: there is no date yet, and the API defaulting to now is the right answer. In the edit form there already is a date, and reading blank as today would quietly move a play. Absent from the patch, per the API’s own absent-vs-null contract.

Alert.alert would have done nothing

The obvious way to confirm a delete is Alert.alert. react-native-web does not implement it — on the surface this project actually deploys, the dialog never appears and the button does nothing at all. The confirmation is inline and two-step instead, which works on both and can be tested.

Definition of done

  • The list shows recorded plays, most recent first, and refreshes on focus.
  • A play logged with one tap can be given a rating afterwards.
  • Tapping the same rating again clears it — sent as null, not omitted.
  • The date is prefilled, and a round trip does not move the play.
  • An emptied date field changes nothing.
  • An unparseable date refuses and leaves the form usable.
  • A failed save keeps what was typed.
  • Delete asks first, and can be backed out of.
  • DELETE returning 204 does not throw.
  • Each check proven able to fail.
  • Gate green.

Verification

Terminal window
pnpm --filter @tabletop/mobile test
pnpm gate

Seeded failures

SeedBit
toDateInput reads UTC components1 test
A blank date field means today1
Delete without asking3
A failed save throws away the draft1
Clearing a rating omits it instead of nulling it2
The spinner starts before the date is checked1
Parse the body of a 204 anyway1
Paste the id straight into the path1
Fill the patch out with nulls2

Nine seeded, nine bit — the first time in this project that has been true on the first pass, and the reason is that all nine were written before the code rather than after.

Not done here

  • Participants cannot be changed after the fact. PUT /me/sessions/:id/participants exists and replaces the set; the row does not offer it, because a chip picker is a bigger piece of UI than the rest of this item put together.
  • review and durationMinutes are in the client type and on no screen.