Skip to content

Commit bce4978

Browse files
committed
docs(website): add reference documentation
1 parent 27a5834 commit bce4978

4 files changed

Lines changed: 540 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: Configuration
3+
description: Every environment variable, configuration constant, and runtime flag in GamePulse.
4+
sidebar_position: 1
5+
---
6+
7+
# Configuration
8+
9+
All configuration is centralized in [`lib/config.ts`](https://github.com/TabletopFoundry/gamepulse/blob/main/lib/config.ts). The rest of the codebase imports typed constants — `process.env` is never read outside this file.
10+
11+
## Environment variables
12+
13+
| Variable | Default | Type | Used for |
14+
| --- | --- | --- | --- |
15+
| `NEXT_PUBLIC_BASE_URL` | `https://gamepulse.example.com` | string | Sitemap, OpenGraph, canonical URLs |
16+
| `NODE_ENV` | `development` | `"development" \| "production" \| "test"` | Standard Next.js modes |
17+
| `GAMEPULSE_ENABLE_PRODUCTION_RESEED` | unset | `"1"` to enable | Allows the seeder to wipe data in production |
18+
19+
Copy `.env.example` to `.env.local` and edit. `NEXT_PUBLIC_*` variables are baked into the client bundle at build time — change them by rebuilding, not at runtime.
20+
21+
## Compile-time constants
22+
23+
These live in `lib/config.ts`. Change them in code, not via environment:
24+
25+
| Constant | Default | Purpose |
26+
| --- | --- | --- |
27+
| `APP_NAME` | `"GamePulse"` | Branded name in metadata + UI chrome |
28+
| `APP_VERSION` | from `package.json` | Returned by `/api/health` |
29+
| `DEFAULT_REVALIDATE` | `60` (seconds) | Cache TTL for static-ish pages |
30+
| `LIST_TYPES` | `["watchlist", "wishlist"]` | Valid `list_type` values |
31+
| `MATCHED_CRITICS_CACHE_TAG` | `"matched-critics"` | Cache tag busted by mutation actions |
32+
| `MAX_REVIEW_LENGTH` | `280` | Community review character cap |
33+
| `MIN_REVIEW_LENGTH` | `10` | Minimum community review length |
34+
| `SIMILAR_GAMES_LIMIT` | `3` | "Similar games" rail size on game detail |
35+
| `FEED_PREVIEW_LIMIT` | `4` | Feed teasers on the home page |
36+
37+
## Rate limiting
38+
39+
`lib/rate-limit.ts` exposes a `rateLimit(actionName, perMinute)` helper used in every server action:
40+
41+
```ts
42+
const limit = await rateLimit("submitCommunityReview", 5);
43+
if (!limit.allowed) return { success: false, message: limit.message };
44+
```
45+
46+
Defaults per action:
47+
48+
| Action | Per-minute limit |
49+
| --- | --- |
50+
| `submitCommunityReview` | 5 |
51+
| `toggleUserList` | 30 |
52+
| `toggleFollowCritic` | 30 |
53+
| `subscribeNewsletter` | 3 |
54+
| `deleteNewsletterSignup` | 5 |
55+
56+
Keys are scoped to `actionName + client IP` (or a stable cookie when IP is unavailable).
57+
58+
## Caching
59+
60+
GamePulse uses three Next.js cache primitives:
61+
62+
| Primitive | Where | Purpose |
63+
| --- | --- | --- |
64+
| `React.cache()` | `lib/queries/games.ts`, others | Per-request memoization of pure queries |
65+
| `unstable_cache()` | `getCachedSearchOptions`, expensive aggregates | Cross-request memoization with TTL |
66+
| `revalidatePath`, `updateTag` | `lib/actions.ts` after every mutation | Bust caches when data changes |
67+
68+
The single named tag is `matched-critics` — any mutation that affects taste matching invalidates it so the next dashboard render recomputes.
69+
70+
## Database location
71+
72+
The SQLite file path is currently `data/gamepulse.db` relative to the working directory. The directory is created on first run.
73+
74+
To relocate the database (e.g. to a mounted volume), update `lib/db/connection.ts`:
75+
76+
```ts
77+
const dbPath = process.env.GAMEPULSE_DB_PATH ?? path.join(process.cwd(), "data", "gamepulse.db");
78+
```
79+
80+
…and set `GAMEPULSE_DB_PATH=/var/lib/gamepulse/gamepulse.db` in your environment. The default is intentionally simple to keep local dev zero-config.
81+
82+
## Mock user
83+
84+
Authentication is currently a stub: `getCurrentUser()` in `lib/queries/user.ts` returns the row with `community_users.is_current = 1`. Override by toggling the `is_current` flag in `lib/db/seeds/users.ts` and reseeding.
85+
86+
To plug in real auth, see [Data Model → Mock authentication](../concepts/data-model.md#mock-authentication).
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
title: Scoring API
3+
description: TypeScript reference for every function exported from lib/scoring.ts and lib/taste.ts.
4+
sidebar_position: 4
5+
---
6+
7+
# Scoring API
8+
9+
The full TypeScript reference for [`lib/scoring.ts`](https://github.com/TabletopFoundry/gamepulse/blob/main/lib/scoring.ts) and [`lib/taste.ts`](https://github.com/TabletopFoundry/gamepulse/blob/main/lib/taste.ts).
10+
11+
## Types
12+
13+
### `TasteDimension`
14+
15+
```ts
16+
export const TASTE_DIMENSIONS = [
17+
"strategy", "thematic", "party", "family", "solo", "conflict",
18+
] as const;
19+
export type TasteDimension = (typeof TASTE_DIMENSIONS)[number];
20+
```
21+
22+
### `TasteProfile`
23+
24+
```ts
25+
export type TasteProfile = Record<TasteDimension, number>;
26+
```
27+
28+
A 6-element vector. Each value is `0..100`.
29+
30+
### `ConsensusLabel`
31+
32+
```ts
33+
export const CONSENSUS_LABELS = [
34+
"Divisive",
35+
"Critically Acclaimed",
36+
"Community Favorite",
37+
"Hidden Gem",
38+
"On the Rise",
39+
] as const;
40+
export type ConsensusLabel = (typeof CONSENSUS_LABELS)[number];
41+
```
42+
43+
## Functions
44+
45+
### `clamp(value, min, max)`
46+
47+
Clamp a number into `[min, max]`.
48+
49+
```ts
50+
clamp(120, 0, 100); // → 100
51+
clamp(-5, 0, 100); // → 0
52+
```
53+
54+
### `average(numbers)`
55+
56+
Numeric mean. Returns `0` for an empty array.
57+
58+
```ts
59+
average([8, 9, 10]); // → 9
60+
average([]); // → 0
61+
```
62+
63+
### `cosineSimilarity(a, b)`
64+
65+
Cosine similarity between two taste profiles. Range: `[0, 1]` (assuming non-negative profiles).
66+
67+
```ts
68+
import { cosineSimilarity } from "@/lib/scoring";
69+
70+
const alex = { strategy: 90, thematic: 70, party: 30, family: 45, solo: 65, conflict: 55 };
71+
const tara = { strategy: 95, thematic: 60, party: 15, family: 30, solo: 70, conflict: 35 };
72+
73+
cosineSimilarity(alex, tara); // → ~0.97
74+
```
75+
76+
Used to score user × critic and game × game similarity.
77+
78+
### `pearson(left, right)`
79+
80+
Pearson correlation. Range: `[-1, 1]`. Returns `0` if either array has length `< 2` or zero variance.
81+
82+
```ts
83+
pearson([8, 9, 7, 6], [85, 92, 75, 60]); // → ~0.98 (perfectly correlated)
84+
```
85+
86+
Used to detect calibration agreement between a user's ratings and a critic's scores on the same games.
87+
88+
### `buildConsensus(criticsScore, communityScore, rising)`
89+
90+
Compute a consensus label.
91+
92+
```ts
93+
buildConsensus(90, 88, 70); // → "Critically Acclaimed"
94+
buildConsensus(88, 72, 50); // → "Divisive"
95+
buildConsensus(75, 86, 65); // → "Hidden Gem"
96+
buildConsensus(60, 90, 40); // → "Community Favorite"
97+
buildConsensus(70, 70, 40); // → "On the Rise"
98+
```
99+
100+
The function is pure and synchronous. Pass in already-computed 0–100 scores.
101+
102+
### `topGenres(profile)`
103+
104+
Return the top dimensions in a taste profile, sorted descending. Used to render "Likes strategy + thematic" badges on user/critic cards.
105+
106+
```ts
107+
topGenres({ strategy: 90, thematic: 72, party: 30, family: 45, solo: 65, conflict: 55 });
108+
// → ["strategy", "thematic", "solo"]
109+
```
110+
111+
### `getPersonalizedScore(gameId, matchedCritics, reviewsByGame?)`
112+
113+
Compute a 0–100 personalized score for `gameId` based on a user's top matched critics.
114+
115+
```ts
116+
import { getPersonalizedScore } from "@/lib/scoring";
117+
import { getMatchedCritics } from "@/lib/queries/user";
118+
119+
const matchedCritics = await getMatchedCritics();
120+
const score = getPersonalizedScore(brassBirminghamId, matchedCritics);
121+
// → 91
122+
```
123+
124+
Algorithm:
125+
126+
1. Collect each matched critic's score for `gameId` (if any).
127+
2. Weighted average — weight = match score (cosine × pearson blend).
128+
3. If fewer than 2 matched critics reviewed the game, fall back to the global `critics_score`.
129+
130+
The optional `reviewsByGame` parameter pre-fetches reviews in bulk for batch scoring (used on browse/dashboard pages).
131+
132+
### `batchFetchCriticReviews(gameIds)`
133+
134+
Fetch all critic reviews for a list of game IDs in a single SQL query. Returns a `Map<gameId, Array<{ critic_id, score }>>` for efficient batch personalized-scoring on listing pages.
135+
136+
```ts
137+
const reviewsByGame = batchFetchCriticReviews([1, 2, 3, 4, 5]);
138+
const matchedCritics = await getMatchedCritics();
139+
140+
for (const game of games) {
141+
game.personalizedScore = getPersonalizedScore(game.id, matchedCritics, reviewsByGame);
142+
}
143+
```
144+
145+
Use this whenever you compute personalized scores for more than one game at a time.
146+
147+
## Worked example
148+
149+
The dashboard's "Predicted for you" rail:
150+
151+
```ts
152+
// app/me/page.tsx (excerpt)
153+
import { getMatchedCritics } from "@/lib/queries/user";
154+
import { batchFetchCriticReviews, getPersonalizedScore } from "@/lib/scoring";
155+
import { getRecommendedGames } from "@/lib/queries/dashboard";
156+
157+
const matchedCritics = await getMatchedCritics();
158+
const recommendations = getRecommendedGames();
159+
const reviewsByGame = batchFetchCriticReviews(recommendations.map((g) => g.id));
160+
161+
const enriched = recommendations.map((game) => ({
162+
...game,
163+
predicted: getPersonalizedScore(game.id, matchedCritics, reviewsByGame),
164+
}));
165+
```
166+
167+
That's the whole personalization pipeline in 6 lines.

website/docs/reference/scripts.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: npm Scripts
3+
description: Every npm script defined in package.json — when to use it and what it actually does.
4+
sidebar_position: 2
5+
---
6+
7+
# npm Scripts
8+
9+
Every script defined in `package.json`, ordered by how often you'll use it.
10+
11+
## Development
12+
13+
### `npm run dev`
14+
15+
Starts the Next.js development server with hot reload on `http://localhost:3000`.
16+
17+
On first boot it:
18+
19+
1. Opens (or creates) `data/gamepulse.db`.
20+
2. Applies the schema.
21+
3. Runs the seed if `app_meta.seed_version` is behind `SEED_VERSION` or the database is empty.
22+
23+
```bash
24+
npm run dev
25+
```
26+
27+
### `npm run clean`
28+
29+
Removes `.next/` and the SQLite file (plus WAL/SHM sidecars). Use when:
30+
31+
- You changed seed data and want a fresh database.
32+
- The dev server is misbehaving and you want to nuke caches.
33+
34+
```bash
35+
npm run clean
36+
```
37+
38+
Then re-run `npm run dev`.
39+
40+
## Quality gates
41+
42+
### `npm run type-check`
43+
44+
Runs `tsc --noEmit`. Verifies TypeScript types without building.
45+
46+
```bash
47+
npm run type-check
48+
```
49+
50+
Fast (~2–4 seconds on a warm cache). Run this constantly while editing.
51+
52+
### `npm run lint`
53+
54+
Runs ESLint with the Next.js + project ruleset. Strict mode is on; no `any`, no unchecked indexed access.
55+
56+
```bash
57+
npm run lint
58+
```
59+
60+
### `npm run build`
61+
62+
Builds the production bundle. Runs through every page, statically analyzes routes, and emits `.next/`.
63+
64+
```bash
65+
npm run build
66+
```
67+
68+
### `npm run check`
69+
70+
The CI gate. Runs **type-check → lint → build** sequentially. All three must pass.
71+
72+
```bash
73+
npm run check
74+
```
75+
76+
Before every PR, run this. The GitHub Action runs the same command.
77+
78+
## Production
79+
80+
### `npm run start`
81+
82+
Serves the production build (you must run `npm run build` first).
83+
84+
```bash
85+
npm run build
86+
npm run start
87+
```
88+
89+
In Docker, the entrypoint runs both.
90+
91+
## When to use which
92+
93+
| You just… | Run this |
94+
| --- | --- |
95+
| Started working | `npm run dev` |
96+
| Changed seed data | `npm run clean && npm run dev` |
97+
| Made type changes | `npm run type-check` |
98+
| Are about to push | `npm run check` |
99+
| Deployed | `npm run build && npm run start` |
100+
| Got weird build output | `npm run clean && npm install && npm run dev` |
101+
102+
See [Troubleshooting](../troubleshooting.md) if any script fails.

0 commit comments

Comments
 (0)