geocrop-platform./apps/nextgen/.harness/plans/2026-07-22-rollout-plan.md

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):

  1. git diff --stat server/src/database/ is empty for this PR (no new tables).
  2. GET /api/reports/kpis?range=6m returns documented shape with non-zero seed values.
  3. GET /api/reports/timeseries returns 6+ points for 6m, 26+ for 6m+week.
  4. GET /api/reports/cohorts returns rows from the Phase 1 schema (or [] if Phase 1's cohorts are empty on the seed).
  5. GET /api/reports/export?type=...&format=csv downloads a non-empty CSV for each of Overview/Academics/Cohorts/Finance.
  6. Role gating verified: school_admin/principal see all 5 tabs, bursar sees 4 (no Staff), hr sees 4 (no Finance), student/parent/teacher get 403.
  7. Time-range filter changes data (set 7d vs 6m).
  8. Cohort drilldown opens modal with ranked students; Esc closes.
  9. Every KPI query + time-series query <500ms; EXPLAIN QUERY PLAN checked on seed.
  10. E2E spec: load /reports as school_admin, all 5 tabs render, range switch, drilldown, CSV export. Existing portal-smoke tests pass.
  11. 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.jsoffboarding_records, offboarding_actions, ALTER TABLE users ADD COLUMN archive_status.
  • server/src/services/OffboardingService.jsoffboardStudent() + offboardStaff() pipelines. Both wrap in db.transaction(). Reuse Phase 1's user_roles.revoked_at revoke logic.
  • Local node script server/test_offboarding_service.js exercising 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 — append offboarding_records + offboarding_actions to tablesToSync (parents first: usersenrollmentsoffboarding_recordsoffboarding_actions).

PR 3 — frontend pages + components:

  • client/src/store/offboarding.ts — Zustand slice; uses client/src/store/api.ts axios 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):

  1. Migration up + down clean on both fresh DB and existing DB.
  2. Student happy path: users.is_active=0, archive_status='active_alumni', all 5 enrollment/hostel/transport/club state mutations land, 11 offboarding_actions rows all completed, 1 audit_log row.
  3. Student fee block: outstanding balance → step 5 marked failed; with override.reason → step 5 completed + 1 extra audit_log row tagged override.
  4. Staff happy path: is_active=0, archive_status='former_staff', classes.class_teacher_id and subjects.teacher_id set to NULL, pending leave_requests cancelled, payroll block skipped if last run paid.
  5. Payroll block: unpaid last run → step 2 failed; with override → completed + audit row.
  6. Transaction integrity: force step 4 failure → entire pipeline rolls back (no users.is_active flip, no offboarding_actions rows, no enrollments updates).
  7. Cancel: status='cancelled', partial offboarding_actions retained as history.
  8. Sync: each new table sync-tested in isolation; full tablesToSync sweep clean.
  9. E2E: 4 specs pass; existing portal-smoke regression-clean.
  10. 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.js modifications in dev (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.tsx additions (Phase 1 already adds principal in places). Verify with git grep -n "principal" client/src/App.tsx after 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: false already.
  • 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_cohorts table — 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.