name: code-review description: Perform a project-wide full-scope code review covering correctness, security auditing, test coverage, locale sync, documentation quality, code smells, UI/UX accessibility, and project conventions, then report findings and fix critical issues.
When performing a code review, conduct a full project-wide sweep — do not limit scope to recent changes. Read broadly across the codebase and apply every check below.
Step 1 — Orient and Plan
Before reviewing, understand the system's current shape:
- Read
CLAUDE.mdfor architecture, conventions, and known gotchas - Skim
lib/directory structure: all contexts, LiveViews, controllers, components, schemas - Check
doc/TODOs.mdfor known issues that may overlap with findings - Prioritise: public endpoints, federation boundary, auth flows, content handling, admin routes
Step 2 — Correctness
- Logic errors, off-by-one mistakes, incorrect pattern matches, missing
nil/{:error, _}guards - Ecto: missing
Repo.preload, N+1 risks, missingFOR UPDATElocks on counter updates (useEcto.Multi), unescaped ILIKE inputs (requireRepo.sanitize_like/1) - LiveView: missing
handle_infoclauses for all subscribed PubSub topics, stale socket assigns after redirects, race conditions betweenmountandhandle_params - Pagination: uses
Baudrate.Pagination(paginate_opts/3+paginate_query/3) — no hand-rolledLIMIT/OFFSET - Schema timestamps:
published_at(original feed entry date, bot posts only) vsinserted_at(local creation) — usage must match intent - Federation: AP objects stamped with
ap_idpost-insert; Publisher falls back toFederation.actor_uri/2only when storedap_idis nil - Soft-delete: articles and comments use
deleted_at; queries must filteris_nil(deleted_at)where appropriate
Step 3 — Security Audit
Information security is the top priority. Treat every finding as potentially exploitable.
Input and injection
- No
String.to_atom/1on any external or user input (atom table exhaustion) - No user input in file paths (path traversal)
- All user-generated and federated HTML sanitized via
Baudrate.Sanitizer.Native(Ammonia NIF) before storage - All dynamic assigns in HEEx rendered with
{@var}(auto-escaped);{:safe, ...}never wraps untrusted content (XSS) - All user values parameterized in Ecto queries; no raw interpolation into
fragment()(SQL injection)
Network and federation
- Remote HTTP fetches: SSRF-safe (reject private/loopback IPs, HTTPS only); use only
Req(neverHTTPoison,Tesla,httpc) - AP payload ≤ 256 KB, content body ≤ 64 KB enforced at inbox
- Remote actor display names sanitized (strip HTML, control chars, truncate)
Authentication and authorization
- Every public LiveView route has the correct
on_mounthook (:require_auth,:require_admin,:require_admin_or_moderator,:require_admin_totp, etc.) - Admin sudo mode (
:require_admin_totp) enforced on all admin-only actions — 10-minute TOTP re-verification window - Bot accounts (
is_bot: true) rejected byauthenticate_by_password/2; managed exclusively viaBaudrate.Botscontext - Rate limiting applied to all public endpoints (auth, registration, AP inbox, API)
Key material and secrets
- Federation private keys encrypted via
KeyVault; TOTP secrets viaTotpVault; no plaintext secrets in DB or logs KeyStore.ensure_user_keypair/1called before enqueuing any signed outbound activityHTTPSignature.sign/5/sign_get/3must not return a"host"header (causes signature verification failures)
File handling
- File uploads: magic bytes validated; avatars re-encoded as WebP (EXIF stripped)
- No user-supplied filenames used directly on the filesystem
OWASP Top 10 checklist — cross-check each category: A01 Broken Access Control · A02 Cryptographic Failures · A03 Injection · A04 Insecure Design · A05 Security Misconfiguration · A06 Vulnerable Components · A07 Auth Failures · A08 Software Integrity · A09 Logging Failures · A10 SSRF
Step 4 — Test Coverage
- Every public context function and LiveView action has a corresponding test in
test/mirroringlib/ - LiveView/controller tests use
BaudrateWeb.ConnCase; context tests useBaudrate.DataCase - Security-sensitive paths (auth, federation inbox, file uploads, rate limiting) have dedicated negative-path tests
- No
Process.sleepfor timestamp ordering — useRepo.update_allwith explicit timestamps instead - All queries with user-visible ordering include a deterministic tiebreaker (e.g.,
desc: :id) - Rate limiter stubbed via
BaudrateWeb.RateLimiter.Sandbox.set_global_response({:allow, 1})in tests that hit rate-limited paths - Federation delivery tests run synchronously (
federation_async: falseinconfig/test.exs) — no async Task races - Tests pass deterministically across all 4 partitions under concurrent execution — no shared mutable state, no order dependencies
Step 5 — Locale Sync
- Every user-visible string is wrapped in
gettext()— no bare English strings in templates, flash messages, HTML attributes (title,aria-label,placeholder), feed metadata, or error messages %{var}interpolation used insidegettext()— never Elixir string interpolation ("#{}")- After scanning
lib/andpriv/gettext/en/, verify that bothpriv/gettext/zh_TW/andpriv/gettext/ja_JP/.pofiles contain translations for everymsgid - Terminology is consistent across all locales:
Board→zh_TW: 看板(not 版面/板塊),ja_JP: 掲示板(not ボード)User→zh_TW: 使用者(not 用戶)
- No stale/orphaned
msgidentries remaining in.pofiles after string removal
Step 6 — Documentation Quality
@moduledocpresent and accurate for every public-facing module;@docon every public function with non-obvious behaviourdoc/development.mdreflects current architecture, contexts, auth flow, federation mechanicsdoc/sysop.mdreflects current installation, configuration, and maintenance proceduresdoc/api.mddocuments all public AP and web API endpointsdoc/TODOs.mdcontains no items already completedCLAUDE.md— stack table, key gotchas, and project conventions match the current codebaseREADME.md— feature list, prerequisites, and acknowledgements are current- No commented-out dead code left in place of proper documentation
Step 7 — Code Smells
- Duplication: repeated logic across modules that should be extracted into a shared helper or context function
- Bloated functions: functions doing more than one thing; complex
withchains that should be broken into named steps - Primitive obsession: raw strings/integers used where a well-named type, struct, or enum would be clearer
- Feature envy: a module reaching deeply into another context's internals instead of calling its public API
- Unnecessary complexity: over-engineered abstractions for one-time operations; speculative generality (YAGNI)
- Stale code: unused functions, dead branches, obsolete modules, leftover
IO.inspect/dbgcalls, commented-out blocks - Inconsistent naming: functions or variables that don't follow Elixir conventions or contradict surrounding code
- No nested modules in a single file — one module per file, always
- OTP paths: no
:code.priv_dir/1in module attributes (@var); useApplication.app_dir(:baudrate, "priv/...")in functions (runtime resolution) - Cache coherence: settings and board mutations go through context functions; direct
Repowrites tosettings/boardsmust callSettingsCache.refresh()/BoardCache.refresh()manually - Avatar sizes: integers
[120, 48, 36, 24]only — never string names
Step 8 — UI/UX and Accessibility (a11y)
Semantic HTML
<section aria-labelledby="…">for headed content areas<article>for self-contained content items in lists<aside>for supplementary content<nav>for navigation landmarks- Semantic
idattributes on content containers; uniqueid+ semantic CSS class on each list item
WAI-ARIA compliance
- Interactive elements (buttons, links, inputs) have descriptive labels — either visible text,
aria-label, oraria-labelledby - Dynamic regions that update without a page navigation use
aria-liveappropriately - Modal dialogs trap focus and restore it on close;
role="dialog"+aria-modal="true"present - Icon-only buttons and icon links always have
aria-labelor visually-hidden text - Form fields have associated
<label>elements (viafor/idor wrapping); error messages linked viaaria-describedby
Keyboard and focus
- All interactive elements reachable and operable via keyboard alone
- Focus order is logical and follows visual reading order
data-focus-targetpresent on primary content container in list/browse pages for post-navigation auto-focus- No keyboard traps outside intentional modal dialogs
- Focus-visible ring visible on focused elements (check
app.cssfocus-visiblestyles)
Responsive design
- Layouts adapt correctly across mobile, tablet, and desktop breakpoints
- No horizontal overflow on small viewports from fixed widths
- Touch targets ≥ 44×44 px on interactive elements
Colour and contrast
- Text contrast ratio ≥ 4.5:1 (normal text) / 3:1 (large text) per WCAG 2.1 AA
- Information is not conveyed by colour alone — always paired with text, icon, or pattern
Layout correctness
{@inner_content}(not@inner_block) in layouts- Templates not wrapped in
<Layouts.app>(causes duplicate flash IDs)
Reporting
Present all findings grouped by severity:
| Severity | Criteria |
|---|---|
| Critical | Security vulnerabilities, data loss, auth bypass, key material exposure — fix immediately |
| Major | Logic errors, missing test coverage for observable behaviour, broken conventions that cause runtime failures, a11y barriers that block screen-reader or keyboard users |
| Minor | Style, clarity, missing docs, i18n gaps, cosmetic a11y issues, minor code smells |
For each finding: cite the file and line number, describe the issue, explain the impact, and provide a concrete fix.
Fixing
After reporting, apply fixes for all Critical and Major findings directly. Then run the full test suite:
for p in 1 2 3 4; do MIX_TEST_PARTITION=$p mix test --partitions 4 --seed 9527 & done; wait
Do not consider the review complete until all tests pass. Diagnose and resolve any failures before finishing.