Skip to content

Development Guide

NesiciCoding edited this page May 15, 2026 · 2 revisions

Development Guide

Starting the dev server

npm run dev

Vite starts a local server at http://localhost:5173 with hot-module replacement. Most changes are reflected immediately without a browser reload.


Available scripts

Script Command Description
Dev server npm run dev Start local development server (HMR)
Production build npm run build Type-check then bundle for production
Preview build npm run preview Serve the dist/ folder locally
Type-check npm run typecheck Run tsc --noEmit — no output means success
Lint npm run lint Run ESLint over src/
Test (once) npm run test Run Vitest in run mode (no watch)
Test (UI) npm run test:ui Open Vitest browser UI for interactive testing
Coverage npm run coverage Run tests and generate a coverage report

Testing

The project uses Vitest with Testing Library and jsdom.

# Run the full test suite once
npm run test

# Interactive browser UI (great for debugging a specific test)
npm run test:ui

# Coverage report (output in coverage/)
npm run coverage

Tests are co-located with the source files they test, inside __tests__/ subdirectories. Open coverage/index.html in a browser to browse the detailed line-by-line coverage report.

Writing tests

  • Use src/test-utils/renderWithProviders.tsx to render components that need React Context (AppContext, ToastContext).
  • Mock browser APIs (localStorage, Speech Recognition) at the top of the test file, not inside individual tests.
  • Keep tests focused: one behaviour per it() block.

Linting

ESLint is configured in eslint.config.js with:

  • typescript-eslint for TypeScript-aware rules
  • eslint-plugin-react-hooks to enforce the Rules of Hooks
  • eslint-plugin-react-refresh to catch patterns that break Vite HMR
npm run lint

Fix auto-fixable issues:

npx eslint src --fix

TypeScript

npm run typecheck

Runs tsc --noEmit against the full project. No output means no type errors. The CI pipeline runs this before the test suite on every push and pull request.

The project targets ES2022 with strict mode enabled. All new code should be fully typed — avoid any.


Project structure at a glance

src/
├── components/    # Reusable UI components, each with an __tests__/ folder
├── pages/         # Top-level route components
├── context/       # React Context (global state: rubrics, students, grades)
├── hooks/         # Custom React hooks
├── services/      # External integrations (Standards API, Microsoft Graph)
├── store/         # localStorage persistence layer
├── utils/         # Pure utility functions (grade calc, PDF/DOCX export, OCR)
├── types/         # Shared TypeScript type definitions
├── data/          # Static data (CEFR descriptors, VO tracks, templates)
└── locales/       # i18n translation files (en.json, nl.json)

See Architecture for a detailed breakdown.


CI pipeline

GitHub Actions runs on every push and pull request to main:

  1. Type-checknpm run typecheck
  2. Lintnpm run lint
  3. Test with coveragenpm run coverage

The coverage report is uploaded as an artifact and retained for 14 days. A pull request is expected to keep all three checks green.


Working with Supabase locally

If you want to develop or test the Supabase sync feature:

# Start the local Supabase stack (requires Docker Desktop)
supabase start

# The CLI prints a local URL + anon key.
# Paste them into Settings → Database → Connect & Sync,
# or add them to .env.local:
# VITE_SUPABASE_URL=http://127.0.0.1:54321
# VITE_SUPABASE_ANON_KEY=<printed-key>

Browse the local database at http://127.0.0.1:54323 (Supabase Studio).

# Stop the local stack when done
supabase stop

See the Supabase Sync page for cloud setup and the full migration reference.


Working with the state layer

All application state lives in AppContext (src/context/AppContext.tsx). It reads from and writes to localStorage via src/store/storage.ts.

  • Read state with useContext(AppContext) (or the useAppContext helper hook).
  • Dispatch actions through the context's provided setter functions — do not write to localStorage directly from components.
  • For tests, use renderWithProviders which wraps the component in a pre-seeded AppContext.

Adding a new page / route

  1. Create src/pages/MyPage.tsx.
  2. Add the route in src/App.tsx inside the <Routes> block.
  3. Add a navigation entry in src/components/Layout/Sidebar.tsx (and optionally Topbar.tsx).
  4. Add translation keys for the page title and any UI strings in src/locales/en.json and src/locales/nl.json.

Clone this wiki locally