diff --git a/AGENTS.md b/AGENTS.md index fb2940d..680c73f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -284,7 +284,7 @@ Games are a third media type, planned in [docs/scope/games.md](docs/scope/games. - **Ratings:** a personal score from 1 to 10, only on Playing or Finished. Moving back to Want to play clears it, with Undo restoring status, score and timestamps; Playing to Finished and back keeps it. IGDB's community rating shows out of 10 and is always labelled as the community rating. - **Timestamps:** added, started (the first time a game becomes Playing) and finished (every time it becomes Finished). Want to play sorts by added, Playing by started, Finished by finished. - **Not tracked:** platform played on, hours played, 100% completion. -- **Navigation:** SHOWS | MOVIES | GAMES with `?type=game`; for games `/watchlist` is Want to play, `/upcoming` Playing, `/watched` Finished. Since G5 the GAMES tab shows only on those three library pages and only while `NEXT_PUBLIC_GAMES_LIBRARY` is `on` (spec 0027); it reaches every page in G6, and game pages reach the sitemap only in G10. +- **Navigation:** SHOWS | MOVIES | GAMES with `?type=game`; for games `/watchlist` is Want to play, `/upcoming` Playing, `/watched` Finished. Since G6 the GAMES tab shows on every page while `NEXT_PUBLIC_GAMES_LIBRARY` is `on` (spec 0028), and `/games` is the games landing with five paged list pages (`/games/{slug}`); game pages reach the sitemap only in G10. ## Build approach @@ -320,7 +320,7 @@ Tracer Bullet: prove the whole pipe works with one thin real thread, then thicke - Production runs from [docs/deploy.md](docs/deploy.md), the runbook of spec 0018: push a migration with `supabase db push` before merging the PR that adds it, and auth settings reach the cloud only through `supabase config push` (never the dashboard). Run both pushes yourself; from an agent shell the CLI applies them without stopping at the prompt. `vercel.json` pins functions to `fra1`, next to the Supabase region `eu-central-1`. The `/api/search` rate limit is a Vercel Firewall rule, recorded only in `docs/deploy.md`. - `NEXT_PUBLIC_AUTH_EMAIL_DELIVERY` (`on` or `off`, unset meaning `off`) is read only through `getAuthEmailDelivery()` in `lib/env.ts`, separate from `getPublicEnv()` so previews with no Supabase variables still build. Production is `off`: no auth email is sent or promised. Being a `NEXT_PUBLIC_` value, a change takes a redeploy. - `NEXT_PUBLIC_GAMES_LIBRARY` (`on` or `off`, unset meaning `off`) gates the games tabs (spec 0027). It is read only in `lib/env.ts`, through `getGamesLibrary()` (throws on an invalid value; only `LibrarySection` calls it) and `gamesLibraryEnabled()` (an invalid value reads as off; the navbar uses it). Production leaves it unset, Preview and `.env.local` set `on` (`docs/deploy.md`). -- `game` is a `LibraryType` (`MediaType | "game"`), never a `MediaType`: `parseLibraryTypeParam` and the `typedHref` overloads accept it only on `/watchlist`, `/upcoming` and `/watched`, so `/search`, the catalogs, the proxy and `CATALOG_PATHS` cannot receive it until G6 or G7 widens the type on purpose. A games tab reads its stored status, not a classification: one counted `user_game_state` page of 24 plus one `getGamesByIds` batch for those ids (`lib/tracking/game-library.ts`), with no 500 title limit. +- `game` is a `LibraryType` (`MediaType | "game"`), never a `MediaType`: `parseLibraryTypeParam` and the `typedHref` overloads accept it only on `/watchlist`, `/upcoming` and `/watched`, so `/search` cannot receive it until G7 widens the type on purpose. G6 widened `CATALOG_PATHS`, `CATALOG_SOURCES`, the list registry and the proxy's list slug rule to `game` (spec 0028). A games tab reads its stored status, not a classification: one counted `user_game_state` page of 24 plus one `getGamesByIds` batch for those ids (`lib/tracking/game-library.ts`), with no 500 title limit. ## Code conventions diff --git a/app/catalog-lists.test.ts b/app/catalog-lists.test.ts index c7e2658..455fb81 100644 --- a/app/catalog-lists.test.ts +++ b/app/catalog-lists.test.ts @@ -2,10 +2,14 @@ import { readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { MOVIE_LIST_SLUGS, TV_LIST_SLUGS } from "@/lib/catalog/lists"; +import { + GAME_LIST_SLUGS, + MOVIE_LIST_SLUGS, + TV_LIST_SLUGS, +} from "@/lib/catalog/lists"; /** - * covers: spec 0021, the registry invariant + * covers: spec 0021, the registry invariant; spec 0028, AC-9 * * A slug is defined once, in `lib/catalog/lists.ts`, and each list has its * own static route folder. This keeps the two in step: a folder with no @@ -22,6 +26,7 @@ function staticRouteFolders(dir: string): string[] { describe.each([ ["app/shows", "tv", TV_LIST_SLUGS], ["app/movies", "movie", MOVIE_LIST_SLUGS], + ["app/games", "game", GAME_LIST_SLUGS], ] as const)("%s", (dir, mediaType, slugs) => { it("has one static folder per registry slug, and no other", () => { expect(staticRouteFolders(dir)).toEqual([...slugs].sort()); @@ -31,6 +36,9 @@ describe.each([ const source = readFileSync(join(dir, slug, "page.tsx"), "utf8"); expect(source).toContain(`catalogList("${mediaType}", "${slug}")`); expect(source).toContain("