# AGENTS.md

RSS/Atom/JSON feed aggregator. npm workspaces: `server` (Express 5 + built-in `node:sqlite`) and `client` (React 19 + Vite 6). Requires Node 24.

## Setup

- `npm install` at the repo root installs both workspaces.
- `cp .env.example .env`, then set `SESSION_SECRET` to at least 16 chars. Config is zod-validated at startup and the server exits if invalid.
- `npm run dev --workspace=server` loads `../.env` via `--env-file`; run it from the repo root, not from `server/`.

## Commands

- Dev: `npm run dev --workspace=server` (port 3000) and `npm run dev --workspace=client` (5173, proxies `/api` to 3000).
- `npm run check` = ESLint + Prettier check + both test suites. It does **not** run typecheck.
- `npm run typecheck` separately (`tsc -b` across both workspaces).
- Single server test: `npm run test --workspace=server -- test/parser.test.ts`.
- Single client test: `npm run test --workspace=client -- src/components/ItemList.test.tsx`.
- Format: `npm run format`.

## Architecture

- `server/src/index.ts` wires config -> SQLite -> migrations -> poller -> Express. Everything is one process: in-process polling and an in-process EventEmitter bus for SSE; no Redis/queue.
- `server/src/db/migrations/*.sql` auto-apply in filename order at startup. Add new migration files; never edit an applied one.
- Feeds/items are stored once and shared; dedup is `UNIQUE(feed_id, dedup_key)`. Read state is per user in `read_state`. Items older than 90 days are deleted at startup/daily.
- Production Express serves `client/dist` if present. Docker builds only the client, then runs `node server/src/index.ts` (Node 24+ runs TS directly via type stripping).
- CSRF: state-changing `/api` requests need `X-CSRF-Token` from `GET /api/csrf` (`client/src/api.ts` handles it). `/api/auth/*` is mounted before CSRF middleware; other routes require it.
- Session cookie `secure: 'auto'` is intentional so plain-HTTP Docker deployments work; do not force it to `true`.

## TypeScript (complete)

- Server code and tests are `.ts`; client components are `.tsx` (migration complete).
- tsconfigs use `allowJs`, `checkJs: false`, `noEmit`; `npm run typecheck` builds references but emits nothing.
- ESLint targets both server and client `.ts`/`.tsx` with type-aware typescript-eslint rules.
- typescript-eslint does not support TS 7: root `typescript` is aliased to `@typescript/typescript6` for tooling, and `@typescript/native` aliases `typescript@7` so `tsc` stays native.
- Node runs `.ts` directly via type stripping, so value imports of `.ts` files must use the `.ts` extension (`allowImportingTsExtensions` is enabled). Type-only imports may keep `.js`.

## Gotchas

- All relative imports use explicit extensions: `.js` for JS files, `.ts` for TS files. Do not omit them.
- SQLite files under `data/` and `server/data/` are tracked. Running the dev server dirties them, so avoid committing unrelated DB changes.
- Tests use in-memory SQLite (`openDatabase(':memory:')`) and local HTTP fixture servers; no external services required.

## Docs

`docs/DESIGN.md` (data model/polling), `docs/PROGRESS.md` (milestone status), `docs/TypeScript_Migration_Plan.md` (migration phases).
