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.
20 KiB
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 optionnoPropertyAccessFromIndexSignature: 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
-
Branch from
mainwith a short slug:git switch -c feat/portal-shell/auth-login # or fix/..., chore/..., docs/... -
Commit using Conventional Commits. The local
commit-msghook (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 baselinepre-commitrunslint-staged→ Prettier on staged files. Lint and tests stay in CI. -
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).
-
Squash-merge into
main. Branch is auto-deleted. Linear history maintained. -
To cut a release: tag
vX.Y.Zonmain. Therelease.ymlworkflow 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 (6–18 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. |