docs(adr): convert all ADRs to MADR 2.1.2 format

Rewrites all 12 frontend ADRs from a custom structure to the MADR 2.1.2
template required by the VS Code ADR Manager extension: bullet metadata
(Status/Date), standardised section headings, "Chosen option: X, because Y"
wording, and explicit Pros/Cons blocks per option.
This commit is contained in:
2026-04-26 16:50:34 +02:00
parent 8f2632f456
commit c8e2fba13e
12 changed files with 478 additions and 191 deletions
@@ -1,21 +1,47 @@
# ADR 0001: Migration from Next.js/React to Angular
# Migrate frontend from Next.js to Angular
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
The project was initially built with Next.js (React). The development team already maintained an existing Angular stack for other projects. Running two separate frontend frameworks in parallel created an unnecessary knowledge split and increased maintenance overhead.
The project was initially built with Next.js (React). The development team already maintained an existing Angular stack for other projects, creating a knowledge split between two frontend frameworks. Server-side rendering — Next.js's main differentiator — was not required: all data is user-specific and served via authenticated API calls.
Server-side rendering, which is Next.js's main differentiator, was not required for this application — all data is user-specific and served via authenticated API calls, making SSR provide little to no benefit.
## Considered Options
## Decision
- Keep Next.js/React
- Migrate to Angular
- Migrate to Vue or Svelte
Migrate the frontend to Angular, reusing the existing Angular expertise and toolchain. The Next.js/React code (pages, components, utilities, middleware) is abandoned entirely.
## Decision Outcome
## Consequences
Chosen option: "Migrate to Angular", because the team already maintained an Angular stack for other projects, making it the path of least resistance with no additional knowledge split.
- **Positive:** Single frontend framework across projects. Reuse of existing Angular expertise, libraries, and patterns.
- **Positive:** No SSR complexity. The app is a pure SPA, which fits Angular's model.
- **Negative:** Complete rewrite of frontend code. No incremental migration path between React and Angular.
- **Residual:** Next.js artifacts (`.next/`, `next.config.js`, `src/modules/`, `src/middleware.ts`, etc.) were removed as part of the cleanup after the migration.
### Positive Consequences
- Single frontend framework across projects — reuse of existing expertise, libraries, and patterns.
- No SSR complexity; the app is a pure SPA, which fits Angular's model naturally.
### Negative Consequences
- Complete rewrite — no incremental migration path exists between React and Angular.
- Next.js artifacts (`.next/`, `src/modules/`, `src/middleware.ts`, etc.) required a dedicated cleanup pass after the migration.
## Pros and Cons of the Options
### Keep Next.js/React
- Good, because no rewrite cost.
- Bad, because maintaining two frontend frameworks in parallel increases overhead.
- Bad, because SSR provides no benefit for this authenticated, user-specific application.
### Migrate to Angular
- Good, because leverages existing team expertise.
- Good, because consistent toolchain across projects.
- Bad, because full rewrite required.
### Migrate to Vue or Svelte
- Good, because smaller bundle size.
- Bad, because no existing team expertise — introduces a third framework.
@@ -1,20 +1,42 @@
# ADR 0002: Standalone Components Without NgModules
# Use Angular standalone components without NgModules
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Historically, Angular required all components, directives, and pipes to be declared inside NgModules, which acted as compilation units. Angular 14 introduced standalone components as an opt-in alternative. Angular 17+ made them the recommended default and they became the foundation of the new `@angular/build:application` builder.
Angular historically required all components, directives, and pipes to be declared inside NgModules. Angular 14 introduced standalone components as an opt-in. Angular 17+ made them the recommended default and they are required for the esbuild-based application builder. How should the application be structured?
## Decision
## Considered Options
The entire application uses standalone components with no NgModules. The app entry point is `app.config.ts` (providers) + `app.routes.ts` (routing), with no `AppModule`.
- Standalone components (no NgModules)
- NgModule-based architecture
## Consequences
## Decision Outcome
- **Positive:** Each component explicitly declares its own `imports`, making dependencies visible and reducing hidden coupling.
- **Positive:** Better tree-shaking — unused imports in one component don't affect others.
- **Positive:** Aligns with Angular's long-term direction; compatible with the esbuild-based application builder.
- **Negative:** Slightly more verbose component decorators (each must list its own `imports`).
- **Note:** `CommonModule` should not be imported wholesale; use specific primitives (`NgClass`, `DatePipe`, etc.) or the newer `@if`/`@for` control flow syntax instead.
Chosen option: "Standalone components (no NgModules)", because they are the Angular-recommended default since v17, provide explicit per-component dependency declarations, and are required for the esbuild application builder.
### Positive Consequences
- Each component explicitly declares its own `imports` — dependencies are visible and not hidden in a shared module.
- Better tree-shaking: unused imports in one component don't affect others.
- Aligns with Angular's long-term direction.
### Negative Consequences
- Slightly more verbose component decorators — each component lists its own imports rather than inheriting from a module.
## Pros and Cons of the Options
### Standalone components (no NgModules)
- Good, because explicit dependency declarations per component.
- Good, because required for the esbuild application builder.
- Good, because Angular-recommended default since v17.
- Bad, because more verbose decorators.
### NgModule-based architecture
- Good, because familiar pattern for developers coming from Angular < 14.
- Bad, because deprecated direction; incompatible with the modern application builder.
- Bad, because shared module patterns hide dependencies.
@@ -1,28 +1,56 @@
# ADR 0003: Dual UI Library — Progressive Migration Strategy
# Coexist Angular Material and Bootstrap during UI library consolidation
**Date:** 2026-04-26
**Status:** Accepted (transitional)
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
The application currently uses two UI libraries simultaneously:
The application currently uses two UI libraries: Angular Material (interactive components) and Bootstrap 5 (layout, spacing utilities). Bootstrap was the original styling layer; Angular Material was introduced incrementally. Maintaining both increases bundle size, creates styling inconsistencies, and adds cognitive overhead. Which strategy should be adopted?
- **Angular Material** — component library (buttons, dialogs, tables, forms, etc.)
- **Bootstrap 5** — layout utilities, typography, spacing, and some components
## Considered Options
This coexistence is a legacy of a progressive migration: Bootstrap was the original styling layer, and Angular Material was introduced incrementally. Maintaining both libraries increases bundle size, creates styling inconsistencies, and adds cognitive overhead.
- Keep both libraries and consolidate later
- Consolidate to Angular Material only
- Consolidate to Bootstrap only
- Migrate to a different library (PrimeNG, Spartan/shadcn-angular, etc.)
## Decision
## Decision Outcome
Accept the dual-library state as a transitional phase. The long-term goal is to consolidate to a single UI library. Neither Angular Material nor Bootstrap is guaranteed to be the final choice — the decision will be made when consolidation is actively prioritised, potentially in favour of a different library altogether.
Chosen option: "Keep both libraries and consolidate later", because a big-bang UI rewrite is not justified at this stage and the final target library has not yet been chosen.
In the meantime:
### Positive Consequences
- Angular Material handles interactive components (dialogs, forms, navigation).
- Bootstrap handles layout (grid, spacing utilities) and fills gaps where Material has no equivalent.
- No immediate rewrite required — development velocity is preserved.
- Each library covers the other's gaps during the transition period.
## Consequences
### Negative Consequences
- **Positive:** No big-bang UI rewrite required at this stage.
- **Negative:** Two styling systems in tension. Bootstrap's `@import`-based Sass is incompatible with the `@use` module system; Dart Sass deprecation warnings are silenced in `angular.json` as a workaround (see ADR 0012).
- **Future action:** When consolidation is prioritised, evaluate the available options (Material 3, PrimeNG, Spartan/shadcn-angular, or custom) and supersede this ADR.
- Two styling systems in tension, leading to inconsistent UX.
- Bootstrap's `@import`-based Sass is incompatible with the `@use` module system, requiring a deprecation workaround (see ADR 0012).
- Larger CSS bundle than a single-library solution.
## Pros and Cons of the Options
### Keep both libraries and consolidate later
- Good, because no rewrite cost now.
- Bad, because styling inconsistency and larger bundle persist until consolidation.
### Consolidate to Angular Material only
- Good, because single coherent design system.
- Bad, because Bootstrap layout utilities would need to be replaced.
### Consolidate to Bootstrap only
- Good, because removes Angular Material dependency.
- Bad, because Angular Material components (dialogs, date pickers, etc.) would need rebuilding.
### Migrate to a different library
- Good, because could address limitations of both current libraries.
- Bad, because highest rewrite cost; unknown tradeoffs until evaluated.
## Links
- Related to [ADR 0012](0012-scss-bootstrap-import-deprecation.md)
@@ -1,29 +1,46 @@
# ADR 0004: HTTP Interceptors Pipeline
# Handle API base URL, auth token, and errors via HTTP interceptors
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Every API call requires the same three cross-cutting concerns:
Every API call requires three cross-cutting concerns: prepending the API base URL, attaching the JWT bearer token, and unwrapping error responses into a consistent shape. How should these be applied across all HTTP calls without duplicating logic in each service?
1. Prepend the API base URL so services don't hard-code it.
2. Attach the JWT bearer token when one exists.
3. Unwrap error responses into a consistent shape.
## Considered Options
Handling these ad hoc in each service would duplicate logic and couple services to environment details.
- Three chained HTTP interceptors
- Per-service base URL and token handling
- Single utility wrapper function
## Decision
## Decision Outcome
Three functional interceptors are chained in order in `app.config.ts`:
Chosen option: "Three chained HTTP interceptors", because they centralise cross-cutting concerns transparently and require no changes to individual services.
1. **`apiInterceptor`** — prepends `environment.apiBaseUrl` to every outgoing request URL. Consequence: all services call relative paths (e.g. `/skydive/jumps`), never absolute URLs.
2. **`tokenInterceptor`** — attaches `Authorization: Token <jwt>` when a token exists in `JwtService`.
3. **`errorInterceptor`** — unwraps `err.error` from HttpErrorResponse and rethrows, so subscribers receive the API error payload directly.
### Positive Consequences
## Consequences
- Services use relative paths (e.g. `/skydive/jumps`) — environment-agnostic.
- Auth header injection is transparent to all services.
- Uniform error payload shape across the entire application.
- **Positive:** Services are environment-agnostic. Swapping the API base URL requires changing only `environment.ts`.
- **Positive:** Auth header injection is transparent to all services.
- **Positive:** Error handling is uniform across the application.
- **Constraint:** Services must always use relative paths starting with `/`. Absolute URLs would get the base URL prepended, breaking them.
### Negative Consequences
- Services must always use relative paths starting with `/`. Passing an absolute URL would cause the base URL to be prepended twice.
## Pros and Cons of the Options
### Three chained HTTP interceptors
- Good, because zero duplication — logic defined once, applied everywhere.
- Good, because services remain unaware of environment or auth details.
- Bad, because interceptors apply globally — opting out requires explicit handling.
### Per-service base URL and token handling
- Good, because each service controls its own behaviour explicitly.
- Bad, because duplicates the same logic across every service.
### Single utility wrapper function
- Good, because more explicit than interceptors.
- Bad, because all service calls must go through the wrapper — easy to bypass accidentally.
@@ -1,26 +1,48 @@
# ADR 0005: JWT Authentication Stored in localStorage
# Store JWT authentication token in localStorage
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
The application requires persistent authentication across browser sessions. The two main options for token storage are:
The application requires persistent authentication across browser sessions. Where should the JWT token be stored client-side?
- **`localStorage`** — accessible via JavaScript; survives tab/window close; vulnerable to XSS.
- **httpOnly cookies** — inaccessible to JavaScript; protected from XSS; requires CSRF protection.
## Decision Drivers
The application is not publicly accessible — it is used internally by a known, controlled user base. The attack surface for XSS is limited.
- Application is internal — used by a known, controlled user base; not publicly accessible.
- Implementation simplicity.
- Compatibility with Angular functional guards and the APP_INITIALIZER auth bootstrap.
## Decision
## Considered Options
Store the JWT token in `localStorage` under the key `jwtToken`. `JwtService` handles read/write/delete operations. `UserService` exposes `currentUser` (BehaviorSubject) and `isAuthenticated` (ReplaySubject) observables consumed by guards and the layout.
- Store JWT in localStorage
- Store JWT in an httpOnly cookie
An `APP_INITIALIZER` in `app.config.ts` calls `userService.getCurrentUser()` on startup if a token exists, ensuring guards can rely on `isAuthenticated` being populated before the first navigation.
## Decision Outcome
## Consequences
Chosen option: "Store JWT in localStorage", because the application is internal with a controlled user base, making the XSS risk acceptable, and localStorage avoids the additional CSRF complexity of cookie-based auth.
- **Positive:** Simple implementation. No server-side session management. Works seamlessly with Angular's functional guards.
- **Positive:** Token persists across browser restarts without requiring re-login.
- **Risk:** XSS attacks could exfiltrate the token. Acceptable given the non-public nature of the application.
- **Future consideration:** If the application becomes publicly accessible or handles sensitive data, migrate to httpOnly cookies with CSRF tokens.
### Positive Consequences
- Simple implementation — no server-side session management.
- Token persists across browser restarts without requiring re-login.
- Works seamlessly with Angular functional guards and `APP_INITIALIZER`.
### Negative Consequences
- Accessible to JavaScript — an XSS attack could exfiltrate the token.
- If the application ever becomes publicly accessible or handles sensitive data, this decision must be revisited.
## Pros and Cons of the Options
### Store JWT in localStorage
- Good, because simple to implement and operate.
- Good, because survives browser restarts.
- Bad, because XSS-vulnerable.
### Store JWT in an httpOnly cookie
- Good, because inaccessible to JavaScript — XSS-safe.
- Bad, because requires CSRF protection (SameSite cookie policy or CSRF tokens).
- Bad, because more complex server-side coordination.
@@ -1,30 +1,42 @@
# ADR 0006: Data Loading via Route Resolvers
# Pre-fetch route data using Angular route resolvers
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Route components need data to render. Two common approaches exist:
Route components need data to render. Should that data be fetched before the route activates (resolver) or after the component renders (in-component)?
- **In-component fetching** (`ngOnInit`): component renders first in a loading state, then fetches data asynchronously.
- **Route resolvers**: data is fetched before the route activates; the component receives it immediately via `ActivatedRoute.data`.
## Considered Options
Loading states scattered across every component lead to inconsistent UX and duplicate skeleton/spinner logic.
- Angular route resolvers
- In-component fetching (ngOnInit)
## Decision
## Decision Outcome
Use Angular route resolvers (under `src/app/core/resolvers/`) to pre-fetch data for each route. New route components should follow this pattern rather than fetching in `ngOnInit`.
Chosen option: "Angular route resolvers", because components receive data immediately via `ActivatedRoute.data`, eliminating the need for per-component loading states on initial render.
Data is accessed in components via:
### Positive Consequences
```ts
this.route.data.subscribe((data) => { ... });
```
- No loading state needed in components for the initial data fetch.
- Loading indication can be centralised at the router level via router events.
- Data availability is guaranteed before any component lifecycle hook runs.
## Consequences
### Negative Consequences
- **Positive:** Components have their data available immediately — no need for per-component loading states on initial render.
- **Positive:** Loading indication is centralised at the router level (can use router events to show a global progress bar).
- **Negative:** Navigation appears "stuck" while the resolver fetches — there is no partial render before data arrives. Acceptable given the API response times in this application.
- **Constraint:** Resolvers should handle errors gracefully (redirect or return a default value) to avoid navigation hangs on API failure.
- Navigation appears paused while the resolver fetches — there is no partial render before data arrives.
- Resolver errors must be handled explicitly to avoid navigation hangs on API failure.
## Pros and Cons of the Options
### Angular route resolvers
- Good, because data is available immediately in the component.
- Good, because loading logic is centralised outside the component.
- Bad, because navigation is blocked until the resolver completes.
### In-component fetching (ngOnInit)
- Good, because navigation is instant — component renders immediately.
- Bad, because each component must manage its own loading and error states.
- Bad, because inconsistent UX across routes if not implemented uniformly.
@@ -1,20 +1,43 @@
# ADR 0007: Build Tool — Angular CLI with esbuild
# Use the esbuild-based Angular application builder
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Angular 17 introduced a new application builder (`@angular/build:application`) based on esbuild and Vite, replacing the legacy webpack-based `@angular-devkit/build-angular:browser` builder. The new builder delivers significantly faster cold builds and near-instant incremental rebuilds.
Angular 17 introduced a new application builder (`@angular/build:application`) based on esbuild and Vite, replacing the legacy webpack-based `@angular-devkit/build-angular:browser` builder. Which builder should be used for development and production builds?
## Decision
## Considered Options
Use the `@angular-devkit/build-angular:application` builder (esbuild-based) as configured in `angular.json`. The entry point is defined via the `browser` field rather than `main`.
- `@angular/build:application` esbuild-based (Angular 17+)
- `@angular-devkit/build-angular:browser` — webpack-based (legacy)
## Consequences
## Decision Outcome
- **Positive:** Build times are 35× faster than the webpack builder for both development and production.
- **Positive:** Native ESM output. Better code splitting.
- **Positive:** Aligns with Angular's recommended toolchain for Angular 17+.
- **Negative:** Some webpack-specific configuration options (e.g. custom webpack plugins) no longer apply. Not an issue for this project.
- **Note:** The `sass` sub-option under `stylePreprocessorOptions` (for `silenceDeprecations`) is specific to this builder's schema; the Karma test builder uses a different schema and does not support it.
Chosen option: "`@angular/build:application`", because it is the Angular-recommended builder since v17, delivers significantly faster builds, and is required for certain schema-level features such as `stylePreprocessorOptions.sass.silenceDeprecations`.
### Positive Consequences
- Build times are 35× faster than the webpack builder for development and production.
- Native ESM output with better code splitting.
- Aligns with Angular's recommended toolchain for v17+.
### Negative Consequences
- Webpack-specific configuration options (custom plugins, loaders) no longer apply. Not an issue for this project.
- The `sass` sub-option under `stylePreprocessorOptions` is specific to this builder's schema; the Karma test builder uses a stricter schema and does not support it.
## Pros and Cons of the Options
### `@angular/build:application` (esbuild)
- Good, because significantly faster cold builds and incremental rebuilds.
- Good, because Angular-recommended default since v17.
- Good, because required for `sass.silenceDeprecations` support.
- Bad, because webpack plugin ecosystem is not compatible.
### `@angular-devkit/build-angular:browser` (webpack)
- Good, because supports custom webpack configuration.
- Bad, because deprecated direction; slower builds.
- Bad, because does not support the `sass.silenceDeprecations` option.
+43 -13
View File
@@ -1,21 +1,51 @@
# ADR 0008: Testing — Karma + Jasmine
# Keep Karma + Jasmine as the test stack
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Angular's default test setup uses Karma as the test runner (executing tests in a real Chrome browser) and Jasmine as the assertion/spec framework. Alternatives include Jest (runs in Node via jsdom, no real browser) and Vitest (ESM-native, faster).
Angular's default test setup uses Karma as the test runner (executing tests in a real Chrome browser) and Jasmine as the spec framework. Faster alternatives exist. Should the test stack be replaced?
Migrating to Jest or Vitest would require changing test utilities, removing `zone.js/testing`, and potentially adjusting Angular testing module setup.
## Considered Options
## Decision
- Karma + Jasmine (Angular default)
- Jest (Node/jsdom, no real browser)
- Vitest (ESM-native, no real browser)
Keep Karma + Jasmine. The test suite (54 specs) is small enough that Karma's startup overhead is not a bottleneck. The default Angular TestBed utilities work without modification.
## Decision Outcome
## Consequences
Chosen option: "Karma + Jasmine", because the test suite (54 specs) is small enough that Karma's startup overhead is not a bottleneck, and migration would require replacing test utilities and adjusting Angular `TestBed` setup with no functional gain at the current scale.
- **Positive:** Zero migration effort. Full compatibility with Angular's `TestBed` and `ComponentFixture` APIs.
- **Positive:** Tests run in a real browser, catching browser-specific issues that jsdom misses.
- **Negative:** Slower startup than Jest/Vitest due to Chrome launch. Acceptable at the current test suite size.
- **Future consideration:** If the test suite grows significantly or CI performance becomes a concern, evaluate migration to Jest with `jest-preset-angular`.
### Positive Consequences
- Zero migration effort — full compatibility with Angular's `TestBed` and `ComponentFixture` APIs.
- Tests run in a real Chrome browser, catching browser-specific issues that jsdom misses.
### Negative Consequences
- Slower startup than Jest or Vitest due to Chrome launch. Acceptable at the current test suite size.
## Pros and Cons of the Options
### Karma + Jasmine
- Good, because zero migration cost from the Angular default.
- Good, because tests run in a real browser environment.
- Bad, because slower startup than headless alternatives.
### Jest
- Good, because fast — runs in Node via jsdom with no browser launch.
- Bad, because requires `jest-preset-angular`, removal of `zone.js/testing`, and test utility changes.
- Bad, because jsdom is not a real browser — some browser-specific behaviours are not caught.
### Vitest
- Good, because ESM-native, fastest option.
- Bad, because Angular `TestBed` integration requires additional configuration.
- Bad, because jsdom limitation applies here too.
## Links
- Future consideration: if the test suite grows significantly or CI performance becomes a concern, evaluate migration to Jest with `jest-preset-angular`.
@@ -1,27 +1,60 @@
# ADR 0009: Pre-commit Quality Gates with Husky and lint-staged
# Enforce quality gates at commit time with Husky and lint-staged
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Without automated enforcement, code quality rules (linting, formatting, tests) are only checked in CI or not at all if there is no CI pipeline. Issues are discovered late, after a commit is already in the repository.
Without automated enforcement, code quality rules (linting, formatting, tests) are discovered late — in CI, or not at all. Should quality checks be enforced at commit time?
## Decision
## Decision Drivers
Husky manages a `pre-commit` hook that runs:
- Issues should be caught before they enter the repository, not after.
- Formatting should be applied automatically so developers don't need to run it manually.
- The correct Node version must be active during hook execution regardless of the shell environment.
1. **Full test suite** (`npm test -- --watch=false`) — blocks commit on any failing spec.
2. **lint-staged** — on staged files only:
- `prettier --write` on `*.{ts,html,scss,json,md}`
- `eslint --max-warnings=0` on `*.ts`
- `eslint --max-warnings=0` on `*.html`
## Considered Options
- Husky + lint-staged pre-commit hook
- CI-only enforcement (no local hooks)
- No automated enforcement
## Decision Outcome
Chosen option: "Husky + lint-staged pre-commit hook", because it prevents lint errors and failing tests from entering the repository, and auto-fixes formatting on commit without developer intervention.
The hook runs in order:
1. Full test suite (`npm test -- --watch=false`) — blocks commit on any failing spec.
2. `lint-staged` on staged files: `prettier --write` on `*.{ts,html,scss,json,md}`, then `eslint --max-warnings=0` on `*.ts` and `*.html`.
nvm is initialised inside the hook script to ensure the correct Node version (pinned in `.nvmrc`) is active regardless of the shell environment that triggered the commit.
## Consequences
### Positive Consequences
- **Positive:** No lint errors or failing tests can enter the repository via a normal commit.
- **Positive:** Prettier auto-fixes formatting on commit — developers don't need to run it manually.
- **Negative:** Commits are slower (~1020 s for the full test suite). Acceptable trade-off for a solo project.
- **Constraint:** Requires nvm to be installed on the developer's machine. The hook sources `$HOME/.nvm/nvm.sh` directly.
- No lint errors or failing tests can enter the repository via a normal commit.
- Prettier auto-fixes formatting on commit — developers don't need to run it manually.
### Negative Consequences
- Commits are slower (~1020 s for the full test suite). Acceptable trade-off for a solo project.
- Requires nvm to be installed on the developer's machine. The hook sources `$HOME/.nvm/nvm.sh` directly.
## Pros and Cons of the Options
### Husky + lint-staged
- Good, because issues are caught before they enter the repository.
- Good, because formatting is applied automatically.
- Bad, because adds latency to every commit.
- Bad, because requires nvm on the developer's machine.
### CI-only enforcement
- Good, because no local overhead.
- Bad, because broken commits are discovered only after push.
### No automated enforcement
- Good, because zero overhead.
- Bad, because quality degrades without consistent manual discipline.
@@ -1,19 +1,51 @@
# ADR 0010: Node Version Management with nvm and .nvmrc
# Pin Node version with nvm and .nvmrc
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Angular 21 requires Node.js >= 20.19 or >= 22.12. The system Node version on a development machine may not meet this requirement, causing silent build failures or CLI errors.
Angular 18 requires Node.js `^18.19.1 || ^20.11.1`. The system Node version on a development machine may not meet this requirement, causing silent build failures or CLI errors. How should the required Node version be enforced?
## Decision
## Considered Options
A `.nvmrc` file at the repository root pins the Node version to `20.19.6`. Developers use `nvm use` to switch to this version. The Husky pre-commit hook explicitly calls `nvm use 20.19.6` before running tests to ensure the correct version is active in the hook's shell environment.
- nvm with `.nvmrc`
- System Node (no version management)
- Other version managers (fnm, volta)
## Consequences
## Decision Outcome
- **Positive:** Consistent Node version across all commands (dev server, build, tests, pre-commit hook).
- **Positive:** `.nvmrc` documents the required Node version explicitly in the repository.
- **Constraint:** Requires nvm. Developers using other version managers (fnm, volta) must align manually.
- **Note:** The `package.json` `engines` field also declares the required Node range (`^20.19.0 || ^22.12.0`) as a secondary signal.
Chosen option: "nvm with `.nvmrc`", because it pins the version explicitly in the repository, is already in use on the development machine, and integrates directly with the Husky pre-commit hook.
A `.nvmrc` file at the repository root pins Node to `20.19.6`. The Husky pre-commit hook explicitly calls `nvm use 20.19.6` before running tests to ensure the correct version is active in the hook's shell environment.
### Positive Consequences
- Consistent Node version across all commands: dev server, build, tests, pre-commit hook.
- `.nvmrc` documents the required Node version explicitly in the repository.
### Negative Consequences
- Requires nvm. Developers using other version managers (fnm, volta) must align manually.
## Pros and Cons of the Options
### nvm with `.nvmrc`
- Good, because explicit version pinning visible in the repository.
- Good, because integrates with Husky hook via `nvm use`.
- Bad, because tied to nvm — other version managers need manual alignment.
### System Node
- Good, because no tooling required.
- Bad, because version is not pinned — any system upgrade can silently break the build.
### Other version managers (fnm, volta)
- Good, because faster and more ergonomic than nvm for some workflows.
- Bad, because not the currently installed tool — switching would require migration.
## Links
- The `package.json` `engines` field (`^18.19.1 || ^20.11.1`) provides a secondary signal for tooling that reads it.
@@ -1,32 +1,46 @@
# ADR 0011: Feature Domain Organisation
# Organise code by functional domain
**Date:** 2026-04-26
**Status:** Accepted
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
The application covers four independent functional areas, each with its own data model, API backend, and user audience:
The application covers four independent functional areas with distinct data models, API backends, and user audiences: Skydive, CMS, E-commerce, and Hero Wars. Without explicit organisational boundaries, code from different domains would intermingle, making each area harder to reason about independently. How should the codebase be structured?
- **Skydive** — jump logbook, statistics, QCM exam preparation.
- **CMS** — articles, pages, user profiles.
- **E-commerce** — product catalogue by category.
- **Hero Wars** — analytics dashboard for a game guild (static weekly snapshots).
## Considered Options
Without an explicit organisational boundary, code from different domains would intermingle, making it harder to reason about and maintain each area independently.
- Domain-based organisation (one folder per functional area)
- Flat feature-based organisation (one folder per component type)
## Decision
## Decision Outcome
Services, routes, and API prefixes are organised by domain:
Chosen option: "Domain-based organisation", because it mirrors the backend's route grouping, making the full-stack data flow traceable, and allows each domain to be understood and modified independently.
- Services under `src/app/core/services/<domain>/`
- Routes composed from `auth.routes.ts` and `noauth.routes.ts`, with domain-specific guards (`authGuard`, `adminGuard`)
- API service domains: `/skydive`, `/cms`, `/ecommerce`, `/herowars`
Services are placed under `src/app/core/services/<domain>/`, API prefixes are `/skydive`, `/cms`, `/ecommerce`, `/herowars`, and routes are composed from `auth.routes.ts` and `noauth.routes.ts` with domain-specific guards.
This structure mirrors the backend's route grouping (`src/routes/api/<domain>/`), making the full-stack data flow traceable.
### Positive Consequences
## Consequences
- Each domain can be understood and modified independently.
- Consistent mapping between frontend service paths and backend API routes simplifies debugging.
- **Positive:** Each domain can be understood and modified independently.
- **Positive:** Consistent mapping between frontend service paths and backend API routes.
- **Negative:** Cross-domain features (e.g. shared auth, user profile) must be placed in `core/` to avoid circular dependencies.
- **Note:** Hero Wars uses static JSON data files (`src/files-data/`) rather than live API calls, which is intentional — the data is updated manually via weekly snapshots.
### Negative Consequences
- Cross-domain features (shared auth, user profile) must be placed in `core/` to avoid circular dependencies.
## Pros and Cons of the Options
### Domain-based organisation
- Good, because domain boundaries are explicit and enforced by directory structure.
- Good, because mirrors the backend route grouping — frontend `core/services/skydive/` maps to backend `src/routes/api/skydive/`.
- Bad, because shared cross-domain concerns need a neutral `core/` layer.
### Flat feature-based organisation
- Good, because all components of the same type are co-located.
- Bad, because domain boundaries are invisible — mixing domains is too easy.
## Links
- Hero Wars uses static JSON files (`src/files-data/`) rather than live API calls — weekly snapshots updated manually. This is intentional and not a gap in the domain structure.
- Related to backend [ADR 0004](../../adastra_api/docs/decisions/0004-route-organisation-by-domain.md)
@@ -1,29 +1,57 @@
# ADR 0012: SCSS Strategy — Bootstrap Variable Overrides and Sass @import Deprecation
# Silence the Sass @import deprecation for the Bootstrap import chain
**Date:** 2026-04-26
**Status:** Accepted (workaround pending Bootstrap 6)
- Status: accepted
- Date: 2026-04-26
## Context
## Context and Problem Statement
Bootstrap 5 relies on Sass's `@import` rule and `!default` variable overrides: our custom `_variable.scss` must be loaded before Bootstrap's `variables.scss` so that Bootstrap picks up our values via `!default`.
Bootstrap 5 relies on Sass's `@import` rule and `!default` variable overrides: our custom `_variable.scss` must be loaded before Bootstrap's `variables.scss` so that Bootstrap picks up our values via the `!default` mechanism. Dart Sass 1.80+ deprecates `@import` in favour of the `@use` module system. How should the deprecation be addressed?
Dart Sass 1.80+ deprecates `@import` in favour of the `@use` module system. The `@use` system uses isolated module scopes, which breaks the `!default` override mechanism: variables loaded via `@use` in our file do not flow into Bootstrap's module scope.
## Decision Drivers
Migrating to `@use` for the Bootstrap import chain would require either:
- Bootstrap 5's `!default` override mechanism is incompatible with `@use` module scopes — variables loaded via `@use` do not flow into Bootstrap's module scope.
- Other Dart Sass deprecations (`color.red/green/blue()`, global `if()`) were fixed properly.
- Waiting for Bootstrap 6, which is expected to adopt `@use` natively.
- Rewriting the entire Bootstrap variable override mechanism using `@use "bootstrap" with (...)`.
## Considered Options
Neither option is viable in the short term.
- Silence the `@import` deprecation via `angular.json`
- Migrate to `@use` with `@use "bootstrap" with (...)`
- Wait for Bootstrap 6
## Decision
## Decision Outcome
- Suppress the Sass `@import` deprecation warning in `angular.json` via `stylePreprocessorOptions.sass.silenceDeprecations: ["import"]`.
- Keep the existing `@import`-based Bootstrap loading chain intact.
- Fix other Dart Sass deprecations properly: `color.red/green/blue()` replaced with `color.channel()`, global `if()` replaced with `@if`/`@else`.
Chosen option: "Silence the `@import` deprecation via `angular.json`", because the `@use` migration is blocked by Bootstrap 5's architecture and Bootstrap 6 (which adopts `@use` natively) is not yet released.
## Consequences
The deprecation is suppressed in `angular.json` via `stylePreprocessorOptions.sass.silenceDeprecations: ["import"]`. The existing `@import`-based Bootstrap loading chain is kept intact. All other Dart Sass deprecations are fixed properly.
- **Positive:** Bootstrap customisation works correctly with no behaviour change.
- **Negative:** Deprecation silenced rather than fixed. `@import` will be removed in Dart Sass 3.0.
- **Future action:** When Bootstrap 6 is released with `@use` support, migrate the import chain and remove the `silenceDeprecations` entry. Supersede this ADR at that point.
### Positive Consequences
- Bootstrap customisation works correctly with no behaviour change.
- No risk of introducing regressions by attempting an incompatible migration now.
### Negative Consequences
- Deprecation is silenced rather than fixed. `@import` will be removed in Dart Sass 3.0.
## Pros and Cons of the Options
### Silence the `@import` deprecation
- Good, because no behaviour change — existing variable overrides continue to work.
- Bad, because the underlying issue is deferred, not resolved.
### Migrate to `@use` with `@use "bootstrap" with (...)`
- Good, because fully resolves the deprecation.
- Bad, because requires rewriting all Bootstrap variable overrides using `with ()` syntax — significant effort.
- Bad, because the `with ()` syntax has limitations compared to `!default` chaining.
### Wait for Bootstrap 6
- Good, because Bootstrap 6 adopts `@use` natively — migration path will be documented by the Bootstrap team.
- Bad, because Bootstrap 6 release date is unknown; deferring means living with the silenced warning.
## Links
- Related to [ADR 0003](0003-dual-ui-library-progressive-migration.md)
- Future action: when Bootstrap 6 is released with `@use` support, migrate the import chain, remove the `silenceDeprecations` entry, and supersede this ADR.