# TypeScript Migration Plan — truf

Objective: migrate server and client from JavaScript to TypeScript for safer props, DB types, routes, and feed parsing.

## Phase 0: Prep (one-time)

- [x] 0.1 Add devDeps: `typescript` (root), `@types/node`/`@types/express`/`@types/express-session`/`@types/sanitize-html` (server), `@types/react`/`@types/react-dom` (client). Note: installed `typescript@7` (new native compiler), `@types/node` resolves 26.x.
- [x] 0.2 Create root `tsconfig.json` with `"references": [{"path": "client"}, {"path": "server"}]`.
- [x] 0.3 Create `client/tsconfig.json` (ES2022, React JSX, strict: false, noEmit, composite, allowJs + checkJs:false so existing .js/.jsx files are parsed but not type-checked until migrated).
- [x] 0.4 Create `server/tsconfig.json` (NodeNext, ES2022, strict: false, noEmit, composite, allowJs + checkJs:false).
- [x] 0.5 Add scripts to root `package.json`: `typecheck`: `tsc -b`; `lint:ts`: `eslint . --no-warn-ignored` (deviation: `--ext .ts,.tsx` was dropped because ESLint 9 flat config removed the `--ext` flag; parser/rules for .ts arrive in Phase 7); `build:ts`: `npm run typecheck`. Also added `*.tsbuildinfo` to `.gitignore`.

## Phase 1: Shared types (server + client)

- [x] 1.1 Create `server/src/types/db.ts` with `FeedRow`, `ItemRow`, `UserRow`, `SubscriptionRow`, `ReadStateRow` matching 001_init.sql (nullability mirrors SQLite columns).
- [x] 1.2 Create `server/src/types/feed.ts` with `ParsedFeed` (+ `RawFeedItem`, `FeedType`), `NormalizedItem` (+ `NormalizedFeed`), and `DedupKey` as `guid:${string} | hash:${string}`.
- [x] 1.3 Create `client/src/types/feed.ts` with UI/API DTOs: `User`, `Feed`, `Subscription`, `Item`, `ReadState`, `ItemsPage`, `UnreadCount` (camelCase, mirrors services/items.js).
- [x] 1.4 Added barrels `server/src/types/index.ts` and `client/src/types/index.ts` using `export type *`; verified all five files are in `tsc --listFilesOnly` and `npm run check` passes.

## Phase 2: Server repos (typed DB layer)

- [x] 2.1 Rename `server/src/db/repos/users.js` → `.ts`, import `UserRow`; type `getUserById`, `getUserByEmail`, `createUser`.
- [x] 2.2 Rename `feeds.ts`, `items.ts`, `subscriptions.ts`, `readState.ts` → `.ts`; import corresponding Row types; type all exported functions.
- [x] 2.3 Run `npm run typecheck`; fix any `any` leaks. Added `server/src/db/row.ts` (`asRow`/`asRows`) to cast `node:sqlite`'s generic row records at the DB boundary. Enabled `allowImportingTsExtensions` and changed value imports of renamed files to `.ts` so Node's native type stripping can resolve them at runtime.

## Phase 3: Server services (business logic)

- [x] 3.1 Rename `server/src/services/auth.ts`; type `registerUser`, `verifyUser`, `getUser` using `UserRow`.
- [x] 3.2 Rename `items.ts`; type `listUserItems` (params: `Cursor`, `Limit`, `FeedId`), `markItemRead`, etc.
- [x] 3.3 Rename `subscriptions.ts`; type `addSubscription`, `removeSubscription`.
- [x] 3.4 Rename `cursor.ts`; type `encodeCursor`, `decodeCursor`, `Cursor` interface.
- [x] 3.5 Rename `ingest.ts`; type `fetchAndStore` input/output; use `NormalizedItem` from types/feed.

## Phase 4: Server feeds pipeline

- [x] 4.1 Rename `server/src/feeds/fetcher.ts`; type `fetchFeed`, `FetchError`, response shape.
- [x] 4.2 Rename `parser.ts`; type `parseFeed`, `ParseError`, `ParsedFeed`.
- [x] 4.3 Rename `normalizer.ts`; type `normalizeFeed`, `sanitizeContent` (use `sanitize-html` types).
- [x] 4.4 Rename `discover.ts`; type `discoverFeedUrl`.
- [x] 4.5 Rename `ssrf.ts`; type `isPrivateIp`, `assertSafeUrl`, `SsrfError`.
- [x] 4.6 Rename `cli.ts`; light typing for CLI output.

## Phase 5: Server middleware, routes, scheduler

- [x] 5.1 `middleware/auth.ts`: extend `Request` with `session?.userId`.
- [x] 5.2 `middleware/csrf.ts`: type `ensureCsrf`, `requireCsrf`, `Request.csrfToken()`.
- [x] 5.3 `routes/index.ts`: type router, `/csrf` endpoint, mount sub-routers.
- [x] 5.4 `routes/auth.ts`: type `registerSchema`, `loginSchema` with zod; use `z.infer` for body types.
- [x] 5.5 `routes/subscriptions.ts`, `items.ts`, `stream.ts`: type params/query/body via zod; use repo/service types.
- [x] 5.6 `poller/scheduler.ts`: type `FeedDue`, `HostLimit`, `inFlight`, `pending`.
- [x] 5.7 `events.ts`: type `EventPayload = { feedId: number; items: ItemRow[] }`.
- [x] 5.8 `app.ts`: type `createApp` options (`config`, `db`, `guard`, `bus`).

## Phase 6: Server entry + tests

- [x] 6.1 Rename `server/src/index.ts`; type `config`, `db`, `poller`, `app`, shutdown handlers.
- [x] 6.2 Update server tests: rename `*.test.js` → `.ts`; add `ts-node/register` or compile-first step; verify `npm run test --workspace=server` passes.
- [x] 6.3 Run `npm run typecheck`; enable `strict: true` in `server/tsconfig.json` gradually (one file at a time).

## Phase 7: Client setup

- [x] 7.1 Ensure Vite handles `.tsx` (already works with `@vitejs/plugin-react`).
- [x] 7.2 Update `client/eslint.config.js` (or root) with `@typescript-eslint/parser`, `parserOptions.project: './client/tsconfig.json'`.
- [x] 7.3 Add `@typescript-eslint/recommended` rules; disable base `no-unused-vars`.

Note: typescript-eslint 8 does not support TypeScript 7. Root `typescript` is therefore aliased to
`@typescript/typescript6` for tooling, while `@typescript/native` aliases `typescript@7` so `tsc`
still runs the native compiler.

## Phase 8: Client components (leaf → root)

- [x] 8.1 Rename `client/src/components/ItemPreview.jsx` → `.tsx`; type `ItemPreviewProps { item: Item; onClose: () => void }`.
- [x] 8.2 Rename `SubscriptionManager.jsx` → `.tsx`; type props: `{ subscriptions: Subscription[]; onAdded: () => void; onRemoved: () => void }`.
- [x] 8.3 Rename `ItemList.jsx` → `.tsx`; type `ItemListProps { selectedFeed: number | null }`, internal `previewItem: Item | null`, mutation types.
- [x] 8.4 Rename `pages/Home.jsx` → `.tsx`; type `useAuth` return, `selectedFeed`, queries.
- [x] 8.5 Rename `pages/Login.jsx` → `.tsx`; type form state, `api` calls.
- [x] 8.6 Rename `auth.jsx` → `.tsx`; type `AuthContext` value (`user`, `loading`, `login`, `register`, `logout`, `load`).
- [x] 8.7 Rename `api.js` → `.ts`; add generic `api<T>(path, opts)`; type CSRF token cache, error class.
- [x] 8.8 Rename `App.jsx` → `.tsx`; type route elements.
- [x] 8.9 Rename `main.jsx` → `.tsx` (trivial).

## Phase 9: Client tests + strict mode

- [x] 9.1 Update `ItemList.test.jsx` → `.tsx`; add explicit types where helpful.
- [x] 9.2 Run `npm run test --workspace=client`; fix any type errors.
- [x] 9.3 Enable `strict: true` in `client/tsconfig.json` gradually; fix errors file-by-file.

## Phase 10: Docs & polish

- [x] 10.1 Update `README.md` with TypeScript commands (`npm run typecheck`, `npm run lint`, `npm run build`).
- [x] 10.2 Update `docs/PROGRESS.md` with TypeScript milestone.
- [x] 10.3 Run full `npm run check` (lint + format + test) on both workspaces.
- [x] 10.4 Optional: bump `strict: true` in root tsconfig references.

---

## Suggested execution order

Work top-to-bottom. Each `[ ]` item is a small commit. After every 2–3 items run `npm run typecheck` (or `tsc -b`) and the affected test suite. This keeps the migration always-green.

## Quick reference: files by directory

```
server/src/
  types/         ← Phase 1
  db/repos/      ← Phase 2
  services/      ← Phase 3
  feeds/         ← Phase 4
  middleware/    ← Phase 5.1–5.2
  routes/        ← Phase 5.3–5.5
  poller/        ← Phase 5.6
  events.ts      ← Phase 5.7
  app.ts         ← Phase 5.8
  index.ts       ← Phase 6.1

client/src/
  types/         ← Phase 1.3
  components/    ← Phase 8.1–8.3
  pages/         ← Phase 8.4–8.5
  auth.tsx       ← Phase 8.6
  api.ts         ← Phase 8.7
  App.tsx        ← Phase 8.8
  main.tsx       ← Phase 8.9
```
