Coding & Design Guidelines
These guidelines keep every service aligned. Keep changes focused. Mention any deviation in your pull request. Raise follow-up issues when you discover gaps.
Contributor checklist
- Run the formatter and linter for the area you touched. Examples:
pnpm run format:webapp,pnpm run lint:server. - Smoke-test the critical scripts.
pnpm run build:webappmust pass. If you changed migrations, runpnpm run db:generate-erd-docs. - Update Chromatic snapshots when you change UI components.
- Regenerate clients and documentation when you change an API contract.
- Rerun the quality gates that match your changes:
pnpm run check,pnpm run generate:api, orcd server && ./mvnw test -P'!quick'. - Document what you verified in the pull request template. Link failing checks to tickets if you must defer.
Webapp (/webapp)
Platform overview
- React 19 on Vite. Read the pinned versions from
webapp/package.json; they move faster than this page. - React Compiler is enabled in
webapp/vite.shared.ts, which every build of app source pulls in. Leave it on. - Routing uses TanStack Router file routes in
src/routes/**with the generatedrouteTree.gen.ts. - TanStack Query v5 is initialised in
src/integrations/tanstack-query/root-provider.tsx. OpenAPI helpers live insrc/api/@tanstack/react-query.gen.ts. - Global state lives in Zustand stores under
src/stores/**. - Tailwind CSS 4 powers styling. Tokens sit in
src/styles.css. shadcn/ui primitives live insrc/components/ui/**, which is a registry install over Base UI — check a change there against upstream and record a deliberate divergence in the file, because re-vendoring overwrites it. - Storybook, Chromatic, Vitest, oxlint, and Biome cover tooling.
Building features
- Create routes with
createFileRoute. Route modules handle auth gating (seesrc/routes/_authenticated.tsx), loader logic, and orchestration. - Keep presentational components in
src/components/**. They should take props, stay pure, and avoid side effects. - Fetch data by spreading generated
...Options()helpers intouseQueryoruseMutation. The helpers provide typing, query keys, and retry defaults. A hand-written query key is a lint error (hephaestus/no-manual-query-key), because it stops matching the generated one silently. - Invalidate caches with the generated
...QueryKey()helpers or the same keys passed touseQuery. Use optimistic updates only when the UI must respond instantly, such as mentor chat streaming. - Subscribe to Zustand stores with selectors (
useDocumentsStore((state) => state.documents[id])). Keep store actions pure. - Respect React Compiler requirements. No conditional hooks. No prop mutation. Because the compiler memoises for you,
useMemo,useCallbackandmemocannot be imported fromreact, and neither canforwardRef— React 19 passesrefas an ordinary prop. - Keep render deterministic. Reading the wall clock or the RNG while rendering is a lint error (
hephaestus/no-nondeterministic-render); component code takes the time from@/components/common/use-nowand stories from@/components/common/story-clock. - Stay inside the
--color-*token scale declared insrc/styles.css. Reach for shadcn/ui primitives before adding new component libraries.@applylives instyles.cssfor base and wrapper rules, not in components. - Co-locate
*.stories.tsxfiles. Show the states the component can actually reach, with realistic data. Runpnpm --filter webapp run chromatic:ciwhenever stories change. - Defer analytics work to effects that run after paint.
Quality gates
- Install dependencies with
pnpm installat the repo root so optional binaries resolve. - Run
pnpm run check:webappandpnpm run build:webappbefore opening a pull request. - Execute Vitest suites with
pnpm run test:webapp. Add unit tests beside the feature you touched. - Keep Storybook stories in sync with design. Launch
pnpm --filter webapp run storybookwhen pairing with design reviews. - Validate bundle impact by checking the Vite build stats when you add large libraries.
Spring Boot application server (/server)
Structure
- Feature packages sit directly under
de.tum.cit.aet.hephaestus—practices/,evidence/,agent/,integration/,mentor/,workspace/,leaderboard/and the rest. Shared wiring sits inconfig/andcore/. Cross-cutting integration traits (webhook ingress, oauth ingress, SPI, registry) live underintegration/directly.lsthe package root rather than trusting a list here. - Controllers return DTOs located near the controller.
LeaderboardEntryDTOis the reference example. - Repositories return
Optional<T>instead ofnull. - Services take their collaborators through the constructor, over
private finalfields; field injection with@Autowiredis not used. If a service depends on many collaborators, split the responsibility. - Set up loggers with
LoggerFactory.getLogger. Use structured log messages. - Annotate the smallest unit of write work with
@Transactional.
Coding habits
- Use Java 21 features where they simplify code. Records, pattern matching, and sealed hierarchies improve readability.
- Keep controllers thin. Push orchestration into services and prefer constructor helpers over static util classes.
- Expose dedicated DTO mappers in the package instead of sprinkling conversion logic across layers.
- Treat
Optionalas a signal for missing data. Avoid returningnullfrom repository methods.
Null safety
- Add a JSpecify
@NullMarkedpackage-info.javato every handwritten Java package. - Bare types are non-null. Use
@Nullableonly when absence is part of the contract. On API DTOs, use explicit@NonNullfor components that must berequiredin OpenAPI. - Type-use annotations describe exactly what is nullable:
List<@Nullable String>permits null elements;String @Nullable []permits a null array reference. - Fix NullAway errors at the contract or implementation boundary. Suppressions and classes without an explicit null-marking scope fail the quality gate.
- Run
pnpm run check:java-nullnessfor the policy check andcd server && ./mvnw test-compile -P'!quick' -DskipTestsfor compiler analysis.
Safe workflows
- Configuration overrides stay in
application-*.yml. Do not commitapplication-local.yml. - Share long-lived infrastructure in tests. Lean on Testcontainers and GitHub fixtures where possible.
- After updating Liquibase migrations, regenerate the REST client with
pnpm run generate:apiand rerunpnpm run db:generate-erd-docs. - Run the modular test suites with
cd server && ./mvnw verify -P'!quick'— without!quickthe build skips every test and still reports success. The build uses ArchUnit checks to enforce package boundaries. - Prefer integration tests that extend
BaseIntegrationTest, orAbstractWorkspaceIntegrationTestfor anything workspace-scoped, when you touch persistence logic. See Testing.
Webhook receiver (server/.../integration/core/webhook/)
- The provider-specific verifiers and subject parsers are Spring-free and live with their provider,
under
integration/scm/github/webhook/andintegration/scm/gitlab/webhook/;integration/core/webhook/holds the shared ingress — the controller, the ingest pipeline, the JetStream publisher and its bootstrap. All are exercised by JUnit unit tests plus parameterised tests over the captured fixtures inserver/src/test/resources/{github,gitlab}/. - HMAC verification:
MessageDigest.isEqual(constant-time, length-tolerant); hex viajava.util.HexFormat.of()only — enforced by ArchUnit (HexEncodingArchTest). Case-folding usesLocale.ROOTeverywhere — enforced byLocaleSafetyArchTest. - Spring beans (controllers,
JetStreamPublisher,WebhookJetStreamBootstrap, health, lifecycle) gated byRuntimeRole.WEBHOOK_PROPERTY. Production runs them in the dedicatedwebhook-servercontainer (SPRING_PROFILES_ACTIVE=prod,webhook) for restart independence — see ADR 0008. - Configuration is shared with auto-registration:
core.webhook.WebhookPropertiesbound tohephaestus.webhook.*. The webhook packages carry per-package JaCoCo branch floors, declared inserver/pom.xml; read them there rather than from a copy.
Docs site (/docs)
- Docusaurus 3 powers the documentation. Install dependencies with
pnpm installand runpnpm run startto preview. - Keep front matter fields (
id,title,sidebar_position) unique. Updatesidebars.*.tswhen adding new pages. - Use MDX components from
docs/src/components/**to keep styling consistent. - Run
pnpm run buildbefore committing structural changes to confirm the static export succeeds.
Database and migrations
- Liquibase
<changeSet>s (schema deltas — not release changesets) live inserver/src/main/resources/db/changelog/. A schema change also needs a release changeset — seeAGENTS.md§ Changesets (release notes). - Prefer
renameColumnormodifyDataTypewhen evolving schemas so data survives deployments. - Add indexes before backfilling large tables. Profile long migrations locally.
- After schema updates, regenerate the ERD and run server tests that touch the affected tables.
Observability and security
- Sentry is configured in both the webapp and the application server. Capture exceptions with context tags instead of console logs.
- PostHog records product analytics. Gate new events behind environment checks so tests stay deterministic.
- Store shared secrets in LastPass and load them via
.envor the respectiveapplication-*.ymlfiles. Never commit real secrets. - Rotate API keys when you onboard new external services. Document the process in the runbooks under
docs/admin. - Sanitise logged user data. Log IDs rather than emails.
Performance reminders
- Batch network calls and paginate large results.
- Cache expensive computations when you reuse them. TanStack Query handles client caching. Use memoised services or projections on the server.
- Measure before tuning. Add logging or metrics when you touch critical latency paths.
- Profile React work with the DevTools profiler and Chrome Performance panel. Profile Java code with JFR and async-profiler.
When unsure
Record trade-offs in your pull request. Ask for a second review on risky work. Suggest improvements to this page when you find a better pattern.