Files
apf_portal/docs/development.md
T
Julien Gautier 7d27cd8773
CI / check (push) Failing after 2s
CI / commits (push) Has been skipped
CI / perf (push) Failing after 12s
CI / a11y (push) Failing after 3s
CI / scan (push) Failing after 1m33s
docs: turn development.md §7 into a phase-mapped roadmap
The section was a short bullet list of 'sections to be added' - it
underplayed how broad the future content really is, and gave no
visibility on what triggers each. Replace it with a structured table
that maps every planned section to (a) its ADR phase and (b) the
specific implementation work that unlocks it. A contributor reading
the doc today now sees:

- which dev-loops will exist (auth, sessions, MFA step-up, OTel,
  audit, downstream APIs, component patterns, a11y, perf debugging,
  Renovate, release, GitLab migration, architecture diagrams)
- under which ADR each lands
- what concrete event in the codebase makes each section real

Plus the explicit policy: each entry stays a subsection of this doc
until we have at least three substantial sub-topics, at which point
the file is split into docs/development/ with an index. Avoids
creating empty placeholder files (per CLAUDE.md: 'documentation when
genuinely useful, not just to tick a box') while signalling the
future structure clearly.

Cross-references each row to its triggering ADR so the table doubles
as a 'what's pending implementation' radar. Foreshadows the §7 → file
split that will happen once content density justifies it.
2026-04-30 20:11:01 +02:00

20 KiB
Raw Blame History

Development guide

This document is the day-to-day reference for working on apf_portal. It covers the repo layout, the prerequisites, the initial setup from a fresh clone, and the commands you'll run during a typical development cycle. It is meant to grow — add sections as the team's workflow does.

For decision rationale, see the ADRs. For onboarding the local environment (terminal, Node, pnpm), see setup/.


1. Repo layout

apf_portal/
├── .gitea/workflows/             # CI pipelines (ADR-0015)
│   ├── ci.yml                    # per-PR + push to main: check / scan / commits / perf / a11y
│   └── security-scheduled.yml    # weekly full-tree scan + prod Lighthouse
├── .github/                      # Nx AI-tooling skills, prompts, agents (Nx-managed)
├── .husky/                       # local git hooks (ADR-0007)
│   ├── pre-commit                # → pnpm exec lint-staged
│   └── commit-msg                # → pnpm exec commitlint
├── apps/
│   ├── portal-shell/             # Angular 21 SPA (zoneless, standalone, Signals, Vitest, SCSS)
│   │   ├── public/               # static assets
│   │   ├── src/                  # entry, app config, routes, styles
│   │   ├── postcss.config.js     # Tailwind PostCSS plugin
│   │   └── project.json          # Nx project config (build, serve, test, lint targets)
│   ├── portal-shell-e2e/         # Playwright e2e for portal-shell
│   ├── portal-bff/               # NestJS 11 BFF (Express adapter, ValidationPipe, Jest)
│   │   ├── src/                  # main, app module, controllers, services
│   │   ├── prisma/schema.prisma  # Prisma 7 schema (postgresql)
│   │   ├── prisma.config.ts      # Prisma 7 TS config (loads DATABASE_URL from .env)
│   │   ├── .env.example          # env-vars catalog (committed); .env stays gitignored
│   │   └── project.json
│   └── portal-bff-e2e/           # Jest e2e for portal-bff
├── libs/
│   ├── feature/<name>/           # vertical feature libs (e.g. feature-auth)
│   └── shared/<scope>/           # cross-cutting libs (tokens, ui, util)
├── docs/
│   ├── README.md                 # doc index
│   ├── decisions/                # ADRs (MADR 4.0.0)
│   ├── setup/                    # local-environment onboarding (Zsh, pnpm, Nx workspace)
│   └── development.md            # this file
├── notes/                        # personal scratchpad (gitignored)
├── CLAUDE.md                     # project rules + architecture summary
├── commitlint.config.cjs         # Conventional Commits config
├── eslint.config.mjs             # workspace ESLint with module boundaries
├── lighthouserc.js               # Lighthouse CI thresholds (ADR-0017)
├── nx.json                       # Nx workspace config
├── package.json                  # workspace deps + scripts
├── pnpm-workspace.yaml           # apps/* + libs/**
├── tsconfig.base.json            # shared TS strict config
└── vitest.workspace.ts           # Vitest workspace projects

The conventions that govern this layout are recorded in:

  • ADR-0002 — Nx workspace shape
  • ADR-0003 — naming convention (portal-shell, portal-bff, feature-<name>, shared-<scope>)
  • ADR-0007 — local hooks
  • ADR-0015 — CI/CD shape

2. Prerequisites

A working dev machine for apf_portal needs:

Tool Why How
WSL 2 + Debian (Windows) or Linux/macOS native All commands assume a POSIX shell see setup/01
Node.js 24 (latest LTS) Runtime, pinned in .nvmrc nvm install 24 && nvm use (see setup/02)
pnpm 10+ Mandatory package manager (no npm/yarn lockfile) corepack enable && corepack prepare pnpm@latest --activate
Git ≥ 2.40 Husky 9 + signed commits eventually usually default
mkcert Local HTTPS for cookie-prefix __Host- (ADR-0009) apt install mkcert then mkcert -install
Trivy (optional, locally) Dep vuln scan when running ci:scan locally; CI uses an action apt install trivy or trivy install docs
gitleaks (optional, locally) Same pattern; CI uses an action apt install gitleaks or gitleaks install docs
Docker For Postgres + Redis containers in dev (until on-prem infra ADR lands) Docker Desktop on Windows, Docker Engine on Linux

Work inside the WSL filesystem (~/Works/...), never /mnt/c/... — the latter has severe I/O penalties that break Nx caching.


3. Initial setup from a fresh clone

git clone gitea@git.unespace.com:julien/apf_portal.git
cd apf_portal

# Install deps (also runs `husky` to wire git hooks)
pnpm install

# Generate the Prisma client (until you set up the DB it errors on
# missing DATABASE_URL — that's expected; the generation only reads
# the schema, not the DB).
cd apps/portal-bff && pnpm exec prisma generate && cd ../..

# Sanity check
pnpm nx run-many -t lint test build

For the BFF to actually run end-to-end, you'll also need:

# .env from .env.example, then fill in DATABASE_URL with your local Postgres
cp apps/portal-bff/.env.example apps/portal-bff/.env

# Local Postgres via Docker (one-liner; will be replaced by a Docker Compose file in a later doc)
docker run -d --name apf-postgres -p 5432:5432 \
  -e POSTGRES_USER=portal -e POSTGRES_PASSWORD=portal -e POSTGRES_DB=portal_dev \
  postgres:17-alpine

A proper local-dev infra spec (Postgres HA-like, Redis, OTel collector) will land with the on-prem infrastructure ADR; in the meantime the one-liner above is sufficient to run the BFF.


4. Daily commands

Run the apps

pnpm nx serve portal-shell        # http://localhost:4200 (Angular dev server)
pnpm nx serve portal-bff          # http://localhost:3000/api (NestJS)

Both can run in parallel in two terminals; the SPA proxies API calls to the BFF in dev.

Test

pnpm nx test portal-shell         # Vitest (single run; --configuration=watch for watch mode)
pnpm nx test portal-bff           # Jest

pnpm nx run-many -t test          # all projects
pnpm nx affected -t test          # only projects affected since main

Run a single Vitest file:

pnpm nx test portal-shell --testFile=src/app/app.spec.ts

Lint, type-check, format

pnpm nx lint portal-shell             # one project
pnpm nx run-many -t lint              # all projects
pnpm nx affected -t lint              # affected only
pnpm nx affected -t lint --fix        # auto-fix where possible

pnpm nx affected -t type-check        # explicit type-check (independent of test/build)

pnpm nx format:write                  # apply Prettier
pnpm nx format:check                  # CI-style verification

Build

pnpm nx build portal-shell                                   # development build
pnpm nx build portal-shell --configuration=production        # production build
pnpm nx run-many -t build
pnpm nx affected -t build

Generate

# Component in portal-shell
pnpm nx g @nx/angular:component <name> --project=portal-shell --standalone

# Service / controller / module in portal-bff
pnpm nx g @nx/nest:service <name> --project=portal-bff
pnpm nx g @nx/nest:controller <name> --project=portal-bff

# New shared lib (TS-only, consumable by both apps)
pnpm nx g @nx/js:library --name=shared-<scope> --directory=libs/shared/<scope> \
  --bundler=tsc --unitTestRunner=vitest \
  --tags="scope:shared,type:shared" --no-interactive

# New Angular feature lib (front-only)
pnpm nx g @nx/angular:library --name=feature-<name> --directory=libs/feature/<name> \
  --standalone=true --unitTestRunner=vitest-analog \
  --tags="scope:portal-shell,type:feature" --no-interactive

Sweep generated files for process.env.X (dot notation) → process.env['X'] (bracket notation), required by the strict-TS option noPropertyAccessFromIndexSignature: true. The Nx generators don't emit bracket form.

Prisma

# Regenerate the typed client after schema changes
cd apps/portal-bff && pnpm exec prisma generate && cd ../..

# Create and apply a migration in dev
cd apps/portal-bff && pnpm exec prisma migrate dev --name <migration-name> && cd ../..

# Deploy migrations in prod (run by deploy pipeline, not locally)
cd apps/portal-bff && pnpm exec prisma migrate deploy && cd ../..

# Inspect the dev DB
cd apps/portal-bff && pnpm exec prisma studio && cd ../..

CI scripts (runnable locally)

Mirror what the CI does on every PR:

pnpm ci:check      # nx affected -t format:check lint test build
pnpm ci:audit      # pnpm audit --audit-level=moderate
pnpm ci:commits    # commitlint on the PR commit range (uses $COMMIT_LINT_FROM, defaults to origin/main)
pnpm ci:perf       # production build + Lighthouse CI against the static-served bundle

ci:scan (Trivy + gitleaks) is currently invoked from CI YAML rather than as a pnpm script — those tools are Go binaries without clean npm wrappers. Run them locally if you've installed the binaries.


5. Conventional commit cycle

  1. Branch from main with a short slug:

    git switch -c feat/portal-shell/auth-login   # or fix/..., chore/..., docs/...
    
  2. Commit using Conventional Commits. The local commit-msg hook (commitlint) rejects anything else.

    feat(portal-shell): add login flow stub
    fix(portal-bff): correct env var bracket access
    chore: bump @nx/* to 22.7.2
    docs(decisions): add ADR-0018 for security baseline
    

    pre-commit runs lint-staged → Prettier on staged files. Lint and tests stay in CI.

  3. Push and open a PR against main. The CI runs:

    • check (lint, type-check, test, build on affected)
    • scan (audit, Trivy, gitleaks)
    • commits (commitlint on the PR commit range)
    • perf (Lighthouse on the production bundle)
    • a11y (axe-core; placeholder until first real screens)

    All five must be green to merge. PR title must itself be a Conventional Commits message — it becomes the squash-merge subject (ADR-0015).

  4. Squash-merge into main. Branch is auto-deleted. Linear history maintained.

  5. To cut a release: tag vX.Y.Z on main. The release.yml workflow will pick it up (currently a stub; populated alongside the on-prem deploy ADR).


6. Where to look

Question Doc
Project rules and the why behind them CLAUDE.md
All ADRs (decisions index) docs/decisions/README.md
Initial environment setup (Zsh, Node, pnpm) docs/setup/
RSSI briefing for ASVS / HDS / etc. notes/asvs-level-decision-briefing-rssi.md (gitignored, personal)
The dev-team rationale for the UI stack notes/argumentaire-stack-ui-spartan-cdk-tailwind.md (gitignored, personal)

7. Sections to come — roadmap by phase

This doc starts as a phase-1 + cross-cutting reference. As features for later phases land, the corresponding sections below are filled in directly. Each entry is mapped to the ADR / implementation work that unlocks it, so a contributor can see when each section becomes real and what triggers it.

When a section grows beyond a short subsection, it is extracted to its own file under docs/development/. Per the documentation convention (see README.md), we group into a folder once we have at least three related files; this doc is then re-organised into an index pointing at the extracted files. Until then, all sections live here.

Future section Phase Triggered by
Local infra recipe — Docker Compose for Postgres, Redis, OTel Collector, and a Postgres-friendly viewer. 2 / 3b First feature that needs Redis (sessions, ADR-0010). Earlier if the BFF gains a meaningful amount of business code.
Auth dev-loop — Microsoft 365 Developer tenant configuration, MSAL Node connection, OIDC code-flow walkthrough, switching between dev and prod-like tenants. 2 Auth flow code lands (ADR-0009) once the dev tenant is provisioned by IT.
Session inspection — reading the Redis session store in dev, decrypting the AES-GCM tokens blob with the dev key, force-logout patterns. 2 Sessions module lands (ADR-0010).
MFA step-up debugging — triggering claims-challenge flows, verifying mfaVerifiedAt freshness, testing the SPA HTTP interceptor that handles 401 + claims challenge. 2 First @RequireMfa() route lands (ADR-0011).
Observability dev-loop — running an OTel collector container locally, viewing traces (Tempo / Jaeger UI), reading Pino logs with pino-pretty, correlating front spans to BFF spans via traceparent. 2 OTel SDK setup lands (ADR-0012); needs the local collector from "Local infra recipe".
Audit-log inspection workflow — querying audit.events as audit_reader, joining with app logs by trace_id, validating the append-only role grants in dev. 2 Audit module lands (ADR-0013).
Downstream API integration recipe — adding a new DownstreamApiConfig, choosing the auth strategy (OBO vs service+assertion), wiring resilience policies, testing with a mocked downstream. 2 First downstream client lands (ADR-0014).
Component patterns library — the in-house, spartan-style components (Angular CDK + Tailwind) as they ship, with a11y notes per component (keyboard model, ARIA, screen-reader expectations). 5b suite First non-placeholder component in libs/shared/ui/.
a11y testing workflow — running axe-core via Playwright locally, screen-reader testing notes (NVDA / VoiceOver / TalkBack), the APF user-panel cadence and how to triage findings. 3a First Playwright e2e suite touching real screens (ADR-0016).
Performance debugging — running Lighthouse CI locally with full config, reading the HTML reports, using source-map-explorer to investigate bundle bloat, interpreting BFF p95/p99 from OTel. 3a Lighthouse already wired in CI (ADR-0017); section grows when first real route is added to the critical-routes list.
Renovate / dep update policy — bot setup, schedule, auto-merge rules, breaking-change handling. 3a Security baseline ADR (paused, awaiting RSSI input).
Debugging tips — Angular DevTools, NestJS inspector, Prisma query log, OTel trace navigation, common gotchas. cross Accumulates organically as the team encounters them.
Release workflow — tag-driven release, what release.yml does, version bumping, changelog generation from Conventional Commits. 3b On-prem infrastructure ADR + populated release.yml.
GitLab migration runbook — when the org migrates Gitea → GitLab, how the workflows are ported, which level-2 sections of ADR-0015 get superseded. future GitLab migration ADR (618 months horizon).
Architecture overview diagrams — high-level component diagrams, data-flow diagrams, trust boundaries (for security review). cross First major architecture review or onboarding cohort ≥ 3 contributors.