-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
Rubric Maker is a client-side single-page application. All state is stored in the browser's localStorage. An optional Supabase sync layer can mirror data to a PostgreSQL backend for cross-device sync and colleague sharing.
Browser
└── React SPA (Vite bundle)
├── React Router 6 — client-side routing
├── AppContext — global state (rubrics, students, grades)
├── localStorage — primary, synchronous persistence
├── StorageSync — optional background Supabase mirror
│ ├── SupabaseAdapter — Supabase client wrapper
│ └── AttachmentSync — file upload/download via Supabase Storage
├── Tiptap — rich-text editing
├── PDF.js / Mammoth — document rendering
├── Tesseract.js — OCR (in a Web Worker)
├── docx / pdfjs-dist — export generation
└── i18next — UI translations
External services (all optional):
- Supabase — PostgreSQL + Storage for cross-device sync and sharing
- Common Standards Project API — fetches educational standards for the standards picker
- Microsoft Graph API — accesses OneDrive files via Azure MSAL authentication
src/
├── App.tsx # Root component; defines all routes
├── main.tsx # Entry point — mounts <App /> into #root
├── i18n.ts # i18next initialisation
│
├── components/ # Reusable UI components
│ ├── Layout/
│ │ ├── Topbar.tsx # Top navigation bar
│ │ └── Sidebar.tsx # Left navigation sidebar
│ ├── Statistics/ # Chart components (recharts-based)
│ ├── Editor/ # Tiptap editor wrappers
│ ├── CEFR/ # CEFR badge and level picker
│ ├── Comments/ # Comment bank modal
│ ├── Standards/ # Common Standards picker modal
│ └── ... # Other modals and shared components
│
├── pages/ # One file per route
│ ├── PrivacyPage.tsx # GDPR/AVG in-app privacy documentation (/privacy)
│ ├── RubricBuilder.tsx # Create / edit a rubric
│ ├── RubricList.tsx # List all rubrics
│ ├── GradeStudent.tsx # Main grading interface
│ ├── ComparativeGrading.tsx
│ ├── Dashboard.tsx # Statistics overview
│ ├── StatisticsPage.tsx # Full analytics page
│ ├── StudentsPage.tsx # Student roster management
│ ├── StudentProfilePage.tsx
│ ├── ExportPage.tsx # Export to PDF / Word / CSV
│ ├── SpeakingSession.tsx # Oral assessment
│ ├── SelfAssessPage.tsx # Student self-assessment
│ ├── PeerReviewView.tsx # Peer review
│ ├── CommentBankPage.tsx
│ ├── AttachmentsPage.tsx
│ ├── SettingsPage.tsx
│ ├── AdminPage.tsx # Roles, integrations, archive, audit log
│ ├── VocabularyDashboardPage.tsx
│ ├── PeerReviewAnalyticsPage.tsx
│ ├── ActivityDashboardPage.tsx
│ ├── TestListPage.tsx / TestBuilderPage.tsx / StudentTestPage.tsx / TestResultsPage.tsx
│ ├── EssayListPage.tsx / EssayBuilderPage.tsx
│ ├── LiveMonitorPage.tsx
│ └── ...
│
├── context/
│ ├── AppContext.tsx # Main state: rubrics, students, grades, settings
│ └── ToastContext.tsx # Global toast notification system
│
├── hooks/
│ ├── useToast.ts # Convenience hook for triggering toasts
│ └── useVoiceGrading.ts # Speech recognition hook
│
├── services/
│ ├── standardsApi.ts # Common Standards Project API client
│ ├── microsoftGraph.ts # Microsoft Graph API client
│ ├── msalConfig.ts # Azure MSAL configuration
│ ├── mediaStore.ts # IndexedDB blob store for speaking-session recordings
│ ├── RecordingSync.ts # Optional cloud sync of recordings to Supabase Storage
│ ├── AuditLogger.ts # Writes admin/grade/auth events to the audit_logs table
│ └── database/ # Supabase sync layer
│ ├── SupabaseAdapter.ts # Raw Supabase client wrapper
│ ├── StorageSync.ts # Background sync singleton
│ ├── AttachmentSync.ts # File upload/download via Storage
│ ├── types.ts # Database-layer TypeScript types
│ └── index.ts # Public exports
│
├── store/
│ └── storage.ts # localStorage read / write / serialise helpers
│
├── utils/
│ ├── gradeCalc.ts # Score calculation (total points, weighted)
│ ├── pdfExport.ts # PDF report generation
│ ├── docxExport.ts # Word document generation (raw)
│ ├── docxTemplateExport.ts # Word mail-merge template export
│ ├── textExtraction.ts # PDF.js + Tesseract OCR pipeline
│ ├── rubricImport.ts # Import rubric from JSON
│ ├── vocabularyAnalyser.ts # NLP vocabulary analysis
│ ├── grammarChecker.ts # Grammar analysis via compromise.js
│ ├── learningGoalsAggregator.ts
│ ├── essayShareCode.ts # Share code generation
│ ├── studentShareCode.ts
│ ├── nanoid.ts # Unique ID generation
│ ├── syncMerge.ts # LWW + pending-queue conflict resolution for Supabase sync
│ ├── contrastCheck.ts # WCAG luminance/contrast-ratio audit (no dependency)
│ ├── clozeParse.ts # Parses {{cloze}} and [[hot-text]] test-question syntax
│ └── ...
│
├── types/
│ └── index.ts # All shared TypeScript interfaces and types
│
├── data/
│ ├── cefrDescriptors.ts # CEFR level descriptor text
│ ├── speakingDimensions.ts # Speaking assessment dimensions
│ ├── templates.ts # Default rubric templates
│ └── voTracks.ts # Dutch VO education track data
│
└── locales/
├── en.json # English UI strings
├── nl.json # Dutch UI strings
├── fr.json # French UI strings
├── de.json # German UI strings
└── es.json # Spanish UI strings
There is no Redux or Zustand. State is managed with React's built-in useContext + useReducer.
AppContext holds:
-
rubrics— all rubric definitions -
students— student records and class assignments -
grades— graded rubric submissions (indexed by student + rubric) -
commentBank— reusable feedback snippets -
settings— user preferences (language, theme, Supabase connection, etc.)
On every state change, AppContext writes the updated state to localStorage via storage.ts. On startup, it reads the initial state from localStorage. The AppContext reducer is not aware of Supabase — it only writes to localStorage. StorageSync observes the context and mirrors changes to Supabase in the background.
ToastContext is a lightweight notification queue. Components call useToast() to show success/error/info messages.
src/store/storage.ts provides typed read/write functions over localStorage. Data is serialised as JSON. There is no migration system on the localStorage side — structural changes require a manual backup/restore or a one-time migration function added to the startup path in AppContext. The Supabase schema, by contrast, does have a sequential migration system (supabase/migrations/, 40+ files as of June 2026) — see Supabase Sync.
localStorage has a browser-imposed limit of roughly 5–10 MB. Large attachments (PDFs, images) stored as base64 can approach this limit quickly. The app warns the user when storage is running low.
Routes are defined in src/App.tsx using React Router 6's <Routes> / <Route> components. All routes are client-side; the server (or nginx/SharePoint) must fall back to index.html for any path to support deep linking.
When a student attachment is opened:
- The file is read as an
ArrayBufferin the browser. - PDF files → rendered via PDF.js in a canvas; text layer extracted for search.
- DOCX files → converted to HTML by Mammoth and rendered inline.
- Images → displayed directly; if no text is detected, Tesseract.js OCR runs in a Web Worker to extract text.
OCR is CPU-intensive. Tesseract runs in a Web Worker to avoid blocking the UI thread.
| Export format | Library | Entry point |
|---|---|---|
| PDF report | PDF.js (canvas → blob) | src/utils/pdfExport.ts |
| Word (raw) | docx | src/utils/docxExport.ts |
| Word (template) | docx + custom mail-merge | src/utils/docxTemplateExport.ts |
| CSV | PapaParse | src/pages/ExportPage.tsx |
| JSON backup | Native JSON.stringify
|
src/context/AppContext.tsx |
All exports are generated entirely in the browser and downloaded via file-saver.
| Layer | Library | Version |
|---|---|---|
| UI framework | React | 19 |
| Language | TypeScript | 6 (strict) |
| Build tool | Vite | 8 |
| Routing | React Router | 7 |
| Rich-text editor | Tiptap | 3.23 |
| Drag and drop | @hello-pangea/dnd | 18 |
| Charts | Recharts | 2.12 |
| Document rendering | PDF.js, Mammoth, docx-preview | — |
| OCR | Tesseract.js | 5.1 |
| NLP | compromise.js | 14.15 |
| Export (Word) | docx | 9.5 |
| Export (CSV) | PapaParse | 5.4 |
| Microsoft auth | @azure/msal-browser | 3.2 |
| Database sync (optional) | @supabase/supabase-js | 2.x |
| i18n | i18next, react-i18next | 25 / 16 |
| Speech | react-speech-recognition | 4 |
| Testing | Vitest + Testing Library | 4.1 |
| Container runtime | nginx Alpine | — |