WI-014: Move reviews onto the session, add session tags
WI-014: Move reviews onto the session, add session tags
Problem
Two things, one a defect and one a gap.
The defect. review lives on shelf_entries while rating lives on sessions. ADR-0006 indicts this in its own words:
Goodreads is shelf-first and bolted rereads on badly — still one rating and one review per book.
WI-010 fixed the rating and left the review Goodreads-shaped. Writing a new review today overwrites what you thought last time, which is the exact loss the ADR exists to prevent. It is cheap to fix now and painful once there are reviews to migrate.
The gap. Notable moments — a comeback, a rules argument, a game decided on the last turn — can only go in free-text notes. Nothing is queryable, so there is no way to surface them in a feed or filter by them.
In scope
sessions.review(text, nullable) — the prose opinion for this play.shelf_entries.reviewbecomes a projection of the most recent session review, maintained by the existing trigger (ADR-0021), and raises on direct write exactly asratingdoes.sessions.tags(text[], default{}) with a GIN index.- Regenerated
packages/sharedschemas;newShelfEntrySchemamust now omitreviewas well asrating.
Out of scope
- A moment/tag taxonomy with its own table. Free tags first; formalise only if they are actually used.
- Backfill. No production data exists.
- Teaching cost — WI-015 (#32). Mechanic reactions were deferred there; see that spec for why.
Definition of done
-
sessions.reviewexists and is nullable — one-tap logging is unaffected (ADR-0007). - A newer session review projects to the shelf and the older session’s review is unchanged — the rating invariant, applied to prose.
- Deleting the most recent session re-projects the review, and clears it when no reviewed session remains.
- Writing
shelf_entries.reviewdirectly raises, with the same wording style as the rating guard. -
sessions.tagsaccepts an array, defaults to empty, and is GIN-indexed; a query filtering by tag uses the index. -
newShelfEntrySchemaomits bothratingandreview; a test asserts it. - Gate green.
Verification
pnpm --filter db test -t "invariant"pnpm --filter @tabletop/shared testpnpm gateNotes
The trigger already recomputes on insert, update and delete for rating. Extending it to review is the same query with a second column, so the delete and clear paths come along for free — but they still need their own tests, because “it should work the same way” is not evidence.
Tags are deliberately uncontrolled. A vocabulary imposed before anyone has logged a play is a guess; ADR-0017’s controlled vocabulary exists for catalogue mechanics, which is a different problem.