Skip to content

Getting started

Set up the Git hooks once after cloning:

Terminal window
./scripts/setup-git-hooks.sh

On commit, the hooks check formatting and lint the frontend and documentation files being committed. On push, they run svelte-check and the tests affected by your frontend changes. Configuration, dependency, and file-removal changes run the full frontend suite instead. Backend checks run when the push changes the backend, and CI (.forgejo/workflows/ci.yml) still runs everything. Run ./scripts/verify.sh from the repository root for the same complete gate.

Install Chromium once before running browser-mode tests: cd frontend && bunx playwright install chromium. CI also runs the editor, selection, focus, and grading interaction subset in WebKit. Run it locally with bunx playwright install webkit followed by bun run test:browser:webkit.

Goal Start here
Frontend work with the fastest reload Native frontend, with a native or Docker backend
Backend work with fast test feedback Native backend on JDK 25
Integrated work against a simple local A+ scripts/dev-stack.sh --profile aplus up
Realistic product and release QA Comprehensive local test course

The native loops require Bun for the frontend and JDK 25 for the backend. Docker can run the full stack when you do not want those runtimes installed on the host.

Terminal window
cd frontend
bun install
cp .env.example .env.local
bun dev # http://localhost:5173, expects backend on :8088
bun run check # Svelte and TypeScript diagnostics
bun run lint # Oxfmt, Oxlint, and Svelte-specific ESLint rules
bun run dead-code # Knip
bun run test # unit, browser, and Storybook Vitest projects
bun run build # production build
bun run bundle:check # stable route and eager-chunk budgets
bun run artifact:check # client credential and server-boundary scan

When you want the native dev frontend to talk to the local dockerised A+ (to load real submissions, and for LTI auto-login), run it through the launcher instead of bare bun dev; it wires the endpoints the frontend cannot infer on its own and works on any port, so parallel worktrees don’t clash:

Terminal window
scripts/dev-frontend.sh # port 3000; LTI auto-login works here
scripts/dev-frontend.sh 5176 # port 5176; sign in with the token gate
scripts/dev-frontend.sh 5176 --lti # also re-point local A+ so LTI works on 5176

It sets APLUS_URL=http://localhost:8000 for browser-facing A+ links plus the LTI variables. If you prefer a committed env file, copy frontend/.env.example to frontend/.env.local. First bring the shared stack up with docker compose --profile aplus up -d.

Needs a JDK 25, or run through Docker:

Terminal window
cd backend
FRONTEND_SESSION_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= \
FRONTEND_SERVICE_SECRET=local-frontend-service-secret-12345 \
ALLOW_IN_MEMORY_DATABASE=true ./gradlew run # http://localhost:8088, ephemeral H2
./gradlew check # Detekt, Spotless, compilation, and tests
../scripts/test-backend-postgres.sh # disposable PostgreSQL 18 integration tests
../scripts/check-backend-dependencies.sh # report stable dependency updates

The local service secret matches frontend/.env.example; the fixed encryption key is suitable only for disposable development sessions. POSTGRES_URL is required unless ALLOW_IN_MEMORY_DATABASE=true selects ephemeral H2 for native development. H2 data does not persist across restarts. The PostgreSQL integration script starts an isolated container and initializes the schema twice. It tests database constraints and concurrent persistence, then removes the container and volume.

Native bun dev above is the fastest loop for frontend work. When you want the whole stack in Docker (e.g. against the local A+, or without a JDK) but still with frontend hot reload, use the dev overrides in docker-compose.dev.yml:

Terminal window
scripts/dev-stack.sh # up; frontend HMR on :3000
scripts/dev-stack.sh --profile aplus up # incl. local A+
scripts/dev-stack.sh up --build -V # after package.json changes

This swaps only the frontend to a bind-mounted Vite dev server; the backend stays prod-shaped (Gradle has no real hot reload; iterate on it natively and up --build backend when needed). The file is deliberately not named docker-compose.override.yml, so plain docker compose up keeps producing the production-shaped stack.

For a seeded course with 100 students, grading histories, feedback documents, email, LTI, templates, and manual ZIP grading, use the comprehensive test course instead of building fixtures by hand.

This site lives in docs/. The compose stack serves the built site on port 3001 (DOCS_PORT); for writing, use the dev server:

Terminal window
cd docs
bun install
bun run dev # http://localhost:3001
bun run lint # Oxfmt and Oxlint
bun run check # Astro diagnostics
bun run dead-code # Knip
bun run build # link validation and static site in docs/dist/