Getting started
Set up the Git hooks once after cloning:
./scripts/setup-git-hooks.shOn 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.
Choose a development loop
Section titled “Choose a development loop”| 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.
Frontend (native)
Section titled “Frontend (native)”cd frontendbun installcp .env.example .env.localbun dev # http://localhost:5173, expects backend on :8088bun run check # Svelte and TypeScript diagnosticsbun run lint # Oxfmt, Oxlint, and Svelte-specific ESLint rulesbun run dead-code # Knipbun run test # unit, browser, and Storybook Vitest projectsbun run build # production buildbun run bundle:check # stable route and eager-chunk budgetsbun run artifact:check # client credential and server-boundary scanWhen 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:
scripts/dev-frontend.sh # port 3000; LTI auto-login works herescripts/dev-frontend.sh 5176 # port 5176; sign in with the token gatescripts/dev-frontend.sh 5176 --lti # also re-point local A+ so LTI works on 5176It 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.
Backend (native)
Section titled “Backend (native)”Needs a JDK 25, or run through Docker:
cd backendFRONTEND_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 updatesThe 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.
Whole stack in Docker (frontend HMR)
Section titled “Whole stack in Docker (frontend HMR)”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:
scripts/dev-stack.sh # up; frontend HMR on :3000scripts/dev-stack.sh --profile aplus up # incl. local A+scripts/dev-stack.sh up --build -V # after package.json changesThis 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.
Documentation site
Section titled “Documentation site”This site lives in docs/. The compose stack serves the built site on
port 3001 (DOCS_PORT); for writing, use the dev server:
cd docsbun installbun run dev # http://localhost:3001bun run lint # Oxfmt and Oxlintbun run check # Astro diagnosticsbun run dead-code # Knipbun run build # link validation and static site in docs/dist/