Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,41 @@

## Overview

WordWise is a reflective word-of-the-day practice app. Built with Node.js + Express.
WordWise is a reflective word-of-the-day practice app. Node.js + Express, deployed on Vercel behind Cloudflare.

## Rules

1. Dormant project. Do not make significant changes without explicit request.
2. Node.js/Express backend with serverless deployment.
3. Branch: \`feat/*\`, \`fix/*\`, \`docs/*\`, \`refactor/*\`, \`chore/*\`.
1. Active project (not dormant — daily batch system implemented July 2026).
2. Branching: `feat/*`, `fix/*`, `docs/*`, `refactor/*`, `chore/*`.
3. `main` branch is protected — requires PR with 1 approval.
4. Always test locally with `npm start` before deploying.

## Architecture summary

- **Daily batch**: 50 words selected via date-seeded PRNG (Mulberry32). Deterministic — same date always yields same pool.
- **Word serving**: `GET /api/word?slot=N` returns word at that slot position in today's pool. Client stores `nextSlot` in `localStorage`.
- **Media**: Pexels API fetches nature landscapes (photos 50%, videos 50%). Pre-fetched in background batch on first request of the day.
- **Cache**: `defCache` (word definitions, seeded from `words.json`), `mediaCache` (Pexels URLs). In-memory, per-instance.
- **Fallback**: Words not found in Dictionary API get a random curated word's definition from `words.json`.

## Key technical details

- Seeded PRNG: `seededRandom(dateString)` produces reproducible shuffle of the 20K+ word list, taking first 50.
- Slot wrapping: `(slot + 1) % 50` — after 49th word, wraps back to 0.
- Background pre-fetch: `preFetchBatchMedia()` iterates through all 50 words, fetching one Pexels URL each. Runs once per day per instance.
- Timeouts: Pexels fetch has 3s timeout, Dictionary API has 4s timeout.
- Curated fallback: `words.json` has 31 pre-defined words — enough to seed initial cache. For Dictionary misses, falls back gracefully with just a definition string (no mismatched phonetic).

## Deployment

```bash
vercel --prod --scope sparsh-sams-projects
```

Vercel project is linked — just run from repo root. Auto-deploys on push to `main`.

## Domains & DNS

- **App**: wordwise.kovina.org
- **DNS**: Cloudflare, CNAME proxied → wordwisehiccups.vercel.app
- **CF token**: `$CLOUDFLARE_API_TOKEN` in `.zshrc` (Zone:DNS:Edit scope)
56 changes: 43 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,61 @@

## Overview

Reflective word-of-the-day practice. Node.js + Express.
Reflective word-of-the-day practice. Node.js + Express.
Production: https://wordwise.kovina.org (Vercel)

## Commands

npm start # Run server locally
vercel --prod # Deploy to production
```bash
npm start # Run server locally (port 3000)
vercel --prod # Deploy to Vercel production
git push origin main # Auto-deploys via Vercel Git integration
```

## Architecture

- Daily batch system: 50 unique words per day, date-seeded deterministically
- Slot tracking: Each visitor progresses through the pool via localStorage
- Media pre-fetch: First request triggers async batch fetch of Pexels media
- Cache layers: In-memory defCache (Dictionary API) + mediaCache (Pexels)
- No external DB: Deterministic from date seed + in-memory caches
- **Daily batch system**: 50 unique words per day, date-seeded deterministically (Mulberry32 PRNG on `YYYY-MM-DD` seed)
- **Slot tracking**: Each visitor progresses through the pool via `localStorage` (`wordwise_slot` + `wordwise_day`)
- **Media pre-fetch**: First request of the day triggers async background fetch of 50 Pexels media URLs (50:50 video/photo)
- **Cache layers**: In-memory `defCache` (Dictionary API results) + `mediaCache` (Pexels URLs) — per serverless instance
- **No external DB**: Deterministic from date seed + in-memory caches

## Key files

| File | Purpose |
|------|---------|
| routes/word.js | Daily batch, word serving, media pre-fetch |
| views/index.html | Client with timezone bar, word display, media |
| words.json | Curated word pool (fallback definitions) |
| words.txt | 20K+ word pool for daily selection |
| `routes/word.js` | Daily batch generation, word serving, media pre-fetch |
| `views/index.html` | Client with timezone bar, word display, background media |
| `words.json` | 31 curated words with definitions (fallback for rare words) |
| `words.txt` | 20K+ English word pool for daily selection |

## API

### GET /api/word?slot=N

Returns the Nth word from today's deterministic 50-word batch.

Response: `{ word, phonetic, definition, example, partOfSpeech, audioUrl, background?, backgroundType?, photographer?, photoUrl?, nextSlot }`

- `slot` defaults to 0 if omitted/invalid; wraps at 49 → nextSlot: 0
- Media comes from pre-fetched Pexels cache or fetched on-demand

## Deployment

Custom domain on Vercel behind Cloudflare proxy.
- **Domain**: wordwise.kovina.org (Cloudflare proxied → Vercel)
- **Vercel project**: `wordwise` under `sparsh-sams-projects` team
- **Project ID**: `prj_ytl2Hm76xJYVrolTa8cnhsjBJAwa`
- **DNS**: CNAME `wordwise` → `wordwisehiccups.vercel.app` (proxied, Cloudflare)
- **Cloudflare zone**: kovina.org (NS: steven.ns.cloudflare.com / roxy.ns.cloudflare.com)
- Domain added via: `vercel domain add wordwise.kovina.org wordwise --scope sparsh-sams-projects`

## Branch protection

`main` requires 1 approving review, dismisses stale reviews, enforces for admins. No force pushes or deletions.

## Environment variables

| Variable | Source | Purpose |
|----------|--------|---------|
| `PEXELS_API_KEY` | Hardcoded fallback in code | Pexels API for background media |
| `CLOUDFLARE_API_TOKEN` | `~/.zshrc` | Cloudflare DNS management |