Skip to content

feat(tracking): show your game status on every game card - #59

Merged
kris1027 merged 20 commits into
mainfrom
feat/game-cards-everywhere
Oct 26, 2026
Merged

kris1027 merged 20 commits into
mainfrom
feat/game-cards-everywhere

Conversation

@kris1027

Copy link
Copy Markdown
Owner

What

Game cards now show your own state everywhere they appear: on the /games landing, the five /games/{slug} list pages and /search?type=game. When you are signed in, each card shows your score when you have one, a Want to play bookmark you can tap, a release pill on a wanted game that is not out yet, and a read only Playing or Finished chip on a game you have started. The library tabs get the same bookmark and chip.

Why

This builds scope feature G8 from spec 0030 (rationale). Until now game cards were plain covers with the IGDB rating, so you had to open a game to see whether it was already yours. The spec mirrors the movie card slots: one grid read per grid, a pure card status function, a server badge slot and controls slot, and one small client bookmark. It adds no table and no Server Action.

Changes

One read per grid (lib/tracking/, lib/catalog/)

  • New getGameGridStates(idsKey) in lib/tracking/game-state.ts, wrapped in React cache(). It reads user_game_state once for all the grid's ids, under the user's session. gameIdsKey sorts and dedupes the ids, so every card on a grid shares that one read. It checks publicEnvProblems() first, and a failed read logs once and falls back to the plain card.
  • New pure gameCardStatus(state, releaseDate, today) in lib/catalog/game-page.ts. It reads straight from the stored status, so a card always names the library tab the game is on (AC-8).

Card slots (components/tracking/)

  • GameCardBadge shows your cyan score on a Playing or Finished game that has one, and the amber IGDB rating otherwise (also while loading, for a visitor, or after a failed read).
  • GameCardControls shows the bookmark on a card with no status or with Want to play, and the GameStatusChip on a started game. A Want to play game that is not released yet (UTC day rule) gets the ReleasePill, or Date TBA. releasePill={false} skips it on cards that already print the date, so Coming soon never shows two.
  • GameCardBookmarkButton is optimistic and disabled while its write runs. Unpressed it calls setGameStatus(id, "want_to_play"); pressed it calls removeGame(id). A failure rolls it back and shows showGameTrackingError (with the Sign in action back to the grid's page). Every undo goes to showGameUndo, so removing offers Undo, and adding over a stale started card shows the score cleared Undo.
  • GameStatusChip has no server-only import, so the client library grid can render it too.

Wired into every grid (components/catalog/, components/search/)

  • GameListCard takes gridGameIds and returnPath, wraps the badge and controls in their own Suspense boundaries, and prints the release year as its meta line. The old meta prop is gone.
  • CatalogListCards passes each list page's ids; /search passes the results page's ids and searchHref(params) as the return path.
  • LANDING_SLOTS.game now has a badge, controls and a new meta field (the year). Badge and controls are no longer nullable, so LandingBadge and LandingControls lose their null checks. This also lights the Coming soon wide cards and the Top rated rows.

Library tabs (components/library/)

  • LibraryGameItem gets a status field, set in gameLibraryTab.
  • On Want to play the X becomes the green PlannedIcon button, named Remove {name} from Want to play, using the same removal and Undo the grid already owns. Playing and Finished cards show the chip left of the X. The missing game card keeps the X.

Smaller pieces

  • The icon key reads Plan / Want to play and Planned / Want to play.
  • /showcase gets a Game card section with all seven states (visitor, no status, Want to play released, unreleased, TBA, Playing scored, Finished unscored).
  • Spec 0030 with its rationale and verify checklist; scope G8 moves to in progress with build, verify and test ticked.

How to test / verify

You can run these against pnpm dev:docker with NEXT_PUBLIC_GAMES_LIBRARY=on. The full checklist is in verify.md; the UI and command steps are ticked, and three value sourcing checks are still open there (grid ids never mixed across sections, no score badge on a Want to play row, the UTC day boundary in a timezone ahead of UTC).

  • pnpm typecheck && pnpm lint && pnpm test, then pnpm build. /games, every /games/{slug} and /search should stay partially prerendered (◐).
  • Signed in, open /search?type=game&q=witcher. The grid should run one user_game_state query for all its cards.
  • Tap a bookmark on a game you have no status for. It should turn green at once with no toast, and the game should be on /watchlist?type=game. Tap it again: "Removed from your games." with Undo.
  • Make a game Playing with a score on its page. Its card should show the cyan score and a Playing chip, with no button.
  • Want to play a game releasing after today (UTC). The release pill should show beside the bookmark. On /games/coming-soon there should still be exactly one date pill.
  • Sign out in another tab and tap a bookmark. It should roll back and offer Sign in back to the same URL.
  • On /watchlist?type=game the card should show the green bookmark; on /upcoming?type=game and /watched?type=game the chip left of the X.
  • Signed out, /games, /games/popular and /search?type=game should show IGDB ratings only, no bookmark or chip.

Risk & rollout

Low risk. No migrations, no new Server Actions and no new flags. The cards add no NEXT_PUBLIC_GAMES_LIBRARY gate of their own; the routes keep the gates they have. Shows and movies are touched only through LANDING_SLOTS (they get meta: null) and the icon key labels. Rollback is a plain revert.

Tradeoffs the spec accepts:

  • A card cannot change a started game's status or score; you use the game page or the library tab for that.
  • A stale card's bookmark can still move a started game back to Want to play. The score cleared Undo toast is the only guard.
  • /games runs one status query per section (about five), as the movie landing does.
  • If Supabase is down, every game card quietly falls back to the plain catalog card.

Notes for reviewers

  • Worth a close look: releasePill in GameListCard is releaseDay === undefined, so the coming soon list keeps its visitor pill and the slot skips its own. The landing WideCard already passed releasePill={false}.
  • RemoveButton in game-library-card.tsx gains a className prop, so the X after a chip does not take its own ml-auto (the chip already pushes the row right).
  • Follow ups after merge: /sync should update the components/AGENTS.md line saying the game landing entry is null until G8, and the root card status line should name getGameGridStates. The G8 follow ups in specs 0027, 0028 and 0029 can be ticked.

🤖 Generated with Claude Code

kris1027 and others added 12 commits October 26, 2026 11:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris1027 kris1027 added docs Documentation additions and updates feature New user-facing functionality test Automated tests and test infrastructure ui Layouts, styling, and shared interface components labels Oct 26, 2026
@kris1027 kris1027 self-assigned this Oct 26, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in BeStats Oct 26, 2026
@vercel

vercel Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bestats Ready Ready Preview Oct 26, 2026 11:43am UTC

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kris1027 and others added 6 commits October 26, 2026 12:38
The movie, game, show and episode reads each carried the same sort and
join one-liner, and the card slot tests stubbed a copy of it. One pure
idsKey and parseIdsKey in lib/tracking/ids-key.ts now serve them all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 0030's surface table says an empty key returns an empty map with no
query; the check ran after getOptionalUser, so an empty grid still read
the session and a visitor got signed_out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Playing or Finished literal and the want_to_play comparisons were
written out across the chip, the card status, the showcase, the card
slots and the library card. StartedGameStatus and isStartedGame in
lib/tracking/types.ts now carry that rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readStatus wrapped its result in an object only to tell a visitor or a
failed read apart from a game with no row, so callers wrote
status.status. readCard returns unknown or known with the card instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoveButton defaulted to ml-auto and the started card cancelled it with
an empty className. The button now sets no margin, and the one card
where the X stands alone passes ml-auto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 0030 AC-8 says a card and its library page read the same stored
row. gameItem took the status from the tab's GAME_LIBRARY_TABS entry
instead, which only matched while the query filtered on it. The tab read
now selects status and the card shows row.status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris1027
kris1027 merged commit 93c516a into main Oct 26, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in BeStats Oct 26, 2026
@kris1027
kris1027 deleted the feat/game-cards-everywhere branch October 26, 2026 11:47

This branch was successfully deployed

1 active deployment
Preview — 5d47bac7 Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation additions and updates feature New user-facing functionality test Automated tests and test infrastructure ui Layouts, styling, and shared interface components

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant