12 KiB
Phase 2 + Phase 3 Rollout Plan — 2026-07-22
Date: 2026-07-22 Owner: fchin (orchestrator) — Arthur owns Phase 2 implementation, Craig owns Phase 3 implementation Source plans: phase2-analytics · phase3-offboarding Decision (this doc): salvage + rebase for Phase 2; both phases wait for Phase 1 to land before starting
1. State of play (today)
| Branch | Worktree | State |
|---|---|---|
dev (f50e272) |
main checkout | Uncommitted modifications in App.tsx, Nav.tsx, Reports.tsx, docker-compose.yml, Dockerfile, docker-entrypoint.sh, reports.controller.js, demo.js seed. Dirty — leave alone. |
feature/admin-cohorts-2026-07-22 |
.worktrees/feat-auto-20260722-ed7cf80c/ |
Phase 1 in flight. user_roles PR committed (changelog + migration + controller + components + e2e spec). cohorts PR partially staged (uncommitted: cohorts.controller.js, cohorts migration, SyncEngine.js, index.js, plus dbg12.js / seed_smoke.js debug noise to drop). Class/teacher assignment UX not started. |
feature/phase2-analytics (5eb6729) |
.worktrees/phase2-analytics/ |
Phase 2 donor branch. Branched from 4c0ab8b (older dev, pre-superadmin). 2 commits of analytics work: fb80a73 KPI rebuild + 5eb6729 100%-real-SQLite follow-up. Result: 812-line Reports.tsx, 548-line reports.controller.js. Predates the Phase 1 schema and the new plan doc; missing the principal/bursar/hr role-scoped access from the plan doc. |
| (none) | (none) | Phase 3 has no worktree yet. |
Solo-dev rule (AGENTS.md): "only one worktree at a time per developer." Phase 2 + Phase 3 are file-disjoint but cannot run truly in parallel on this machine. The work plan below sequences Phase 2 first (smaller, closer to done) then Phase 3.
2. Sequencing
dev (current, dirty)
│
├─► finish + merge Phase 1 → feature/admin-cohorts-2026-07-22 into dev
│
├─► Phase 2 worktree from clean dev → 3 PRs into dev
│ (PR1 backend endpoints, PR2 frontend rewrite, PR3 routing fix + e2e)
│
├─► Phase 3 worktree from clean dev → 4 PRs into dev
│ (PR1 migration + service + unit tests, PR2 controller,
│ PR3 frontend pages, PR4 offboard buttons + e2e)
│
└─► promote dev → main (only after both phases green + Phase 1 green)
Why this order: Phase 1's user_roles and student_cohorts tables are referenced by Phase 2 (/api/reports/cohorts, principal reads via the new hasRole helper) and Phase 3 (OffboardingService calls the user_roles revoke logic). Phase 2 is smaller and reuses existing reports.controller.js; Phase 3 introduces 2 new tables and the transaction-critical OffboardingService. Phase 2 first gives Arthur a quick win and surfaces any App.tsx/Nav.tsx merge issues before Phase 3 needs to merge into the same files.
3. Phase 2 — worktree: .worktrees/analytics-dashboard-2026-07-22/
Branch: feature/analytics-dashboard-2026-07-22 (renamed from feature/phase2-analytics for plan-doc alignment)
Setup (after Phase 1 lands in dev):
cd .worktrees/phase2-analytics
git remote prune origin
git branch -m feature/phase2-analytics feature/analytics-dashboard-2026-07-22
git fetch origin
git merge origin/dev # not rebase — preserves the 2 donor commits on top
# resolve conflicts in: client/src/App.tsx, client/src/components/Nav.tsx,
# client/src/store/auth.ts, server/src/database/seeds/demo.js
git worktree move .worktrees/phase2-analytics .worktrees/analytics-dashboard-2026-07-22
Work to complete (gap-fill against the plan doc):
| Plan-doc item | Status in donor branch | Action |
|---|---|---|
/api/reports/kpis endpoint |
Likely already present (large reports.controller.js) |
Verify against the documented shape; add role-scoped filtering if missing |
/api/reports/timeseries |
Likely already present | Verify metric/range/granularity query handling; add if missing |
/api/reports/cohorts |
Probably gated by Phase 1's student_cohorts; verify not 500ing |
Add/fix; tie to Phase 1's cohort schema |
/api/reports/export generalization |
Existing GET /api/reports/export/:type per the conventions doc |
Generalize to ?type= query param, stream CSV, support 4 new types |
KpiTile / DrilldownModal / FilterBar components |
Probably inline in 812-line Reports.tsx |
Extract as separate components per the plan doc |
| 5 tabs (Overview/Academics/Cohorts/Finance/Staff) with role gating | TBD — verify | Add missing tabs; wire role visibility to hasRole from Phase 1 |
| CSV export button | Probably exists | Verify; ensure dropdown covers all 4 types |
principal/bursar/hr in every getRoutes() arm of App.tsx for /reports |
Missing — primary gap | Add principal to every arm; add bursar/hr to arms where role should see the route |
| Cohorts tab conditional on Phase 1 | If Phase 1 ships first, no flag needed | If Phase 1 is delayed, gate with featureFlagCohorts query param |
| Changelog | Missing | Write .harness/changelogs/2026-07-22-analytics-dashboard.md |
Verification bar (cross-walked from plan doc §Verification):
git diff --stat server/src/database/is empty for this PR (no new tables).GET /api/reports/kpis?range=6mreturns documented shape with non-zero seed values.GET /api/reports/timeseriesreturns 6+ points for 6m, 26+ for 6m+week.GET /api/reports/cohortsreturns rows from the Phase 1 schema (or[]if Phase 1's cohorts are empty on the seed).GET /api/reports/export?type=...&format=csvdownloads a non-empty CSV for each of Overview/Academics/Cohorts/Finance.- Role gating verified:
school_admin/principalsee all 5 tabs,bursarsees 4 (no Staff),hrsees 4 (no Finance),student/parent/teacherget 403. - Time-range filter changes data (set 7d vs 6m).
- Cohort drilldown opens modal with ranked students; Esc closes.
- Every KPI query + time-series query <500ms; EXPLAIN QUERY PLAN checked on seed.
- E2E spec: load
/reportsasschool_admin, all 5 tabs render, range switch, drilldown, CSV export. Existing portal-smoke tests pass. - No regression on
admin/principal dashboards, exam-review, finance module.
Execution order (this plan only):
PR 1: backend endpoints (kpis, timeseries, cohorts, export) — gap-fill on reports.controller.js
PR 2: frontend — extract KpiTile/DrilldownModal/FilterBar; complete 5 tabs; role gating
PR 3: App.tsx routing fix (principal/bursar/hr for /reports) + E2E spec + changelog
Merge into dev: after PR 3 is green and the verification bar is met.
4. Phase 3 — worktree: .worktrees/offboarding-2026-07-22/
Branch: feature/offboarding-2026-07-22 (new branch off post-Phase-1 dev)
Setup (after Phase 2 lands in dev):
cd /c/Users/fchin/Documents/next-gen
git fetch origin
git worktree add .worktrees/offboarding-2026-07-22 -b feature/offboarding-2026-07-22 origin/dev
cd .worktrees/offboarding-2026-07-22
Work to complete (per the plan doc, with concrete PR sequencing):
PR 1 — schema + service (foundation, hardest part):
server/src/database/migrations/knex/2026072200000030_offboarding.js—offboarding_records,offboarding_actions,ALTER TABLE users ADD COLUMN archive_status.server/src/services/OffboardingService.js—offboardStudent()+offboardStaff()pipelines. Both wrap indb.transaction(). Reuse Phase 1'suser_roles.revoked_atrevoke logic.- Local node script
server/test_offboarding_service.jsexercising the happy path + rollback-on-step-failure path. Deleted before merge.
PR 2 — controller + sync + routing:
server/src/controllers/offboarding.controller.js— all 7 endpoints from the plan doc.server/src/index.js— register the controller.server/src/services/SyncEngine.js— appendoffboarding_records+offboarding_actionstotablesToSync(parents first:users→enrollments→offboarding_records→offboarding_actions).
PR 3 — frontend pages + components:
client/src/store/offboarding.ts— Zustand slice; usesclient/src/store/api.tsaxios instance.client/src/pages/admin/Offboarding.tsx— list view with status/cohort/date filters.client/src/pages/admin/OffboardingWizard.tsx— 4-step wizard.client/src/pages/admin/Alumni.tsx— read-only directory.client/src/components/OffboardingStepRow.tsx,OverrideModal.tsx.- Routes + Nav entries in
client/src/App.tsx,client/src/components/Nav.tsx.
PR 4 — wire-up + e2e + changelog:
- "Offboard" buttons on
Users.tsx(student detail),HRManagement.tsx,StaffDirectory.tsx. - E2E spec (
client/e2e/offboarding.spec.ts) covering: student happy path, student fee block + override, staff happy path, cancel. .harness/changelogs/2026-07-22-offboarding.md.
Verification bar (cross-walked from plan doc §Verification):
- Migration up + down clean on both fresh DB and existing DB.
- Student happy path:
users.is_active=0,archive_status='active_alumni', all 5 enrollment/hostel/transport/club state mutations land, 11offboarding_actionsrows allcompleted, 1audit_logrow. - Student fee block: outstanding balance → step 5 marked
failed; withoverride.reason→ step 5completed+ 1 extraaudit_logrow taggedoverride. - Staff happy path:
is_active=0,archive_status='former_staff',classes.class_teacher_idandsubjects.teacher_idset toNULL, pendingleave_requestscancelled, payroll block skipped if last run paid. - Payroll block: unpaid last run → step 2 failed; with override → completed + audit row.
- Transaction integrity: force step 4 failure → entire pipeline rolls back (no
users.is_activeflip, nooffboarding_actionsrows, noenrollmentsupdates). - Cancel:
status='cancelled', partialoffboarding_actionsretained as history. - Sync: each new table sync-tested in isolation; full
tablesToSyncsweep clean. - E2E: 4 specs pass; existing portal-smoke regression-clean.
- No regression on admin/principal dashboards, exam-review, finance, analytics.
5. Merge coordination
Files touched by both phases (potential conflicts at merge into dev):
| File | Phase 2 | Phase 3 |
|---|---|---|
client/src/App.tsx |
Adds principal/bursar/hr to /reports allowed roles; potentially adds a nav entry |
Adds routes for /offboarding, /offboarding/wizard, /alumni; Nav entries |
client/src/components/Nav.tsx |
Same — analytics nav entry already exists | Adds Offboarding + Alumni nav entries |
server/src/index.js |
No change | Registers offboarding controller |
server/src/services/SyncEngine.js |
No change | Appends 2 tables |
Strategy: Phase 2 merges first (no SyncEngine.js/index.js change). When Phase 3 merges, its App.tsx/Nav.tsx changes will be on top of Phase 2's — simple additive merge in those files; no conflict on SyncEngine.js or index.js.
6. Risks + watch-points
- Phase 1's uncommitted cohorts PR must land first. The
server/src/database/seeds/demo.jsmodifications indev(uncommitted) suggest Arthur may be staging demo seed updates — confirm with Phase 1 owner before starting Phase 2. - Debug files in Phase 1 worktree (
server/dbg12.js,server/seed_smoke.js) must not land in dev. The Phase 1 PR review should call these out. - Phase 2's principal/bursar/hr routing fix may conflict with Phase 1's
App.tsxadditions (Phase 1 already addsprincipalin places). Verify withgit grep -n "principal" client/src/App.tsxafter each merge. - Phase 3's transaction integrity is the load-bearing risk. Without a test framework, the rollback verification (verification item 6) must be exercised manually before each Phase 3 PR merge.
- No lint/typecheck scripts — Phase 2 frontend rewrite and Phase 3 new TS files will compound the existing "73 TypeScript warnings" issue. Don't make it materially worse; the project compiles with
strict: falsealready. - CSV export is the only export format. PDF export is explicitly deferred per both plan docs.
- Cohorts tab in Phase 2 is conditional on Phase 1's
student_cohortstable — if for any reason Phase 1 ships but the cohorts migration doesn't apply cleanly, the tab silently renders empty rather than 500s. Add a feature-flag query param if needed.