Self Serve Code Review
Reviewer scope and the signal bar are owned by the copilot-code-reviewer
skill (.github/skills/copilot-code-reviewer/SKILL.md); this skill assumes
those and covers only the line-level judgment. Read enough surrounding code
to judge each hunk in its real context — for a server change, the middleware →
controller → service path it sits on; for an Angular change, the component, its
template, and the signals and services it consumes.
A diff alone is not enough. For each non-trivial hunk, read the whole changed
function, not just the diff lines, and Grep for callers and sibling
implementations of the same pattern to confirm the change matches how the repo
already does it — convention drift is a finding even when the code "works". For
a proxy change, read one layer past the diff: the upstream contract the call
must mirror, not just the local shape.
The house standards
The repo defines its own standards; hold the diff to them, and name the
documented source in any standards finding. Read the parts relevant to the diff
before judging, every run, because the standards belong to the repo and move with
it. They live in:
CLAUDE.md — the source-of-truth order, the domain language (PCC, ED,
Admin Mode, personas, L2), commit/PR conventions, and the global "what NOT to
do" list.
.claude/rules/ — component-organization.md (Angular signal structure,
model() vs WritableSignal, the DELETE→CREATE rule for full component
replacements), logging-patterns.md (the Pino LoggerService, operation
lifecycle, controller-vs-service logging responsibility), styling.md
(Tailwind, lfxColors, flex + flex-col + gap-* not space-y-*),
ssr-safety.md, and development-rules.md (the shared package, M2M-vs-user
tokens, upstream-contract verification, code-quality rules).
- The four
docs/reviews/ checklists — frontend-checklist.md (PrimeNG
wrapper strategy: prefer the LFX wrapper over a raw <p-*> in feature
templates unless a documented exception applies, no function calls in
render-time expressions — interpolation and property bindings should read
signals or pipes, not call methods that re-run every change detection; event
bindings like (click)="save()" are the normal exception — component
organization),
backend-checklist.md (the three-file service/controller/route pattern,
controller-vs-service separation, custom error classes, user bearer tokens vs
M2M, upstream API validation, protected files), shared-and-sql-checklist.md,
and docs-checklist.md.
Enforcement runs in both directions: code that violates a documented standard is
a finding, and a documented standard the code has visibly outgrown is a finding
against the docs. If a documented convention is wrong for this specific change,
say so explicitly and explain the trade, rather than silently waiving or silently
enforcing it.
Quality dimensions
Run these on the changed code, scaled to the size of the change:
- Correctness: does it do what it claims? Watch unhandled Observable errors
and leaked subscriptions, signals read where a
computed was meant, effects
with hidden write loops, async/await that drops a rejected promise,
boundary conditions, and proxy calls whose request/response shape does not
match the upstream contract.
- Error handling: server errors follow the repo's error model — controllers
pass errors to the handler (
next(error)), services throw the custom error
classes (BaseApiError and friends), and nothing is silently swallowed or
leaked to the client. On the client, HTTP failures surface through the
established error path, not a swallowed catchError(() => of(null)) that hides
a real failure as empty state.
- Tests: new or changed behavior has tests that assert real behavior, not
that a mock was called; new components get
data-testid hooks and the dual
E2E coverage the repo expects where appropriate. Missing tests on
contract-bearing or security-sensitive code is worth raising when you can name
the untested behavior and what breaking it would cost — not as a standing
request for coverage.
- Performance: for a caller-facing list, page the cursor through to the
caller rather than draining every upstream page into one response
(
/query/resources carries a cursor — use it); a deliberate all-pages fetch
for a complete-set operation (via the established all-pages helper) is a
supported pattern, not a defect. No work in a template
expression (it re-runs every change detection), no waterfall of sequential
awaits that should be concurrent, and no oversized or unread TransferState
payload. Transferring server-rendered state is the established hydration path
here (AuthContext, runtime config) and it saves a duplicate client fetch, so
the finding is state the client never reads or a result set big enough to
bloat every SSR document — not the transfer itself. A server-only secret
crossing that boundary is a security finding, not a performance one.
- Readability and structure: the change reads like the surrounding code;
names say what a thing is or does; duplicated logic that wants a shared helper
is a finding when it traps the next editor.
- Code truthfulness: comments, docs, and the PR description match what the
code actually does; a stale comment, a dead branch, a
data-testid that lies
about the element, or a TODO dressed as done is a finding.
Self Serve specifics worth a second look
- Shared package boundary. New interfaces, reusable constants, and enums
belong in
@lfx-one/shared, not as module-level consts or local interface
declarations inside apps/lfx-one/. A type duplicated instead of imported is a
finding.
- PrimeNG independence. Components are consumed through the LFX wrapper
components, and types reference the PrimeNG component interface. A raw
<p-*>
where an LFX wrapper exists, or a hand-rolled type where the wrapped interface
exists, breaks the UI-library-independence the repo deliberately keeps —
PrimeNG controls with a sanctioned direct use (documented exceptions) are
fine.
- SSR safety. Browser-only APIs (
window, document, localStorage,
navigator, the observers) must sit behind a browser-only boundary — an
isPlatformBrowser guard, or an Angular render callback that does not execute
during SSR; either is acceptable. Browser-only libraries must be lazy-imported
inside that boundary
— a static top-level import crashes the SSR bundle even when the call site is
guarded. The failure only shows under yarn build, not yarn start. (Security
consequences of SSR — secret leakage into the client — are the security skill's
job.)
- Critical constants. A changed constant is a behavior change even when the
code "works": timeouts, retry/backoff values, page-size caps, cache TTLs,
rate-limit tiers, feature-flag defaults, env-var keys, and upstream URLs or
subjects. When the diff moves one, ask whether the change is stated and
intentional and what its blast radius is. The finding is a blast radius the
change does not account for, not the absence of a sentence explaining it — a
correct new value needs no rationale to be correct.
- Protected files. Changes to
server.ts, the singleton services, build/format
config, or CLAUDE.md carry repo-owner weight and warrant closer scrutiny —
raise one when its risk or intent is unclear, not merely because a sensitive
file was touched (a clean, well-understood edit to one is not itself a
finding).
Judgment calls
- Point at the working pattern. When the diff violates a pattern, cite the
working example in the surrounding code rather than describing an abstract
ideal.
- Do not propose rewrites of a sound approach, and do not suggest change for
its own sake; working, readable code needs no improvement.
- Know your limits. Distinguish "this is wrong" from "this might be a problem
depending on context", and say which one you mean. When a judgment depends on
something you cannot see (an upstream microservice's contract, a deployment
value, a runtime feature flag), note the dependency rather than asserting a
defect you cannot confirm.
1---2name: self-serve-code-review3description: How to judge the implementation of an lfx-self-serve (LFX One) pull request: the general quality dimensions (correctness, error handling, tests, performance, readability, code truthfulness) and how to hold the diff to the repo's documented standards for the Angular SSR app and the Express BFF. Use on every PR that changes code, however small; this is the reviewer's line-level lens. Security has its own skill (self-serve-security-review).4---56<!-- Copyright The Linux Foundation and each contributor to LFX. -->7<!-- SPDX-License-Identifier: MIT -->89# Self Serve Code Review1011Reviewer scope and the signal bar are owned by the `copilot-code-reviewer`12skill (`.github/skills/copilot-code-reviewer/SKILL.md`); this skill assumes13those and covers only the line-level judgment. Read enough surrounding code14to judge each hunk in its real context — for a server change, the middleware →15controller → service path it sits on; for an Angular change, the component, its16template, and the signals and services it consumes.1718A diff alone is not enough. For each non-trivial hunk, read the **whole changed19function**, not just the diff lines, and `Grep` for **callers and sibling20implementations** of the same pattern to confirm the change matches how the repo21already does it — convention drift is a finding even when the code "works". For22a proxy change, read one layer past the diff: the upstream contract the call23must mirror, not just the local shape.2425## The house standards2627The repo defines its own standards; hold the diff to them, and name the28documented source in any standards finding. Read the parts relevant to the diff29before judging, every run, because the standards belong to the repo and move with30it. They live in:3132- **`CLAUDE.md`** — the source-of-truth order, the domain language (PCC, ED,33 Admin Mode, personas, L2), commit/PR conventions, and the global "what NOT to34 do" list.35- **`.claude/rules/`** — `component-organization.md` (Angular signal structure,36 `model()` vs `WritableSignal`, the DELETE→CREATE rule for full component37 replacements), `logging-patterns.md` (the Pino `LoggerService`, operation38 lifecycle, controller-vs-service logging responsibility), `styling.md`39 (Tailwind, `lfxColors`, `flex + flex-col + gap-*` not `space-y-*`),40 `ssr-safety.md`, and `development-rules.md` (the shared package, M2M-vs-user41 tokens, upstream-contract verification, code-quality rules).42- **The four `docs/reviews/` checklists** — `frontend-checklist.md` (PrimeNG43 wrapper strategy: prefer the LFX wrapper over a raw `<p-*>` in feature44 templates unless a documented exception applies, no function calls in45 render-time expressions — interpolation and property bindings should read46 signals or pipes, not call methods that re-run every change detection; event47 bindings like `(click)="save()"` are the normal exception — component48 organization),49 `backend-checklist.md` (the three-file service/controller/route pattern,50 controller-vs-service separation, custom error classes, user bearer tokens vs51 M2M, upstream API validation, protected files), `shared-and-sql-checklist.md`,52 and `docs-checklist.md`.5354Enforcement runs in both directions: code that violates a documented standard is55a finding, and a documented standard the code has visibly outgrown is a finding56against the docs. If a documented convention is wrong for this specific change,57say so explicitly and explain the trade, rather than silently waiving or silently58enforcing it.5960## Quality dimensions6162Run these on the changed code, scaled to the size of the change:6364- **Correctness**: does it do what it claims? Watch unhandled Observable errors65 and leaked subscriptions, signals read where a `computed` was meant, effects66 with hidden write loops, `async`/`await` that drops a rejected promise,67 boundary conditions, and proxy calls whose request/response shape does not68 match the upstream contract.69- **Error handling**: server errors follow the repo's error model — controllers70 pass errors to the handler (`next(error)`), services throw the custom error71 classes (`BaseApiError` and friends), and nothing is silently swallowed or72 leaked to the client. On the client, HTTP failures surface through the73 established error path, not a swallowed `catchError(() => of(null))` that hides74 a real failure as empty state.75- **Tests**: new or changed behavior has tests that assert real behavior, not76 that a mock was called; new components get `data-testid` hooks and the dual77 E2E coverage the repo expects where appropriate. Missing tests on78 contract-bearing or security-sensitive code is worth raising when you can name79 the untested behavior and what breaking it would cost — not as a standing80 request for coverage.81- **Performance**: for a caller-facing list, page the cursor through to the82 caller rather than draining every upstream page into one response83 (`/query/resources` carries a cursor — use it); a deliberate all-pages fetch84 for a complete-set operation (via the established all-pages helper) is a85 supported pattern, not a defect. No work in a template86 expression (it re-runs every change detection), no waterfall of sequential87 awaits that should be concurrent, and no oversized or unread `TransferState`88 payload. Transferring server-rendered state is the established hydration path89 here (`AuthContext`, runtime config) and it saves a duplicate client fetch, so90 the finding is state the client never reads or a result set big enough to91 bloat every SSR document — not the transfer itself. A server-only secret92 crossing that boundary is a security finding, not a performance one.93- **Readability and structure**: the change reads like the surrounding code;94 names say what a thing is or does; duplicated logic that wants a shared helper95 is a finding when it traps the next editor.96- **Code truthfulness**: comments, docs, and the PR description match what the97 code actually does; a stale comment, a dead branch, a `data-testid` that lies98 about the element, or a TODO dressed as done is a finding.99100## Self Serve specifics worth a second look101102- **Shared package boundary.** New interfaces, reusable constants, and enums103 belong in `@lfx-one/shared`, not as module-level consts or local `interface`104 declarations inside `apps/lfx-one/`. A type duplicated instead of imported is a105 finding.106- **PrimeNG independence.** Components are consumed through the LFX wrapper107 components, and types reference the PrimeNG component interface. A raw `<p-*>`108 where an LFX wrapper exists, or a hand-rolled type where the wrapped interface109 exists, breaks the UI-library-independence the repo deliberately keeps —110 PrimeNG controls with a sanctioned direct use (documented exceptions) are111 fine.112- **SSR safety.** Browser-only APIs (`window`, `document`, `localStorage`,113 `navigator`, the observers) must sit behind a browser-only boundary — an114 `isPlatformBrowser` guard, or an Angular render callback that does not execute115 during SSR; either is acceptable. Browser-only libraries must be lazy-imported116 inside that boundary117 — a static top-level import crashes the SSR bundle even when the call site is118 guarded. The failure only shows under `yarn build`, not `yarn start`. (Security119 consequences of SSR — secret leakage into the client — are the security skill's120 job.)121- **Critical constants.** A changed constant is a behavior change even when the122 code "works": timeouts, retry/backoff values, page-size caps, cache TTLs,123 rate-limit tiers, feature-flag defaults, env-var keys, and upstream URLs or124 subjects. When the diff moves one, ask whether the change is stated and125 intentional and what its blast radius is. The finding is a blast radius the126 change does not account for, not the absence of a sentence explaining it — a127 correct new value needs no rationale to be correct.128- **Protected files.** Changes to `server.ts`, the singleton services, build/format129 config, or `CLAUDE.md` carry repo-owner weight and warrant closer scrutiny —130 raise one when its risk or intent is unclear, not merely because a sensitive131 file was touched (a clean, well-understood edit to one is not itself a132 finding).133134## Judgment calls135136- **Point at the working pattern.** When the diff violates a pattern, cite the137 working example in the surrounding code rather than describing an abstract138 ideal.139- **Do not propose rewrites of a sound approach**, and do not suggest change for140 its own sake; working, readable code needs no improvement.141- **Know your limits.** Distinguish "this is wrong" from "this might be a problem142 depending on context", and say which one you mean. When a judgment depends on143 something you cannot see (an upstream microservice's contract, a deployment144 value, a runtime feature flag), note the dependency rather than asserting a145 defect you cannot confirm.