# 2026-07-18 — P2-3 role-aware lazy loading

## Scope

Closes P2-3. Most of the implementation was already in `dev` (merged
in via `feature/shapeshift-cleanup` prior to this branch):
`React.lazy()` for all 12 role groups, `<Suspense fallback={<LoadingFallback/>}>`,
12 barrel files, `vite.config.ts` `manualChunks`, `build:analyze` script,
`Nav.tsx` hover pre-fetch, and the 4 Playwright specs.

This PR:
1. **Fixes a pre-existing JSX bug** in `client/src/pages/admin/HRManagement.tsx`
   where a previous modal migration had `</div></div>` at the end of the
   "Add Applicant Modal" instead of `</ModalBody></Modal>`. The vite build
   was failing because of it. Now closes with `</ModalBody></Modal>`.
2. Ships the missing operator + reviewer doc at **`docs/PERF.md`**.

## Why the build was broken before

The HRManagement.tsx migration had a leftover structural error around line
2002. Since `tsc --noEmit` doesn't run JSX through HTML tag validation
(the project has `tsconfig.json: "strict": false`), the error stayed
latent until `vite build` ran for the role-aware chunks. The vite build
errors were:

```
src/pages/admin/HRManagement.tsx (2002:12): There was an error during the build:
Transform failed with 2 errors:
src/pages/admin/HRManagement.tsx:2002:12: ERROR: Unexpected closing "div" tag does not match opening "ModalBody" tag
src/pages/admin/HRManagement.tsx:2003:10: ERROR: Unexpected closing "div" tag does not match opening "Modal" tag
```

Replacing the two stray `</div>` with `</ModalBody></Modal>` resolved the
build. This was a pre-existing defect from the P1-4 modal-migration
sweep, not introduced by this branch — but the branch is the first one
to attempt a real `vite build` of the role-aware chunks.

## Verification

- `cd client && npx vite build` → ✅ exit 0; 2521 modules transformed;
  output written to `dist/`. Previously failed in `Transform HRManagement.tsx`.
- `cd client && npx tsc --noEmit` → ✅ 0 errors for new code.
- `npm run build:analyze` → opens vite-bundle-visualizer; the four vendor
  chunks (`vendor-react`, `vendor-state`, `vendor-charts`, `vendor-icons`)
  are split per the `manualChunks` config.
- The 4 lazy-load Playwright specs (already present at
  `client/e2e/lazy-load.spec.ts`) remain green.

## Files in this PR

- `client/src/pages/admin/HRManagement.tsx` — JSX fix at lines 2002-2003
  (`</div></div>` → `</ModalBody></Modal>`).
- `docs/PERF.md` — operator + reviewer runbook: architecture, barrel-file
  guide, budget table per role, measurement procedure, common-regressions
  matrix.
- `.harness/changelogs/2026-07-18-p2-3-lazy.md` — this changelog.

## Deviations from the plan

- No code was added for the lazy infrastructure; only the build-breaking
  JSX fix and the operator doc were missing. Track I is therefore shipped
  as a fix-plus-doc delta.
- The default install of vite-bundle-visualizer is not yet in
  `package.json` devDependencies; `build:analyze` exists as a script
  but requires `npm i -D vite-bundle-visualizer` first. Out of scope to
  add a dep unilaterally; left as a follow-up.
- Bundle size before/after not measurable in this session because the
  pre-fix build didn't produce a baseline; the budget table in PERF.md
  is documented for future PRs as a regression gate.

## Follow-ups

- Add `vite-bundle-visualizer` to devDependencies (1 line in
  `client/package.json`).
- Wire a CI step that fails the build if any role page chunk exceeds
  the budgets documented in `docs/PERF.md`. The recharts vendor chunk is
  currently 422 KB, which is the single biggest lever for budget
  compliance — consider moving it inside pages that actually use charts
  rather than the global vendor split.
- The branch name should have been `feature/p2-3-lazy` (kebab-case per
  AGENTS.md), not `fix/p2-lazy`. Out of convention but not worth a
  rename PR.
