-
Notifications
You must be signed in to change notification settings - Fork 1
Roadmap
Items 1–14, 16, and 19 are complete. This file tracks the remaining work.
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: Planned
Tier: Content & Templates
Effort: 1–2 days
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: Planned
Tier: UX / Personalisation
Effort: 2–3 days
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: Planned
Tier: UX / Features
Effort: 3–4 days
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: Planned
Tier: Compliance
Effort: 3–5 days
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: Planned
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
GradeStudent.tsx line 1358 appends a raw string to TipTap's HTML output:
updateEntry(showCommentBankFor, { comment: current + spacer + text });TipTap stores content as HTML (<p>…</p>). Appending plain text to the end of an HTML string produces malformed markup that TipTap may re-render incorrectly or lose on the next save. The fix is to use TipTap's insertContent command so the insertion is structured as a proper paragraph node.
What to build:
- In
GradeStudent, replace the string-concat insertion with a call to the TipTap editor instance'schain().focus().insertContent(text).run()API (same pattern used inTiptapEditor.tsx) - Add a unit test that round-trips a comment bank insertion and checks the resulting HTML is valid
Architecture impact: Minimal — single component change.
Status: Planned
Tier: Bug Fixes & Quick Wins
Effort: < 0.5 days
Impact: Medium — confuses every teacher using the rubric builder
Found by: Teacher tester
RubricBuilder.tsx lines 1524–1548 render two adjacent icon-only buttons that both use the Copy (lucide) icon — one for "Copy criterion to clipboard" and one for "Duplicate criterion." The only distinction is a tooltip, which appears only on hover. First-time users cannot tell them apart.
What to build:
- Replace the "Duplicate" button icon with
Files(two overlapping pages) orCopyPlusto visually distinguish it from the clipboard copy - Add a visible label or aria-description to each button
Architecture impact: Trivial — icon import swap.
Status: Planned
Tier: Bug Fixes & Quick Wins
Effort: 1–1.5 days
Impact: Medium — affects all non-English users
Found by: Teacher tester, Student tester
Despite the locale files being complete (see item 16), several recently added strings are hardcoded English and bypass t():
RubricBuilder.tsx:
- "Rubric Details" (line 727)
- "Expand all" / "Collapse all" (lines 1033–1043)
- "Proficiency descriptor — what 'meets standard' looks like" (line 1593)
- "Points (meets)" (line 1623)
-
title="Copy to clipboard"/title="Paste criterion from clipboard"(lines 1527, 1063)
GradeStudent.tsx:
-
Essaybutton label (line 454) -
Import essaybutton label (line 460)
ComparativeGrading.tsx:
- "Compare: " title prefix (lines 51, 114, 541)
- "Select a Class" / "Choose Starting Student" / "All linked classes" / "Random Start" (picker UI)
- "Session Complete" / "Continue Comparing" (session-done screen)
- "Student Progress" / "Per-student limit:" (session header)
StudentEssayPage.tsx + EmailGate:
- The entire page has no
useTranslationimport — all strings are hardcoded English
What to build:
- Add missing keys to all five locale files (en, nl, fr, de, es)
- Replace hardcoded literals with
t('key')calls in all four files - Add a CI lint rule (or Vitest snapshot) that catches new hardcoded visible strings
Architecture impact: Low — string replacements + locale file additions.
Status: Planned
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
StudentEssayPage.tsx line 212 initialises the essay HTML from sessionStorage:
const [html, setHtml] = useState<string>(() => sessionStorage.getItem(draftKey) ?? '');sessionStorage is cleared when the browser tab is closed. A student who closes the wrong tab or whose laptop battery dies loses everything.
What to build:
- Move the draft key to
localStorage(same key prefix, swap the storage API) - On mount, if a
localStoragedraft exists and it is newer than any existing submission, load it and show a "Draft restored" banner - Clear the
localStoragedraft key on successful submission
Architecture impact: Trivial — one-line API swap + banner component.
Status: Planned
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 line 354:
settings.language === 'nl' ? 'nl-NL' : 'en-US'The useVoiceGrading hook accepts a BCP-47 language tag. The mapping is binary despite the app supporting five languages.
What to build:
- Extend the map:
nl → nl-NL,fr → fr-FR,de → de-DE,es → es-ES, defaulten → en-US - Expose the selected recognition language in the voice-grading status bar so teachers know which language is active
Architecture impact: Trivial — object lookup swap.
Status: Planned
Tier: Bug Fixes & Quick Wins
Effort: 0.5 days
Impact: Low-Medium — confuses admins deploying in offline-only mode
Found by: Administrator tester
The "Users" and "Schools" tabs in AdminPage.tsx call Supabase APIs and show a loading spinner indefinitely in offline-only deployments. Admins who set up the app without Supabase have no idea why nothing appears.
What to build:
- Check
dbStatus.isConnectedat the top ofUsersTabandSchoolsTab - If not connected, render an inline
EmptyStatewith copy: "User management requires a Supabase database. See [Deployment docs] to configure cloud sync." - Link to the wiki Deployment page
Architecture impact: Minimal — guard clause + EmptyState component.
Status: Planned
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
The "Mark as anchor" checkbox in the GradeStudent grade footer and the "Show anchor paper" button have no tooltip, help icon, or documentation link. The concept (a calibration exemplar used to benchmark grader consistency) is not common knowledge.
What to build:
- Add a
?icon next to the checkbox that opens a small popover/tooltip explaining: "Anchor papers are reference submissions used to calibrate your grading. Marking a submission as an anchor lets you compare it side-by-side with new submissions in the grade footer." - Add a translation key for the help text in all five locale files
Architecture impact: Trivial — tooltip component.
Status: Planned
Tier: Essay & Writing Workflow
Effort: 2–3 days
Impact: High — currently the only way to create an essay is buried inside a single student's grading view
Found by: Teacher tester
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: Planned
Tier: Essay & Writing Workflow
Effort: 2–3 days
Impact: High — makes the comparative grading view useful for writing assignments
Found by: Teacher tester
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: Planned
Tier: Essay & Writing Workflow
Effort: 1.5–2 days
Impact: Medium — affects every student taking a timed essay
Found by: Student tester
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: Planned
Tier: Student Experience
Effort: 1–2 days
Impact: Medium — current binary rating loses nuance that matters for CEFR profiling
Found by: Student tester
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: Planned
Tier: Student Experience
Effort: 1–2 days
Impact: Medium — students don't know they have a peer review to complete
Found by: Student tester
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: Planned
Tier: Student Experience
Effort: 3–4 days
Impact: High — without notifications, students must proactively check their portal
Found by: Student tester
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: Planned
Tier: Rubric Builder Polish
Effort: 0.5 days
Impact: Medium — teachers can accidentally submit rubrics where weights sum to 110% or 80% with no warning
Found by: Teacher tester
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.