diff --git a/MIGRATION_BASELINE.md b/MIGRATION_BASELINE.md
index 79bbb1e..a209714 100644
--- a/MIGRATION_BASELINE.md
+++ b/MIGRATION_BASELINE.md
@@ -139,3 +139,71 @@ node scripts/owliver-baseline.mjs
npm run build
git diff --stat -M
```
+
+---
+
+## 2026-09-20 — closing addendum: the migration is complete
+
+Everything above this line is the record as measured on 2026-09-11 and is left
+exactly as it was written. The numbers in it describe `main` before the first
+rename; they are the thing the migration was checked against, so correcting them
+to today's figures would destroy the comparison rather than update it. What
+follows is the other end of that comparison.
+
+### Final state, measured on the `ts-migration` branch
+
+| Check | Command | 2026-09-11 baseline | Now |
+|---|---|---|---|
+| Types | `npm run typecheck` | FAILS — 71 errors / 27 files | **0 errors**, 321 files in the program |
+| Lint | `npm run lint` | PASSES, exit 0 | PASSES, exit 0 |
+| Lint (with warnings) | `npx eslint .` | 0 errors, 289 warnings | 0 errors, 289 warnings |
+| Behaviour | `npm test` | 1641 / 1642 | **1693 / 1693** |
+| Build | `npm run build` | succeeds | succeeds |
+| Bundle | `dist/assets/index-*.js` | — | `74d17e2d5cafdd6f88eaf6d89ffdab11` |
+| Owliver, standalone | `node scripts/owliver-baseline.mjs` | FAILS — pre-existing | **matches the baseline** |
+| Backend fixture | `npm run seed:check` | FAILS — stale | **in step** |
+
+No `.js` or `.jsx` remains under `src/`: 103 `.ts`, 216 `.tsx` and 2 `.d.ts`.
+`src/agents/**/*.md` and `src/skills/**/*.md` are untouched, as promised.
+
+### The three items this document left open, and where they were closed
+
+**The standalone Owliver script.** Recorded above as failing before any of this
+began, verified by running it on a clean `main`. The byte-exact comparison was
+reporting one real difference: the baseline held 23 skills and the runtime
+resolved 24, because `create-employee-role` was added after the baseline was
+last written and nobody recaptured it. `npm test` tolerated it — its check
+asserts only that nothing was *removed*, and read "23 kept, 1 added since" — so
+the drift was visible only to the stricter script. Refreshed in `34505d7`:
+three inserted lines, no deletions, routes byte-identical at 18 and all eleven
+contexts unchanged. The check now reads "24 kept, 0 added since".
+
+**The backend seed fixture.** Recorded above as predating the migration and
+deliberately left alone. It stayed that way for good reason: the generator was a
+version behind the seeder and emitted no `users` key, so regenerating would have
+dropped `employer@krow.app` from every fresh seed and left nobody to sign in as
+to reach the employer console. Fixed in `3ddacf2` by teaching the generator to
+emit the key, not by overwriting the fixture — with that change the generator
+reproduces the committed fixture byte for byte, so `seed.json` in the sibling
+repository was never written to at all.
+
+**The HTML render baselines.** Recorded above as read-only for the duration of
+the migration, and that held: they were untouched through every phase. Three of
+them — Hired History, Analytics and Candidates — were recaptured in `878a235`,
+after the migration, because a parallel feature removed demo data those files had
+captured while it was still in effect. They are renamed `*.render.html`, since a
+file called `pre-migration` holding post-feature markup is a lie in the filename.
+The other five still hold genuine pre-migration markup, still pass, and keep the
+name. One check that could no longer be satisfied was replaced by three that read
+the render directly; `scripts/__baseline__/README.md` has the detail.
+
+### Commits
+
+ 3ddacf2 fix(seed): emit the users array the backend seeder reads
+ 878a235 test(baseline): recapture the three HTML baselines that held invented people
+ 34505d7 chore: refresh owliver baseline
+
+Strictness remains as debt rather than a blocker: `strict: true` reports 4438
+errors, `noImplicitAny` 3317 and `strictNullChecks` 1794, while the committed
+configuration reports none. None of it affects emitted output — the production
+bundle hash is unchanged — so it is hardening work, not migration work.
diff --git a/README.md b/README.md
index 0b8039b..693bcb5 100644
--- a/README.md
+++ b/README.md
@@ -19,22 +19,22 @@ Recharts + MUI X Charts · react-hot-toast.
```
src/
- main.jsx entry — mounts , imports index.css
- App.jsx providers + the entire route table
+ main.tsx entry — mounts , imports index.css
+ App.tsx providers + the entire route table
index.css design tokens (HSL CSS variables) + utility layers
api/ the backend seam — see below
- base44Client.js the contract the production app talks to
+ base44Client.ts the contract the production app talks to
store.js in-memory entity store, mirrored to localStorage
- aiEngine.js local, deterministic InvokeLLM
- seed.js demo dataset
- attendanceSeed.js shift/attendance dataset
+ aiEngine.ts local, deterministic InvokeLLM
+ seed.ts demo dataset
+ attendanceSeed.ts shift/attendance dataset
lib/ domain logic and data hooks
- krowHooks.js React Query hooks over the entity store
- krowAi.js AI workflows
- krowScore.js score engine
- AuthContext.jsx auth provider
+ krowHooks.ts React Query hooks over the entity store
+ krowAi.ts AI workflows
+ krowScore.ts score engine
+ AuthContext.tsx auth provider
admin/ admin session, permissions, position insights
skills/ the Owliver skill system (registry, resolver, flows)
agents/ the agent system (registry, runtime, lifecycle)
@@ -79,37 +79,37 @@ The app opens on **`/admin/login`**. Everything else lives under `/admin/*` behi
| `/admin/workspace/skills`, `skills/new`, `skills/:id` | UI skill list and editor |
| `/admin/workspace/skills/owliver/new`, `owliver/:id` | Owliver skill editor |
| `/admin/workspace/skill-development` | Skill Development |
-| `*` | `lib/PageNotFound.jsx` |
+| `*` | `lib/PageNotFound.tsx` |
Legacy top-level paths (`/overview`, `/positions`, `/candidates`, `/hired`, `/talent-pool`,
`/university`, `/analytics`, `/activity`, `/tracking`) all `Navigate` into their `/admin/*`
equivalent, id preserved. Static route segments are declared before dynamic ones so
`workspace/agents/new` cannot be read as an agent whose id is `"new"`.
-> **Note.** `App.jsx` still imports a set of pre-redesign Employer/Talent pages
+> **Note.** `App.tsx` still imports a set of pre-redesign Employer/Talent pages
> (`Overview`, `Apply`, `WorkerProfile`, `KrowIdentity`, `EmployeeDashboard`, `DesignSystem`, …)
-> and `layouts/Layout.jsx`. None of them are mounted on a route — they are unreachable at runtime
+> and `layouts/Layout.tsx`. None of them are mounted on a route — they are unreachable at runtime
> and retained pending a decision on whether to re-route or remove them.
## The backend seam
-The production app talks to a Base44 backend through `src/api/base44Client.js`. This demo keeps that
+The production app talks to a Base44 backend through `src/api/base44Client.ts`. This demo keeps that
module path, export name and full method contract, and swaps only the transport:
| Contract | Demo implementation |
| --- | --- |
| `base44.entities..list/filter/get/create/update/delete` | `api/store.js` — in-memory records, mirrored to `localStorage` |
-| `base44.integrations.Core.InvokeLLM` | `api/aiEngine.js` — local, deterministic |
+| `base44.integrations.Core.InvokeLLM` | `api/aiEngine.ts` — local, deterministic |
| `base44.integrations.Core.UploadFile` | blob URLs, session-scoped |
| `base44.auth.*` | seeded employer/admin user, always signed in |
-Because the seam did not move, everything above it — `lib/krowHooks.js`, `lib/krowAi.js`,
-`lib/krowScore.js`, every page and component — is unchanged from the reference implementation.
+Because the seam did not move, everything above it — `lib/krowHooks.ts`, `lib/krowAi.ts`,
+`lib/krowScore.ts`, every page and component — is unchanged from the reference implementation.
## The AI engine
Every AI workflow funnels through one call, `InvokeLLM({ prompt, response_json_schema })`.
-`api/aiEngine.js` reimplements it locally: it recognises each workflow by the phrase its prompt opens
+`api/aiEngine.ts` reimplements it locally: it recognises each workflow by the phrase its prompt opens
with, reads the structured fields the prompt already carries (`Years Experience: 6`,
`Required Certifications: …`, the talent-pool JSON block) and scores them deterministically.
@@ -124,8 +124,8 @@ Two registries discover their definitions from markdown at build time:
| Registry | Glob | Files |
| --- | --- | --- |
-| `lib/skills/registry.js` | `import.meta.glob('/src/skills/**/*.md')` | 18 Owliver skills, 5 workforce training skills |
-| `lib/agents/registry.js` | `import.meta.glob('/src/agents/**/*.md')` | 9 agent definitions |
+| `lib/skills/registry.ts` | `import.meta.glob('/src/skills/**/*.md')` | 19 Owliver skills, 5 workforce training skills |
+| `lib/agents/registry.ts` | `import.meta.glob('/src/agents/**/*.md')` | 9 agent definitions |
**These globs are absolute paths.** Moving or renaming `src/skills/` or `src/agents/` makes the
registry silently return nothing — no build error, no import failure, just an empty registry. Add
@@ -133,9 +133,9 @@ definitions by dropping a new `.md` file into the right folder; nothing else nee
## Demo data
-Seeded in `src/api/seed.js` — positions, applicants, AI-scored candidates, interviews, hires,
+Seeded in `src/api/seed.ts` — positions, applicants, AI-scored candidates, interviews, hires,
talent-pool profiles, Proving Ground courses and an activity log; shift data in
-`src/api/attendanceSeed.js`.
+`src/api/attendanceSeed.ts`.
Edits persist to `localStorage`. To restore the shipped dataset:
@@ -163,12 +163,12 @@ Tokens live in `src/index.css` as HSL CSS variables consumed by `tailwind.config
| Body | Inter |
`src/components/ds/` is the vocabulary the app actually writes in — `Surface`, `KpiCard`,
-`DataTable`, `PageHeader`, `Modal`, `toast` and the rest, exported through `ds/index.js`.
+`DataTable`, `PageHeader`, `Modal`, `toast` and the rest, exported through `ds/index.ts`.
`src/components/ui/` holds the shadcn primitives those are built on. Surfaces use `glass` and
`glass-card`; the page background is the `gradient-mesh` utility.
Toasts go through `ds/toast` (a wrapper over `react-hot-toast`), rendered by `` in
-`App.jsx`.
+`App.tsx`.
## Scripts
@@ -179,7 +179,7 @@ Toasts go through `ds/toast` (a wrapper over `react-hot-toast`), rendered by `