06ea1feabff235e9383d0979fc1ae5aeece5fb1e
The audit-log table carries `trace_id` + `actor_id_hash` columns
already populated by the BFF (ADR-0013) but they were plain text —
an investigator could see the values but had to copy them out of the
table to do anything with them. This PR turns both into interactive
affordances:
* **`trace_id` becomes a Jaeger deep link.** Each non-null
`traceId` renders as `<a target="_blank" rel="noopener
noreferrer">` pointing at `${jaegerBaseUrl}/trace/<id>`. The
base URL is per-env in `apps/portal-admin/src/environments/
environment.ts` (dev: `localhost:16686` matching the compose
observability profile; prod: the future infra ADR will swap to
whatever trace backend is settled on — Tempo, Grafana Cloud,
on-prem Jaeger…). Anonymous events (no traceId) keep the dash
placeholder. Closes the "join audit + traces by trace_id"
promise of ADR-0012 / ADR-0013 with the lowest-coupling option
available today (no new BFF endpoint, no log aggregator
required).
* **`actor_id_hash` becomes a click-to-filter button.** Each
non-null hash renders as a `<button>` styled to read inline
like the hash text but with a hover affordance + focus ring +
underline. Clicking it sets `actorIdHash.set(hash)`, resets
the offset to 0, and re-runs the query — pivoting the table on
that single actor's events. Anonymous rows keep the
`(anonymous)` plain-text rendering since there's no filter
value to apply. Each pivot still emits its own
`admin.audit.query` audit row server-side (ADR-0020) so the
drill is auditable.
Why not inline-expand Pino logs under the row? Considered, deferred
to a future chantier — the BFF's Pino output goes to stdout today
with no queryable backend; standing up a log aggregator (Loki /
OpenSearch / …) is a separate infra ADR. Jaeger jump-off carries
~99 % of the investigator's needs because the trace already
contains span attributes (db.statement, http.status_code, exception
events) for the same scope.
Implementation:
* `audit.ts` gains `jaegerUrl(traceId)` (builds the URL with
encodeURIComponent) and `filterByActor(hash)` (mutates the
actor filter + offset + re-fetches). Imports
`environment.jaegerBaseUrl`.
* `audit.html` swaps the trace cell to an anchor (or dash) and
the actor cell to a clickable button (or anonymous text),
each with a `title` attribute for hover hint.
* `audit.scss` adds `.actor-hash--clickable` (button reset +
dotted-underline hover) and `.trace-link` (brand-coloured
underlined link), both with focus rings + dark-mode swaps.
Verification:
* 54 portal-admin specs pass (was 50; +4 for the four new
behaviours: trace anchor + dash + actor pivot + anonymous
plain text).
* `pnpm nx build portal-admin` clean; lazy `audit` chunk grows
marginally (4.36 → 4.44 KB gzip) — well under the per-chunk
budget.
Documentation index
This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.
Conventions
- Documentation is written in English.
- One topic per file. Group related files into a folder when there are three or more.
- Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
- For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.
Sections
Daily development
- development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.
Architecture
- architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.
Onboarding & environment
Setup guides for new contributors:
- setup/01-wsl-terminal-setup.md — modern WSL terminal (Zsh + Powerlevel10k + CLI tools)
- setup/02-dev-web-stack.md — Node via nvm, pnpm via corepack, Docker
- setup/03-angular-nx-monorepo.md — Angular + Nx monorepo bootstrap
Operations & runbooks
Empty — to be populated when we deploy.
Security, performance, accessibility
Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.
Description
Languages
TypeScript
85.3%
JavaScript
5.4%
SCSS
4.3%
HTML
3.9%
Shell
0.8%
Other
0.3%