A comprehensive, offline-first rubric creation and grading tool built with React and TypeScript. Designed for educators who need to design complex rubrics, grade students efficiently, and analyse performance — including language proficiency tracking aligned to the Common European Framework of Reference (CEFR).
- Flexible structure: Create rubrics with custom criteria and performance levels.
- Scoring modes: Total Points (raw score), Weighted Percentage, and Single-Point Rubric.
- Advanced level options: sub-item checklists within a level, point ranges (min/max), and score modifiers.
- Standards integration: Link criteria to CCSS, NGSS, and other state/national standards via the Common Standards Project API.
- CEFR descriptors: Attach CEFR Can-Do statements to individual criteria.
- Framework descriptors: Link criteria to IB Learner Profile attributes or Bloom's Taxonomy levels.
- Rubric versioning: Automatic snapshots on save; restore any previous version.
- Tests & quizzes: Build multiple-choice, multiple-response (select all that apply), true/false, short-answer, open, fill-the-gap (with optional dropdown), matching, ordering, categorize, and hot text tests with a duration, optional Safe Exam Browser requirement, and grade scale. Link standards and CEFR descriptors per question, then assign tests to a class — each student gets a unique share link. Every question type has an in-context help button explaining how to author and answer it.
- Live monitoring: While a test or essay is in progress (cloud sync enabled), watch a live presence/progress view per student — response grid for tests, live word count and draft preview for essays, plus advisory proctoring flags (tab switches, copy/paste, battery, Safe Exam Browser status).
- Student management: Manage students and organise them into classes, with Dutch VO track (VMBO/HAVO/VWO) support.
- Interactive grading: Click levels, toggle sub-items, or use the slider for point ranges.
- Score modifiers: Apply percentage, point, or level adjustments with a reason.
- Comment Bank: Tag and insert reusable feedback snippets while grading.
- Voice grading: Dictate comments hands-free using speech recognition.
- Overall feedback: Add general comments and file attachments per graded rubric.
- Comparative grading: Grade two students side-by-side for consistency.
- Peer review: Students review each other's work against the same rubric.
- Peer review analytics: Compare peer grades against the teacher baseline (consistency and leniency bias per reviewer), a feedback heatmap of which criteria attract the most peer comments, and round-over-round trends.
- Self-assessment: Students self-assess against CEFR Can-Do statements.
- Speaking sessions: Structured speaking assessments with six pre-built dimensions aligned to Dutch VO CEFR targets (VMBO-BB through VWO). Audio (and, with cloud sync, video) recordings can be attached and play back from the student's portfolio.
- CEFR overview: Per-student and whole-class proficiency dashboards showing progress across Reading, Writing, Speaking, and Listening.
- Student self-assessment: Students rate themselves against Can-Do descriptors; reflection text is stored alongside teacher scores.
- Cambridge English exam mapping: Optional setting shows the Cambridge English Qualification (A2 Key, B1 Preliminary, B2 First, C1 Advanced, C2 Proficiency) alongside CEFR level badges; vocabulary items can be enriched with CEFR level and definition via an optional Cambridge Dictionary API key.
- Dedicated workspace: A standalone "Essays" section (parallel to Tests) lists every essay assignment, with a builder for the prompt, rubric link, word/time limits, assigning to a class, copying per-student share links, importing submission codes, and a live monitor link.
- Essay assignments: Teachers create prompts with optional CEFR-linked rubrics.
- Rich text editor: TipTap (ProseMirror) editor with formatting toolbar.
- Submission codes: Anonymous essay access via shareable codes — students submit without logging in.
- Document analysis: OCR via Tesseract.js and DOCX parsing via Mammoth; vocabulary and grammar checking on uploaded documents.
- Essay import: Import student essay text from uploaded DOCX or PDF files.
- Peer review: Classmates leave structured feedback on submissions.
- Statistics dashboard: Class performance with Average, Median, Highest, and Lowest scores; grade distribution charts; per-criterion performance breakdown.
- Vocabulary Profile dashboard: Per-class and per-student CEFR vocabulary distribution (A1–C2), aggregated from document analysis results, with CSV export of vocabulary lists filtered by CEFR band.
- Student profiles: Individual progress view across all rubrics, CEFR levels, and essays. A Portfolio tab shows a unified chronological timeline of grades, speaking sessions, and self-assessments.
- Overdue tracking: Highlights students with assignments past due dates.
- Export options:
- PDF: Individual student reports or bulk class export.
- Word (.docx): Raw export or mail-merge templates with field substitution.
- CSV: Raw data for Excel or other gradebooks.
- Period report: Aggregated CEFR progress report for a class over a date range.
- Shareable links: Each student gets a unique portal link; no login required.
- View feedback: Students see their grades, teacher comments, and attached files.
- Submit essays: Anonymous essay submission via submission codes.
- Self-assessment: Students complete CEFR self-assessments from their portal.
- Theme bundles: Six named bundles (Academy, Nature, Midnight, Warm, Slate, Rose) set accent colour, UI font, and export header colour in one click. Eight quick accent-colour presets are also available.
- WCAG 2.1 AA: Icon-only buttons carry
aria-label; tab navigation usesrole="tablist"/role="tab"witharia-selected; axe-core audits run in CI on key pages and components.
- Offline-first: All data lives in the browser's
localStorage. No account required. - Cloud sync (optional): Supabase backend for multi-device access and multi-teacher collaboration. Sync hydrates
localStoragefrom Supabase on load and after reconnect; per-record conflicts resolve last-write-wins (newestupdatedAtwins), and offline edits queued in the pending-sync queue are protected from being clobbered by stale cloud data until they are pushed (seesrc/utils/syncMerge.ts). - Backup & restore: Export the entire dataset to JSON; restore from any prior backup.
- Admin panel: School-level management — user roles, onboarding, student anonymisation, data-retention policies.
- HestiaCP setup — shared hosting / cPanel-style VPS
- Virtualmin setup — Virtualmin VPS deployment
- Observability on a HestiaCP subdomain — Loki/Promtail/Grafana behind a dedicated HTTPS subdomain
- Grafana dashboards — what the provisioned dashboards show and how to customize them
- Magister integration — importing students from Magister SIS
- Self-hosting operations — backup/restore, upgrades, resource sizing, pg_cron setup, troubleshooting
To run the project locally:
-
Install dependencies:
npm install
-
Start the development server:
npm run dev
-
Open http://localhost:5173 in your browser.
Other useful commands:
npm run typecheck # TypeScript check (run before commits)
npm run lint # ESLint
npm run test # Vitest unit tests
npm run coverage # Coverage report
# Supabase local dev (optional)
npm run db:start # Start local Supabase stack
npm run db:reset # Reset and re-apply all migrations| Path | Page |
|---|---|
/ |
Dashboard |
/rubrics |
Rubric list |
/rubrics/new |
New rubric |
/rubrics/:id |
Rubric builder |
/rubrics/:rubricId/grade/:studentId |
Grade a student |
/rubrics/:rubricId/peer-review/:studentId |
Peer review view |
/peer-analytics/:rubricId |
Peer review analytics (consistency, feedback heatmap, reviewer trends) |
/rubrics/:rubricId/self-assess/:studentId |
Student self-assessment |
/essays |
Essay list |
/essays/new |
New essay |
/essays/:teacherKey |
Essay builder (prompt, rubric link, assign students, import submissions) |
/essays/:teacherKey/monitor |
Live essay monitor (presence, live word counts, draft preview) |
/speaking/:rubricId/:studentId |
Speaking session |
/grade-comparative/:classId/:rubricId |
Comparative grading |
/tests |
Test list |
/tests/new |
New test |
/tests/:id |
Test builder |
/tests/:testId/results/:studentTestId |
Test results, manual grading, and class-average adjustment |
/tests/:testId/monitor |
Live test monitor (presence, response grid, proctoring flags) |
/students |
Students list |
/students/:id |
Student profile |
/students/:id/cefr-overview |
Per-student CEFR overview |
/cefr-overview |
Whole-class CEFR overview |
/vocabulary |
Vocabulary Profile dashboard (CEFR vocabulary distribution per class/student, CSV export) |
/portal/:studentId |
Student portal (public) |
/test/:code |
Take a test (public, no login — answer questions, optional timer, submit) |
/attachments |
Attachment manager |
/comments |
Comment bank |
/statistics |
Statistics dashboard |
/export |
Export page |
/settings |
Settings |
/admin |
Admin panel (admin role only) |
/privacy |
Privacy statement |
RubricMaker works in two modes:
- Offline-only — data lives in the browser's local storage. No server needed. Works on GitHub Pages, SharePoint, or any static host.
- With database sync — add an optional Supabase backend for multi-device sync, email login, and rubric sharing between teachers. Hosted on your own infrastructure.
The easiest way to run the full stack. Requires Docker.
Your own laptop or school LAN:
cp .env.docker.example .env # defaults work as-is for localhost
docker-compose up -d --buildOpen http://localhost:8080. To make it accessible to other teachers on the network, set SITE_URL=http://<your-ip>:8080 in .env first.
VPS with a domain name (HTTPS):
cp .env.docker.example .env
# Edit .env:
# DOMAIN=rubricmaker.school.nl
# SITE_URL=https://rubricmaker.school.nl
# JWT_SECRET=<random 64-char string> ← change this!
# POSTGRES_PASSWORD=<strong password> ← change this!
docker-compose --profile https up -d --buildCaddy obtains a free Let's Encrypt certificate automatically. Open ports 80 and 443 on your firewall.
Enabling email login (OTP):
Without SMTP, teachers log in anonymously. To allow email-linked accounts:
# In .env:
MAILER_AUTOCONFIRM=false
SMTP_HOST=smtp.office365.com # or smtp.gmail.com, smtp-relay.brevo.com
[email protected]
SMTP_PASS=your-app-password
docker-compose up -d --force-recreate authTeachers receive an 8-digit sign-in code by email. The bundled GoTrue config sends a code-only template (public/email-templates/otp-code.html, served by the app container at /email-templates/otp-code.html) with no clickable confirmation link — some email security scanners (e.g. Microsoft Safe Links) automatically open links in incoming mail, which would consume the one-time token before the teacher can enter the code, causing "Token has expired or is invalid" errors.
Backup and restore:
./scripts/backup.sh # saves to ./backups/YYYYMMDD_HHMMSS/
./scripts/restore.sh backups/20260515_120000Updating to a new version:
git pull
docker-compose up -d --build # rebuilds the app image, restarts services
# Migrations run automatically on next startupNightly attachment cleanup (recommended):
Attachment files and their database rows are deleted automatically when they age past the owner's school retention period (default: 7 years for users not linked to a school). Schedule the bundled script with crontab -e:
0 2 * * * cd /path/to/rubricmaker && ./scripts/delete-old-attachments.sh >> /var/log/rubricmaker-cleanup.log 2>&1The script uses the Storage HTTP API — it does not delete rows directly from storage.objects (which Supabase blocks). It calls public.get_overdue_attachments() to find eligible rows, removes each file via DELETE /storage/v1/object/attachments/{path}, then cleans up the metadata rows. A 404 from storage is treated as success so orphaned DB rows are always removed.
On Supabase Cloud, schedule the delete-old-attachments edge function instead (see Supabase Dashboard → Edge Functions).
Stress-test logging (optional):
Before a school-wide rollout, you can enable a diagnostic event stream to a
client_logs table for both the teacher and student portals — useful for
running a full-class pilot and catching errors or sync failures afterwards.
# Apply migration 035_client_logs.sql (included with db:reset / docker-compose db_migrate)
# In .env:
VITE_STRESS_TEST_LOGGING=true
docker-compose up -d --buildLogged events cover user actions (by type and id only), Supabase sync results
and latency, and JS errors — never free-text content such as essay text,
comments, or grades. Query client_logs via the Supabase SQL editor
(select * from client_logs order by created_at desc). When the stress-test
window is over, unset VITE_STRESS_TEST_LOGGING and rebuild.
A standalone Loki + Promtail + Grafana stack for filtering server logs during a class pilot. Works independently of how RubricMaker itself is deployed — the combined Docker stack above, or a traditional Apache/Nginx + HestiaCP/Virtualmin server. Only Docker is required on the host running this stack.
cp .env.observability.example .env.observability
# edit RUBRICMAKER_LOG_DIR (and SUPABASE_DB_* if querying client_logs)
docker-compose -f docker-compose.observability.yml --env-file .env.observability up -dOpen http://localhost:3001 (default login admin /
admin, change via GRAFANA_ADMIN_PASSWORD). Grafana is bound to
127.0.0.1:3001 only — for remote access during a pilot, put it behind a
reverse proxy (see Observability on a HestiaCP subdomain
for a worked example with HTTPS).
Two dashboards are auto-provisioned into a RubricMaker folder: "Web &
Container Logs" (Loki — always available) and "Client Diagnostics
(client_logs)" (Postgres — populated when SUPABASE_DB_* and
VITE_STRESS_TEST_LOGGING=true are set). See
Grafana dashboards for what each panel
shows and how to customize them.
Log sources:
- Web server logs — Promtail scans
RUBRICMAKER_LOG_DIRfor*access*.log/*error*.logfiles. Set it to your panel's log directory:/var/log/virtualmin(Virtualmin),/var/log(HestiaCP — per-domain logs under/var/log/apache2/domains/or/var/log/nginx/domains/). - Combined Docker stack containers — Promtail also scrapes container
stdout/stderr for the
rubricmakercompose project directly (no extra config needed);docker/nginx.prod.confwrites access/error logs to stdout/stderr for this. client_logstable (see "Stress-test logging" above) — setSUPABASE_DB_HOST/SUPABASE_DB_NAME/SUPABASE_DB_USER/SUPABASE_DB_PASSWORDto provision a Postgres datasource in Grafana for querying app-level events. This is the one log source available regardless of deployment style, including managed Supabase Cloud projects.- Managed Supabase Cloud — there are no local containers/files for the
Supabase services themselves; use the Supabase dashboard's Log Explorer for
those. This stack still covers your web server logs and
client_logs.
No database sync — all data stays in the browser. Works on any static host.
Build:
npm run build # output in dist/Deploy the dist/ folder to GitHub Pages, Vercel, Netlify, or any web server.
SharePoint:
- Run
npm run build - In
dist/, renameindex.html→index.aspx - Upload the entire
dist/folder to a SharePoint Document Library - Click
index.aspxto launch
For Standards Integration on SharePoint, add the SharePoint domain to your Common Standards Project API key's allowed origins.