Skip to content

Architecture

Wouter Meetsma edited this page Jun 20, 2026 · 3 revisions

Architecture

Overview

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

Directory structure

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

State management

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.


Persistence layer

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.


Routing

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.


Document processing pipeline

When a student attachment is opened:

  1. The file is read as an ArrayBuffer in the browser.
  2. PDF files → rendered via PDF.js in a canvas; text layer extracted for search.
  3. DOCX files → converted to HTML by Mammoth and rendered inline.
  4. 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 pipeline

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.


Tech stack summary

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

Clone this wiki locally