-
Notifications
You must be signed in to change notification settings - Fork 1
Roadmap
Items 1–14, 16–17, 19–21, 23–40 are complete. This file tracks the remaining work (items 15, 18, 22).
Status: Complete (PRs #93, #94)
Core smoke tests covering the offline teacher workflow are live. playwright.config.ts and e2e/ are in the repository.
Status: Complete (PR #104)
Tier: Testing & Quality
Magic-link sign-in via the admin generate_link API (no email/inbucket required), cloud persistence, offline-queue-and-flush, and pending-queue checks are all covered in a dedicated supabase Playwright project (e2e/specs/14-supabase-sync.spec.ts).
What was built:
-
e2e/fixtures/supabase.fixture.ts— creates a fresh test user + school per test, signs in via magic link, tears down after -
npm run e2e:supabase— runs the Supabase suite (--project=supabase --workers=1) against a local stack - CI job
E2E — Supabase Integrationruns after the standard offline suite; spins upsupabase start, exports credentials, uploads separate artifacts - Five test scenarios: magic-link auth, cloud persistence after localStorage wipe, deletion sync, offline-queue flush on reconnect, queue empty while online
What to keep building (see CLAUDE.md):
- Shared rubric flow — teacher A shares, teacher B sees it (not yet covered)
- Grading-while-offline → sync on reconnect
- Student portal sign-in via share code
- Broader coverage of the reconnect-hydration path and multi-device data merging
Status: Planned
Tier: Architecture
Effort: 8–12 days
Currently localStorage is the source of truth and Supabase is a write-through cache. This causes Supabase data to lag behind, making cross-device access unreliable. Flipping the model — Supabase primary, localStorage offline cache — makes cloud sync reliable and opens the door to real-time collaboration between colleagues.
Scope:
- Audit the read path in
src/store/storage.tsandsrc/context/AppContext.tsx - AppContext hydrates from Supabase on load (when configured); localStorage is populated as a cache
- Offline detection via
navigator.onLine+ reconnect events; queue writes and flush on reconnect - Conflict resolution: last-write-wins on
updated_attimestamp (simplest safe default) - Auth-gated: the Supabase-first path only activates when
VITE_SUPABASE_URLis set; full offline-only mode is unchanged for users without Supabase
Architecture impact: High — core data layer (storage.ts, AppContext, StorageSync). Requires a migration path for existing localStorage-only users and thorough E2E coverage before ship (see item 14).
Status: Complete
Tier: Reach & Localisation
All five locale files (en, nl, fr, de, es) are fully in sync at 852 keys each. Spanish is registered in src/i18n.ts and exposed in the Settings language selector. CLAUDE.md documents that all five files must be updated together.
Status: Complete
Tier: Content & Templates
IB is used in 5,000+ schools globally. MYP Language Acquisition has 4 criteria × 8 performance levels (0–8), a clean fit for the existing rubric model. DP Language A Literature and Language B criteria can also be pre-built.
What to build:
- IB MYP Language Acquisition criteria as a built-in rubric template (JSON in
src/data/) - IB DP Language A: Literature criteria
- IB DP Language B criteria
- Surface these in the template picker in the rubric builder
Architecture impact: Minimal — static data files + template picker extension.
Research: IB criteria are publicly documented; no API needed.
Status: Planned
Tier: EFL Core
Effort: 2–3 days (static) or 5–7 days (live API)
Cambridge levels (KET/PET/FCE/CAE/CPE mapping to A2–C2) are the dominant assessment framework for EFL teachers. The API key field exists in Admin → Integrations and src/services/cambridgeApi.ts contains a working lookupWord() implementation, but the service is not yet imported or called by any feature.
What to build:
- Investigate Cambridge Assessment API availability and terms; if accessible, integrate via a new
src/services/cambridgeApi.tsmodelled onsrc/services/standardsApi.ts - If a live API requires a partnership agreement, ship as static CEFR-mapped Cambridge descriptor data (aligned to the existing CEFR infrastructure)
- Surface Cambridge exam labels (e.g., "B2 First") alongside CEFR levels in the grading and analytics views
Architecture impact: Medium — new service module; CEFR level mapping already exists.
Status: Complete
Tier: UX
Print buttons added to StudentProfilePage, StatisticsPage, and ComparativeGrading. Centralized @media print block in src/index.css hides nav, sidebar, and interactive controls; forces white background; handles view-specific classes (.statistics-controls, .comparative-actions).
Status: Complete
Tier: UX / Personalisation
The app UI is visually tied to a single accent color and the Inter font. Full theme support lets schools and individual teachers express their identity and makes the app more accessible for neurodiverse students who prefer certain typefaces.
Phase 1 (in progress): Foundation already shipped:
-
AppSettings.uiFontFamilyfield + AppContext applies it as--fontCSS variable with dynamic Google Fonts loading - 8 named accent-color preset swatches in Settings → General (Ocean, Forest, Indigo, Sunset, Rose, Slate, Teal, Gold)
- UI font picker in Settings → General (Inter, Nunito, Source Sans 3, Lato, Roboto)
Phase 2 (planned): Full theme bundles:
- Define 4–6 named themes (e.g. "Academy", "Nature", "Midnight", "Warm") — each theme sets
accentColor,uiFontFamily,defaultFormat.fontFamily,defaultFormat.headerColor - Add a
colorPreset?: stringfield toAppSettingsto track the active preset name - Implement tonal accent-color scales (50–900 steps via
color-mix()or LCH interpolation) so--accent-50through--accent-900are available for richer component styling in charts and rubric exports - Export font library for rubric DOCX/PDF: include decorative options:
Playfair Display(elegant serif headings),Oswald(condensed bold),Bebas Neue(display caps),Special Elite(distressed typewriter),Courier Prime(clean terminal) - Rubric title "ASCII / terminal art" styling: monospace fonts + optional border decorations around rubric headings in DOCX export
What to build:
- Theme definition file:
src/data/themes.ts— array of{ id, label, accent, font, headerColor, exportFont }objects - Theme picker in Settings → General (visual card swatches with preview)
- Update
src/context/AppContext.tsxto apply theme bundles atomically (singleupdateSettingscall) - Extend
src/utils/docxExport.tsandsrc/utils/pdfExport.tsto readsettings.defaultFormat.fontFamilyfor decorative heading fonts - CSS custom properties audit: ensure
--accent-{50..900}scale is wired consistently to charts, badges, and rubric cell highlights
Architecture impact: Low-medium — theme data file + Settings UI + CSS variable additions. No data model migration needed.
Status: Complete
Tier: UX / Features
All underlying data exists (grades, CEFR sessions, essays, feedback history) but is spread across separate pages. A portfolio view aggregates a student's full year into a single scrollable report — useful for parent-teacher conferences, student self-reflection, and end-of-year reporting.
What to build:
- New route
/students/:id/portfolio(or a tab onStudentProfilePage) - Chronological timeline: rubric grades → essays submitted → CEFR assessments → speaking sessions
- Print/export to PDF from the portfolio view
- No new data fetching; purely a UI aggregation of existing AppContext state
Architecture impact: Low — new page on existing data model.
Status: Complete
Tier: Compliance
WCAG 2.1 AA compliance is required by EU law (EN 301 549) for software used in schools. The app is currently untested for accessibility. Key risk areas: icon-only buttons without aria labels, keyboard navigation in the grading interface, color contrast of CSS custom properties in light and dark mode.
What to build:
- Run axe-core or Lighthouse accessibility audit; document the top 10 violations
- Fix: add aria labels to all icon-only buttons (lucide-react icons)
- Fix: ensure the rubric level selector in GradeStudent is keyboard-navigable (arrow keys, Enter)
- Fix: verify color contrast ratios for
--accent,--text,--bg-elevatedin both themes - Add axe-core to the Vitest/Playwright suite so regressions are caught automatically
Architecture impact: Low — HTML attributes and CSS only.
Status: Planned (Community Contribution)
Tier: Integrations
Effort: 4–6 days per platform
Magister and SOMtoday are the dominant student information systems (SIS) in Dutch schools. Auto-importing class rosters would eliminate the largest manual onboarding step for NL teachers. Full integration documentation is in /docs/MAGISTER_INTEGRATION.md.
What to build:
-
src/services/magisterApi.ts— Magister REST + OAuth flow -
src/services/somtodayApi.ts— SOMtoday OpenID Connect flow - OAuth token exchange must go through a Supabase edge function (client secret cannot be exposed in the SPA)
- Student data model already exists; the import maps to existing types
Architecture impact: Medium — new service files; edge function for token exchange.
Note: This item is open for community contribution. Core team will review and merge a well-tested PR.
Found during multi-role UX testing. Ordered by impact × effort ratio — do these first.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: 0.5 days
Impact: High — affects every teacher who combines comment bank with rich-text feedback
Found by: Teacher tester
Comment bank insertion now uses TipTap's insertContent API (commentEditorRef.current.insertContent(text)) instead of string concatenation. The text lands as a proper document node and the resulting HTML remains valid regardless of cursor position.
Architecture impact: Minimal — single component change.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: < 0.5 days
Impact: Medium — confuses every teacher using the rubric builder
Found by: Teacher tester
Both the classic and WYSIWYG rubric builder modes now use distinct icons: Copy for "Copy criterion to clipboard" and Files for "Duplicate criterion." The WYSIWYG duplicate button also received title and aria-label attributes that were previously missing.
Architecture impact: Trivial — icon import swap.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: 1–1.5 days
Impact: Medium — affects all non-English users
Found by: Teacher tester, Student tester
All hardcoded English strings in RubricBuilder.tsx, GradeStudent.tsx, ComparativeGrading.tsx, and StudentEssayPage.tsx have been replaced with t() calls. 21 new keys were added to all five locale files (en, nl, fr, de, es), covering form labels, button titles, aria-labels, sub-item tooltips, score panel headers, comparative grading buttons, and email gate error messages. The tier12.test.tsx suite verifies that all new keys are present in every locale file.
Architecture impact: Low — string replacements + locale file additions.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: 0.5 days
Impact: High — a student who accidentally closes the browser tab loses their entire essay draft
Found by: Student tester
Essay drafts are now persisted to localStorage under the rm_essay_draft_<code> key. A "Draft restored" banner appears on page load when a saved draft exists. The draft is cleared on successful submission. Only the countdown timer continues to use sessionStorage (intentional — timer resets on reload are correct behaviour).
Architecture impact: Trivial — one-line API swap + banner component.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: < 0.5 days
Impact: Medium — FR, DE, ES teachers get English speech recognition regardless of UI language
Found by: Teacher tester
GradeStudent.tsx now maps all five app languages to their BCP-47 counterparts: nl → nl-NL, fr → fr-FR, de → de-DE, es → es-ES, with en-US as the default. Speech recognition now respects the teacher's chosen UI language.
Architecture impact: Trivial — object lookup swap.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: 0.5 days
Impact: Low-Medium — confuses admins deploying in offline-only mode
Found by: Administrator tester
UsersTab and SchoolsTab in AdminPage.tsx now check dbStatus.isConnected and render a clear offline-state message instead of an infinite spinner. Translation keys (admin.users_offline_title, admin.users_offline_body, admin.schools_offline_title, admin.schools_offline_body) are present in all five locale files.
Architecture impact: Minimal — guard clause + EmptyState component.
Status: Complete
Tier: Bug Fixes & Quick Wins
Effort: 0.5 days
Impact: Medium — teachers new to standards-based grading don't know what "Mark as anchor" means
Found by: Teacher tester
A help icon (?) next to the "Mark as anchor" checkbox now shows a tooltip explaining the concept. The help text is stored in the gradeStudent.anchor_help_text key and translated into all five locale files.
Architecture impact: Trivial — tooltip component.
Status: Complete
Tier: Essay & Writing Workflow
The essay assignment workflow lives exclusively in GradeStudent (via the "Essay" button in the topbar). To assign an essay to an entire class, the teacher must:
- Open any one student's grading view
- Click "Essay" and configure the assignment
- Click "Show class link" to open the
EssaySlipSheet
This sequence is non-obvious. Many teachers would not discover the slip-sheet path. There is also no way to see all active or past essay assignments from a dashboard.
What to build:
- New "Essays" tab on the
RubricListpage (or a dedicated/essaysroute): list all essay assignments per rubric with status (draft / active / past deadline) - "New essay assignment" action from the rubric card that opens
EssayAssignmentModalpre-populated with the rubric, targeting the whole class (not a single student) - Link the slip-sheet directly from this new entry point
- "My Essays" widget on the
Dashboardshowing assignments with unreviewed submissions
Architecture impact: Low-medium — new route/tab + minor changes to EssayAssignmentModal to accept an optional class ID instead of requiring a student ID.
Status: Complete
Tier: Essay & Writing Workflow
ComparativeGrading shows two students' rubric scores side-by-side but has no way to display their submitted essay text. For writing assignments the teacher needs to read both texts while grading — currently they would need to switch back and forth between the individual grading views.
What to build:
- Add a collapsible "Essay" panel above (or inline with) each student column in the comparative grading grid
- Fetch essay submission HTML for each student via the existing
EssayImportModal/EssayAdapterflow - Render both essays in read-only TipTap instances with synchronized scroll
- Add an "essay" mode toggle to the comparative session header (hidden when no essay submissions exist)
Architecture impact: Medium — new fetch + read-only TipTap render in ComparativeGrading.tsx.
Status: Complete
Tier: Essay & Writing Workflow
StudentEssayPage (including EmailGate) hardcodes a light-mode color palette (background: '#f8fafc', color: '#1e293b') and uses no useTranslation calls — all visible strings are hardcoded English. This is inconsistent with the rest of the app where the student may have selected Dutch, French, German, or Spanish.
What to build:
- Replace all hardcoded hex colors with CSS custom properties (
var(--bg),var(--text), etc.) so the page inherits the user's theme - Add
useTranslationtoStudentEssayPageandEmailGate; add translation keys to all five locale files for: email gate copy, SEB blocking banner, submission confirmation, deadline-passed screen, and all button labels - Confirm dark mode works end-to-end on the essay page
Architecture impact: Low — CSS variable substitution + i18n wrapping; no logic changes.
Status: Complete
Tier: Student Experience
SelfAssessPage presents each Can-Do descriptor with a single "confident" checkbox. A student who partially understands a descriptor has no way to express that; they must choose between fully claiming it and fully rejecting it. This reduces the diagnostic value of self-assessments.
What to build:
- Replace the boolean
confidentfield with a 1–4 scale (e.g., 1 = "Not yet", 2 = "Sometimes", 3 = "Usually", 4 = "Always / Confident") - Update
SelfAssessmentRatingtype: add optionalconfidenceLevel?: 1 | 2 | 3 | 4alongside the legacyconfident: booleanfor backwards compatibility - Render as a small segmented button or emoji-scale row per descriptor
- Aggregate the new scale into the CEFR overview charts (e.g., average confidence level per skill per student)
- Add translation keys for the four labels in all five locale files
Architecture impact: Low — type extension + UI change in SelfAssessPage; CEFR aggregator needs a minor update.
Status: Complete
Tier: Student Experience
The student portal (StudentPortalPage) shows rubric grades, CEFR progress, and essay assignments — but has no section for pending peer reviews. Students learn about peer review assignments only if the teacher explicitly shares a separate link with them. Most students in testing never discovered they had a peer review to complete.
What to build:
- Add a "Peer Reviews" section to the student portal below the essay assignments section
- Query
peerReviewsfrom AppContext for the currentstudentId(as reviewer) to find incomplete ones - Show: rubric name, assigned submission (student being reviewed, if visible), due date if set, link to the peer review URL
- Highlight incomplete peer reviews with a badge
Architecture impact: Low — UI addition to StudentPortalPage; no new data fetching.
Status: Complete
Tier: Student Experience
Students have no way to know when a teacher has published feedback. In Supabase mode, the grading data is stored in the database, making email triggers feasible via Supabase Edge Functions.
What to build:
- Supabase Edge Function:
notify-student-graded— triggered by an insert/update on thestudent_rubricstable; looks up the student's email (if stored) and sends a templated notification email via the configured SMTP provider - Teacher setting (in
SettingsPage): "Notify students when graded" toggle (default off until teachers opt in) - Email template: "Your feedback for [Rubric Name] is ready — view it at [portal link]"
- Gracefully skip notification if student has no email or SMTP is not configured
Architecture impact: Medium — new Edge Function; optional SMTP dependency already configured for auth emails.
Status: Complete
Tier: Rubric Builder Polish
In weighted-percentage scoring mode, each criterion has a weight field but no running total is shown and no warning fires when weights don't add to 100%. The grade calculation engine normalises the weights at runtime, but the rubric still looks misconfigured to the teacher.
What to build:
- Compute
totalWeight = criteria.reduce((sum, c) => sum + c.weight, 0)inRubricBuilder - Display the running total near the criteria header: "Total weight: 95%
⚠️ (should be 100%)" - Color the indicator amber when outside 98–102%, red when outside 90–110%
- Optionally add a "Distribute evenly" one-click button that sets all weights to
Math.round(100 / criteria.length) - Add a non-blocking warning (not a hard block) on save if total weight is outside 90–110%
Architecture impact: Trivial — derived value computation + UI display.
Status: Planned
Tier: Rubric Builder Polish
Effort: 1–2 days
Impact: Medium — teachers currently cannot reorder levels without deleting and recreating them
Found by: Teacher tester
Criteria support drag-and-drop reordering via @hello-pangea/dnd. Levels within a criterion do not — there are no move handles, and the rubric builder renders levels in a horizontal scroll without any reorder affordance. A teacher who wants to reverse the order of levels (e.g., to match the rubric format setting levelOrder) must delete and recreate each level.
What to build:
- Add a
GripHorizontaldrag handle to each level card in the horizontal level list - Wire a nested
Droppable/Draggablewithin the criterion's level row using@hello-pangea/dnd(already installed) - Update the
updateCriterioncall to reorder thelevelsarray on drop
Architecture impact: Low — nested DnD within existing DnD context; the library supports this.
Status: Planned
Tier: Rubric Builder Polish
Effort: 1–2 days
Impact: Medium — teachers who build a rubric want to reuse it without importing/exporting JSON
Found by: Teacher tester
Teachers can create rubrics from built-in templates or import a JSON file, but there is no way to save an existing rubric as a named personal template. The workaround is to export JSON and re-import — a multi-step friction point.
What to build:
- Add a "Save as template" option to the rubric builder export menu (alongside PDF, DOCX, JSON, Print)
- Store user-defined templates in
localStorageunder a dedicated key (e.g.,rm_user_templates) - Surface them alongside the built-in templates in the rubric builder's "New from template" picker
- Allow deleting user-defined templates from the picker
Architecture impact: Low — new localStorage key + template picker UI extension; no changes to the rubric data model.
Status: Planned
Tier: Rubric Builder Polish
Effort: 2–3 days (Supabase mode only)
Impact: Medium — the RubricShare type and database schema already exist; the UI is missing
Found by: Administrator tester
The RubricShare type (src/types/index.ts) and the Supabase schema have support for teacher-to-teacher rubric sharing, but there is no UI to trigger or view shares. Teachers in the same school cannot collaborate on or reuse each other's rubrics without the JSON export/import workaround.
What to build:
- "Share with colleague" button in the rubric builder topbar (visible only in Supabase mode)
- Modal: enter colleague's email → creates a
rubric_sharesrow in the database - Recipient sees shared rubrics in a "Shared with me" section on the rubric list
- Recipient can clone a shared rubric into their own account
Architecture impact: Medium — new Supabase write path + rubric list UI section.
Items marked with (Supabase mode only) require an active database connection. All items marked (offline) work without Supabase.
Tiers 12–15 were added following a structured three-role UX testing session (teacher, administrator, student) conducted on the main branch. Items are ordered within each tier by impact × effort ratio.