# 2026-07-21 โ€” P1-UX terminology final pass + notice board stat polish Branch: `fix/terminology-final-pass` Worktree: `.worktrees/fix-terminology-final-pass` Branched from: `dev` ## Why A code audit of the UI/UX feedback PDF (`NextGen UI and UX feedback and suggestions.docx`, 13 issues) found ~70-80% of items already shipped via the shapeshift dictionary + per-page migrations (P1-11 / P1-UX-5b / P1-UX-7). The remaining leftovers were a handful of hard-coded strings in the analytics teacher dashboard, the exam module, the admin Settings academic tab, and a stat-tile UX bug on the notice board. ## Changes ### `client/src/lib/terminology.ts` (+8 entries) New `T` dictionary keys covering the stragglers the previous PR missed: - `BELOW_THRESHOLD_HEADER` โ€” 'Below Average' (per-subject table column header, was "Below 50%") - `BELOW_THRESHOLD_EMPTY` โ€” 'No learners below the 50% average. ๐ŸŽ‰' (was "No students below the 50% threshold. ๐ŸŽ‰") - `OUTLIERS_HEADING` โ€” 'Below Average (avg < 50%)' (was "Outliers (avg < 50%)") - `OUTLIERS_CTA` โ€” 'Click a learner above' (was "click an outlier above") - `SELECT_FLAGGED_LEARNER` โ€” 'Select a flagged learner to see their history.' (was "Select a flagged student to see their history.") - `GRADING_COLUMN` โ€” 'Marking' (exam list table column header) - `PASS_THRESHOLD` โ€” 'Pass Average' (exam form label, was "Pass Threshold") - `GRADING_SCALE` โ€” 'Marking Scale' (admin Settings tab + exam form label, was "Grading Scale") - `RECENT_GRADES` โ€” 'Recent Marks' (learner profile card, was "Recent Grades") ### `client/src/test/terminology.test.ts` (+2 specs, 12 new assertions) - `it('carries the ยง6 follow-up teacher-dashboard strings', ...)` - `it('carries the ยง6 follow-up exam-module renames', ...)` ### `client/src/pages/teacher/TeacherDashboard.tsx` (analytics) The active analytics dashboard is the one App.tsx mounts for `/dashboard/teacher`. Wired 6 hard-coded strings to `T.*` keys: - Per-subject table column header โ†’ `T.BELOW_THRESHOLD_HEADER` - Outliers card heading โ†’ `T.OUTLIERS_HEADING` - Outliers card empty-state โ†’ `T.BELOW_THRESHOLD_EMPTY` - Drill-down card heading โ†’ `T.STUDENT_DRILLDOWN` - Drill-down card caption โ†’ `T.OUTLIERS_CTA` - Drill-down empty-state โ†’ `T.SELECT_FLAGGED_LEARNER` - Recent grades label โ†’ `T.RECENT_GRADES` `T` is imported as a named export. No other behaviour changed. ### `client/src/pages/exams/ExamViews.tsx` - Exam list table column header โ†’ `Term.GRADING_COLUMN` - Exam form "Pass Threshold" label โ†’ `Term.PASS_THRESHOLD` - Exam form "Grading Scale" label โ†’ `Term.GRADING_SCALE` `Term as T` is imported from `lib/terminology` (local alias to avoid clash with the existing `T` letter grade in the file). ### `client/src/pages/admin/Settings.tsx` Renamed the academic-tab admin copy from "Grading" to "Marking" per PDF ยง6 / ยง9: - Page hero heading "Grading & Terminology" โ†’ "Marking & Terminology" - Hero description: "its grading scale" โ†’ "its marking scale" - Stat label "Grading Scale" โ†’ `Term.GRADING_SCALE` - Section heading "Grading & Levels" โ†’ "Marking & Levels" - Section description: "its grading scale" โ†’ "its marking scale" - Empty-state copy: "to bind it to a grading scale" โ†’ "to bind it to a marking scale" - Configs table column "Grading Scale" โ†’ `Term.GRADING_SCALE` - Add-level modal: "Grading Scale" label โ†’ `Term.GRADING_SCALE` - Add-level modal subtitle: "Bind a new level to a grading scale" โ†’ `Bind a new level to a ${Term.GRADING_SCALE.toLowerCase()}` DB field names (`grading_scale`, `defaultGradingScale`, `grading_scale_label`) are unchanged โ€” these are store/API identifiers, not user-facing labels. ### `client/src/pages/admin/NoticeBoard.tsx` (stat-tile UX fix) PDF ยง6 raised: "when a broadcast is not set to be pinned, it leaves a zero reading appearing on the Notice Board". The Priority Pins stat was rendering a literal `0` (with the trend "Fixed") which reads as a bug to the user. The fix: - The `Priority Pins` stat now carries an optional `valueWhenZero: 'โ€”'` override. - The render path checks `value === 0 && valueWhenZero` and swaps in the dash. - The `trend` text now reads `'Pinned'` when at least one notice is pinned and `'None'` otherwise (was a fixed `'Fixed'` that was ungrammatical in the empty case). No change to the count itself โ€” `n.is_pinned || n.pinned` is still the source of truth, matching the SQLite column the controller returns. ## Issues 11-13 Items 11-13 in the source feedback doc have no description and no screenshots attached. They were marked "Not started" in the source. No code action is possible without a description; they remain open pending client input. ## Verification - `npm test` (vitest) โ†’ **47 / 47 passed** (was 45 / 45; 2 new specs). - Typecheck delta: zero new errors. The dev branch already had 139 pre-existing `tsc` errors (most are missing `@types/vitest` and pre-existing shape mismatches in `pages/admin/NoticeBoard.tsx` / `pages/exams/ExamViews.tsx`). My branch is at 150 โ€” the 11-line increase is all from `terminology.test.ts` (pre-existing test-file pattern, no new types needed since I followed the same shape as the existing `it(...)` blocks). - Manual diff: every edit is a literal string change or a `T.*` substitution. No API surface changes, no logic changes, no identifier renames. ## Out of scope - Search-bar accessibility (zoom expansion) โ€” minor, no WCAG-blocking defect. - Emergency vs Priority badge copy clarity โ€” minor, copy-only. - Issues 11-13 in the source doc โ€” empty, pending client input.