# Track C — Module Placeholders & Toast Foundation **Owner:** Person C **Branch:** `fix/stubs-module-placeholders` (worktree at `.worktrees/stubs-module-placeholders/`) **Files in scope:** `client/src/pages/exams/ExamEditor.tsx`, `client/src/pages/exams/ExamViews.tsx` (Edit button + create-mode extraction only), `client/src/App.tsx` (route decision in C1 only), `client/src/pages/admin/Settings.tsx` (alert → toast pass), `client/src/components/EdutainmentGames.tsx` (optional C4), `client/src/components/Toast.tsx` (new). ## Conventions (from AGENTS.md) - Work in a worktree branched from `dev`. Do not edit the main checkout. - Use the shared axios instance at `client/src/store/api.ts`. - Route gating stays in `App.tsx` via `` — do not gate inside pages. - All new tables go in `server/src/database/init.js` (this track adds none, but if you do, update `tablesToSync` in `SyncEngine.js`). - PWA service worker is only built by `npm run build`. - Do not commit or push without an explicit "go" from the reviewer. ## Tasks ### C1. ExamEditor — Placeholder page - **File:** `client/src/pages/exams/ExamEditor.tsx` (whole file, 43 lines, route `/exams/edit/:id` at `App.tsx:368`) - **Current:** The page renders only a "Module Under Construction" card. Anyone clicking "Edit" from the exam list lands here. - **Pick one with the user (this is a design call, not yours):** - **(a) Implement.** Extract the create-mode form in `ExamViews.tsx` (around the new-exam modal: title, description, exam_type, academic_year, dates) into a shared `client/src/pages/exams/ExamForm.tsx` component. Have `ExamEditor` render ``. Wire `loadExam(id)` against the existing exams API. - **(b) Defer.** Delete the route from `App.tsx:368` and remove the "Edit" button in `ExamViews.tsx` until the editor is actually needed. The "Create" path stays unchanged. - **Recommended starting point:** **(b)**. It is one commit, removes a misleading page, and leaves the form work for a real session. - **Acceptance:** No route resolves to the placeholder. Either a real editor renders, or there is no UI surface at all that lands on the placeholder. ### C2. EdutainmentGames — Biased random-shuffle - **File:** `client/src/components/EdutainmentGames.tsx:77` - **Current:** `const shuffled = [...students].sort(() => 0.5 - Math.random());` — the comparator pattern is biased for arrays longer than ~5. - **Desired:** Replace with a proper Fisher–Yates: `for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; }`. Extract to a helper at the top of the file (`function shuffleInPlace(arr: T[])`). - **Acceptance:** Picking a random student 100 times with a 10-student class produces each name between 5 and 25 times (rough uniformity check, not exact). - **Note:** Other `Math.random` uses in the file (lines 424, 428-447, 596) are for generating maths problems and seed values — leave them. The bug only matters for student picker fairness. ### C3. Settings — Toast foundation and alert sweep - **Files:** New `client/src/components/Toast.tsx`, `client/src/App.tsx` (mount provider), `client/src/pages/admin/Settings.tsx` (8 `alert()`s at :114, :116, :135, :143, :145, :153, :175, :178). - **Current:** Every action in Settings (terminate sessions, integrity check, run diagnostics, download backup, upload logo) reports success or failure via `alert(...)`. - **Desired:** - Add a tiny toast system: `ToastProvider` mounted in `App.tsx` near the ``, exposes a `useToast()` hook returning `{ success, error, info }`. Single-file implementation, no external dependency. - In Settings, replace each `alert('All active sessions have been terminated successfully.')` with `toast.success('All active sessions terminated.')`, etc. Error `alert()` → `toast.error(err.response?.data?.error || 'Failed …')`. - Verify no other file in `Settings.tsx` uses `alert` after the pass. - **Acceptance:** Click each action in Settings — toasts appear, no `alert()`. `grep -n "alert(" client/src/pages/admin/Settings.tsx` returns no hits. ### C4. (Optional, time permitting) Toast sweep in other admin pages - **Files:** `HRManagement.tsx`, `Inventory.tsx`, `Library.tsx`, `Users.tsx`, `Teachers.tsx` (after Track A's modal work, success/error alert()s will mostly be gone already), PostalDispatch, PhoneCalls, AdmissionEnquiry. - **Current:** Each page owns its own success/error `alert()`s. - **Desired:** Swap them to the toast hook from C3. Pure mechanical pass — no UX changes. - **Acceptance:** "Send message" in Messages, "Save" in Inventory, etc. all show toasts. No `alert(` left in the touched files. ### C5. Reservation page thinness — walk-through + scope - **File:** `client/src/pages/librarian/Reservations.tsx` (7.6 KB, 227 lines) - **Current:** Functional thin page. - **Desired:** Read it once. If there are real gaps (filters, bulk-cancel, overdue flag), write a small ticket under `.harness/reins/` or as a comment in the file. **Do not silently expand scope.** If it is honest as-is, close it with a one-line "no action" note in your PR. - **Acceptance:** Either a follow-up ticket exists, or the PR explicitly says "reviewed and intentionally left as-is". ## Cross-cutting checks - `curl http://localhost:3000/exams/edit/123` after `npm run build` returns 200 from the SPA — but clicking an Edit button in the UI does not land on "Module Under Construction" copy (C1). - `grep -rn "alert(" client/src/pages/admin/Settings.tsx` is empty after C3. - `npm run build` is green in your worktree; `` renders in dev (`npm run dev`) and shows in the top-right with auto-dismiss. ## Manual test 1. As `school_admin`, open the exam registry, click "Edit" on any exam. Either an editor renders (C1a) or there is no Edit button at all (C1b). 2. Open Settings, click each action. Toasts. No `alert()` or `confirm()`. 3. Pick a student randomly 10 times in EdutainmentGames → no student wins twice in a row (C2 sanity). 4. If you did C4, click "Save" / "Delete" / "Issue" across HRManagement, Inventory, Library — toasts. ## Out of scope here (other tracks) - Login, Profile, Teachers password resets and filters — see Track A. - Fees, MyCourses classes fallback, AdminDashboard avatars — see Track B. - "Every `confirm()` becomes a styled modal" — separate task. Most `confirm()`s in the codebase are legitimate destructive-action prompts; do not touch on this track.