Recheck (ActiveCAMT full-diff review)
One pass that re-checks the current diff across every dimension that has bitten this
project before: bugs, security/access-control, PDPA & medical-data exposure,
performance, and refactor/cleanup. Default scope is the current diff vs the base
branch — focused, PR-ready. Report findings; only edit when the user asks.
When to run
- Before opening a PR or deploying (pairs with [[safe-deploy]] for schema changes).
- Whenever the user says "re-check", "review everything", "anything wrong with this?",
or wants a quality gate before shipping.
Step 1 — Establish the diff
git rev-parse --abbrev-ref HEAD # current branch (should NOT be main)
git fetch origin main --quiet 2>/dev/null || true
git diff --stat $(git merge-base origin/main HEAD 2>/dev/null || echo main)...HEAD
git diff $(git merge-base origin/main HEAD 2>/dev/null || echo main)...HEAD
If there are uncommitted changes, also review git diff and git diff --staged.
Read the full diff before forming findings — never review from filenames alone.
For any changed file where the surrounding context matters (a function you can only
see half of), open the file and read enough around the hunk to judge it correctly.
Step 2 — Review across all dimensions
Go through every dimension. For each finding, note file:line, the dimension
(bug / security / perf / refactor), a severity tier, and a concrete fix. If a
dimension is clean, say so explicitly — silence is not the same as "checked and fine".
Severity tiers (assign exactly one per finding):
- 🔴 Crucial — must fix before merge. Will break prod, corrupt/lose data, expose
a security hole, or leak PDPA/medical data. Anything that, if shipped, causes an
incident.
- 🟠 Moderate — should fix. A real bug, missing authz on a low-traffic path, an
N+1 on a growing table, or a correctness gap with a workaround. Not catastrophic,
but wrong.
- 🟡 Low — minor. Edge case unlikely in practice, small inefficiency, style/type
tightening, dead code. Safe to defer.
- ⚡ Quick win — a cross-cut tag, not a fourth severity: flag any finding whose
fix is small/low-risk (≈ a few lines, no design change) and clearly worth it.
A finding can be both Crucial and a Quick win — tag it
🔴 ⚡. Surface these
prominently so the user can knock them out immediately.
Apply the tiers to bugs, security, AND performance findings alike — a slow query and
an auth gap both get a tier.
1. Correctness & bugs
- Logic errors, off-by-one, wrong conditionals, inverted boolean checks.
- Unhandled promise rejections, missing
await, race conditions.
- Null/undefined access, unsafe non-null assertions, empty-array/edge cases.
- Error paths: are thrown errors caught and surfaced, or swallowed?
- Does the change actually do what its commit/PR message claims?
2. Security & access control ⚠️ highest-risk area in this repo
src/proxy.ts middleware runs FIRST and is easy to forget. Access is gated in
4 layers — if this change touches auth, roles, or a protected route, verify the
rule holds at every layer, not just the one in the diff. (This caused the
SMO-scanner 5-PR loop — see [[project_admin_access_layers]].)
- Authz on every new/changed API route and server action: is the caller's role
actually checked server-side, not just hidden in the UI?
- IDOR: can a user pass someone else's id and read/write rows they shouldn't?
- Input validation on anything from the request (body, query, params).
- Secrets: no keys/tokens in client bundles, logs, or committed env.
- SQL/Drizzle: parameterized only — no string-interpolated user input.
- Auth domain gate (
src/auth.ts signIn): the callback comment claims a
university-domain restriction (FE-01), but a stray return true silently lets any
Google account in. Verify the code actually return falses non-allowed domains —
don't trust the comment. (Also watch allowDangerousEmailAccountLinking.)
3. PDPA & medical-data exposure ⚠️ this is a regulated, PDPA-sensitive app
- Medical: registration sees the SIGNAL (who has a condition), never the DETAIL.
Detail +
medsCheckOption is admin-only. Verify the diff never leaks medical
detail to a non-admin surface (API response, log, client prop, CSV export).
See [[project_attendance_medical_access]].
- Two medical paths — check BOTH. The attendance endpoint sanitizes medical to
categories for non-super_admin/admin, but the scanner service
(
src/modules/events/scanner.service.ts processScan) builds a studentWithMedical
payload that /api/admin/scan returns to registration/organizer/smo. The UI
only shows labels, but the raw free-text is in the JSON (DevTools-readable). The desk
needs only hasMedicalCondition; confirm the detail fields are stripped for
non-super_admin/admin on the scan path, not just attendance.
- Emergency contacts: visible to admin roles only.
- No PII (names, student ids, phone, medical) in
console.log, error messages, or
analytics that ship to the client or third parties.
- Any new field added to a user-facing payload: confirm it's meant to be exposed.
4. Performance
- N+1 queries — loops issuing one DB call per item; batch with
inArray/joins.
- Queries inside React render or per-request hot paths that should be cached/memoized.
- Missing
await parallelization (Promise.all) where calls are independent.
- Large client bundles: heavy imports pulled into client components; prefer server
components / dynamic import. Next.js
sin1 region — avoid chatty round-trips.
- Unbounded queries (no
limit) on tables that grow (activity feed, attendance).
- Polled endpoints must be O(1), not O(table). Never
with: { attendances: true }
.length to get a count on a polled endpoint — use a COUNT(*) GROUP BY aggregate.
A polled query over a table that grows during the event (/api/admin/events did
this every 8 s) is the classic pooler-starvation 504 trigger here. Also don't run
checkAndAward*() on a hot read path — they run via /api/admin/award-check + cron.
4b. Free-tier capacity (Supabase free + Vercel Hobby, ~400–500 concurrent / event)
- Sessions must stay JWT (
strategy:"jwt" in src/auth.ts) so auth() does no
per-request DB hit; the periodic DB refresh must live in the jwt callback (persists
to the cookie) so it fires at most once per interval, not every request.
- Pool stays
max:5 over the transaction pooler (:6543, prepare:false).
- Student-facing polls stay ≥60 s with a low, bounded per-tick query count; prefer
cacheable (
s-maxage) responses for data shared across users.
src/lib/rate-limit.ts is per-instance in-memory — not a global limit on Vercel.
- Vercel Hobby has no overage — it PAUSES at limits; recommend Pro for a critical
one-shot event. The binding Supabase risk is connection-holding by a slow/growing
query, not raw query volume.
5. Refactor, simplification & reuse
- Duplicated logic that an existing helper already covers — search before flagging.
- Dead code, unused vars/imports, commented-out blocks left in the diff.
- Over-complex conditionals that simplify; values that should be named constants.
- Naming/idiom consistency with surrounding code.
- Tighter types — replace
any, narrow where the data shape is known.
6. Tests & build hygiene (quick check)
Step 3 — Report
Group findings by severity tier, most serious first. Tag the dimension on each line
([bug] / [security] / [perf] / [refactor]) and append ⚡ to quick wins.
Lead with a one-line scoreboard so the user sees the shape at a glance.
## Recheck — <branch>
🔴 2 crucial 🟠 3 moderate 🟡 4 low ⚡ 5 quick wins
### ⚡ Quick wins (small fix, do these now)
- src/db/queries.ts:88 — 🟠 [perf] N+1: one query per attendee. Batch with inArray(). (~3 lines)
- src/lib/log.ts:20 — 🔴 [security] studentId logged to console; drop the field. (1 line)
### 🔴 Crucial (must fix before merge)
- src/app/api/.../route.ts:42 — [security] No server-side role check; any logged-in
user can hit this. proxy.ts doesn't cover it either. Fix: gate with <helper>.
- src/app/api/.../route.ts:61 — [bug] medical detail returned in payload to a
non-admin caller — PDPA leak. Fix: strip to signal unless isAdmin.
### 🟠 Moderate (should fix)
- src/db/queries.ts:120 — [perf] Unbounded attendance query, no limit. Page it.
### 🟡 Low (defer ok)
- src/components/X.tsx:12 — [refactor] Duplicates formatThaiDate(); reuse it.
### Clean
Correctness ✓ PDPA/medical ✓ Build ✓
(Quick wins are also listed under their own tier — the ⚡ section is a curated
shortlist, not a separate set of findings. Don't double-count them in the scoreboard:
the tier counts cover everything; ⚡ counts how many of those are quick wins.)
End with a one-line verdict: ship, ship after crucials, or needs work.
Notes
- Default is report-only. Apply fixes only if the user asks (then keep edits
minimal and matched to surrounding style).
- This skill does not deploy or migrate. For schema/deploy safety use [[safe-deploy]].
- For a deeper cloud multi-agent pass, the user can run
/code-review ultra
themselves — you cannot launch it.
1---2name: recheck3description: Comprehensive multi-dimension review of the current diff for ActiveCAMT (Next.js + Supabase + Vercel) — checks correctness/bugs, security & access control, PDPA/medical data exposure, performance, and refactor/simplification in one pass, ranking every finding as crucial / moderate / low and flagging quick wins. Use before opening a PR, before deploying, or any time the user asks to "re-check", "review everything", or "look it over" for quality. Reviews only the current diff vs the base branch by default.4---56# Recheck (ActiveCAMT full-diff review)78One pass that re-checks the current diff across every dimension that has bitten this9project before: **bugs, security/access-control, PDPA & medical-data exposure,10performance, and refactor/cleanup.** Default scope is the current diff vs the base11branch — focused, PR-ready. Report findings; only edit when the user asks.1213## When to run14- Before opening a PR or deploying (pairs with [[safe-deploy]] for schema changes).15- Whenever the user says "re-check", "review everything", "anything wrong with this?",16 or wants a quality gate before shipping.1718## Step 1 — Establish the diff1920```bash21git rev-parse --abbrev-ref HEAD # current branch (should NOT be main)22git fetch origin main --quiet 2>/dev/null || true23git diff --stat $(git merge-base origin/main HEAD 2>/dev/null || echo main)...HEAD24git diff $(git merge-base origin/main HEAD 2>/dev/null || echo main)...HEAD25```2627If there are uncommitted changes, also review `git diff` and `git diff --staged`.28Read the full diff before forming findings — never review from filenames alone.29For any changed file where the surrounding context matters (a function you can only30see half of), open the file and read enough around the hunk to judge it correctly.3132## Step 2 — Review across all dimensions3334Go through every dimension. For each finding, note **file:line**, the **dimension**35(bug / security / perf / refactor), a **severity tier**, and a concrete fix. If a36dimension is clean, say so explicitly — silence is not the same as "checked and fine".3738**Severity tiers** (assign exactly one per finding):39- **🔴 Crucial** — must fix before merge. Will break prod, corrupt/lose data, expose40 a security hole, or leak PDPA/medical data. Anything that, if shipped, causes an41 incident.42- **🟠 Moderate** — should fix. A real bug, missing authz on a low-traffic path, an43 N+1 on a growing table, or a correctness gap with a workaround. Not catastrophic,44 but wrong.45- **🟡 Low** — minor. Edge case unlikely in practice, small inefficiency, style/type46 tightening, dead code. Safe to defer.47- **⚡ Quick win** — a *cross-cut tag*, not a fourth severity: flag any finding whose48 fix is small/low-risk (≈ a few lines, no design change) **and** clearly worth it.49 A finding can be both Crucial **and** a Quick win — tag it `🔴 ⚡`. Surface these50 prominently so the user can knock them out immediately.5152Apply the tiers to bugs, security, AND performance findings alike — a slow query and53an auth gap both get a tier.5455### 1. Correctness & bugs56- Logic errors, off-by-one, wrong conditionals, inverted boolean checks.57- Unhandled promise rejections, missing `await`, race conditions.58- Null/undefined access, unsafe non-null assertions, empty-array/edge cases.59- Error paths: are thrown errors caught and surfaced, or swallowed?60- Does the change actually do what its commit/PR message claims?6162### 2. Security & access control ⚠️ highest-risk area in this repo63- **`src/proxy.ts` middleware runs FIRST** and is easy to forget. Access is gated in64 **4 layers** — if this change touches auth, roles, or a protected route, verify the65 rule holds at *every* layer, not just the one in the diff. (This caused the66 SMO-scanner 5-PR loop — see [[project_admin_access_layers]].)67- Authz on every new/changed API route and server action: is the caller's role68 actually checked server-side, not just hidden in the UI?69- IDOR: can a user pass someone else's id and read/write rows they shouldn't?70- Input validation on anything from the request (body, query, params).71- Secrets: no keys/tokens in client bundles, logs, or committed env.72- SQL/Drizzle: parameterized only — no string-interpolated user input.73- **Auth domain gate (`src/auth.ts` `signIn`)**: the callback comment claims a74 university-domain restriction (FE-01), but a stray `return true` silently lets any75 Google account in. Verify the code actually `return false`s non-allowed domains —76 don't trust the comment. (Also watch `allowDangerousEmailAccountLinking`.)7778### 3. PDPA & medical-data exposure ⚠️ this is a regulated, PDPA-sensitive app79- **Medical: registration sees the SIGNAL (who has a condition), never the DETAIL.**80 Detail + `medsCheckOption` is **admin-only**. Verify the diff never leaks medical81 detail to a non-admin surface (API response, log, client prop, CSV export).82 See [[project_attendance_medical_access]].83- **Two medical paths — check BOTH.** The *attendance* endpoint sanitizes medical to84 categories for non-super_admin/admin, but the *scanner service*85 (`src/modules/events/scanner.service.ts` `processScan`) builds a `studentWithMedical`86 payload that `/api/admin/scan` returns to `registration`/`organizer`/`smo`. The UI87 only shows labels, but the raw free-text is in the JSON (DevTools-readable). The desk88 needs only `hasMedicalCondition`; confirm the detail fields are stripped for89 non-super_admin/admin on the **scan path**, not just attendance.90- Emergency contacts: visible to admin roles only.91- No PII (names, student ids, phone, medical) in `console.log`, error messages, or92 analytics that ship to the client or third parties.93- Any new field added to a user-facing payload: confirm it's meant to be exposed.9495### 4. Performance96- N+1 queries — loops issuing one DB call per item; batch with `inArray`/joins.97- Queries inside React render or per-request hot paths that should be cached/memoized.98- Missing `await` parallelization (`Promise.all`) where calls are independent.99- Large client bundles: heavy imports pulled into client components; prefer server100 components / dynamic import. Next.js `sin1` region — avoid chatty round-trips.101- Unbounded queries (no `limit`) on tables that grow (activity feed, attendance).102- **Polled endpoints must be O(1), not O(table).** Never `with: { attendances: true }`103 + `.length` to get a count on a polled endpoint — use a `COUNT(*) GROUP BY` aggregate.104 A polled query over a table that grows *during* the event (`/api/admin/events` did105 this every 8 s) is the classic pooler-starvation 504 trigger here. Also don't run106 `checkAndAward*()` on a hot read path — they run via `/api/admin/award-check` + cron.107108### 4b. Free-tier capacity (Supabase free + Vercel Hobby, ~400–500 concurrent / event)109- **Sessions must stay JWT** (`strategy:"jwt"` in `src/auth.ts`) so `auth()` does no110 per-request DB hit; the periodic DB refresh must live in the `jwt` callback (persists111 to the cookie) so it fires at most once per interval, not every request.112- **Pool stays `max:5`** over the transaction pooler (`:6543`, `prepare:false`).113- **Student-facing polls stay ≥60 s** with a low, bounded per-tick query count; prefer114 cacheable (`s-maxage`) responses for data shared across users.115- `src/lib/rate-limit.ts` is **per-instance in-memory** — not a global limit on Vercel.116- Vercel Hobby has **no overage — it PAUSES at limits**; recommend Pro for a critical117 one-shot event. The binding Supabase risk is connection-holding by a slow/growing118 query, not raw query volume.119120### 5. Refactor, simplification & reuse121- Duplicated logic that an existing helper already covers — search before flagging.122- Dead code, unused vars/imports, commented-out blocks left in the diff.123- Over-complex conditionals that simplify; values that should be named constants.124- Naming/idiom consistency with surrounding code.125- Tighter types — replace `any`, narrow where the data shape is known.126127### 6. Tests & build hygiene (quick check)128- Does new logic have/ need a test? Did the change break an existing one?129- Run lint/build when the diff is non-trivial:130 ```bash131 npm run lint132 npm run build # catches type/route errors before Vercel does133 ```134135## Step 3 — Report136137Group findings by severity tier, most serious first. Tag the dimension on each line138(`[bug]` / `[security]` / `[perf]` / `[refactor]`) and append `⚡` to quick wins.139Lead with a one-line scoreboard so the user sees the shape at a glance.140141```142## Recheck — <branch>143🔴 2 crucial 🟠 3 moderate 🟡 4 low ⚡ 5 quick wins144145### ⚡ Quick wins (small fix, do these now)146- src/db/queries.ts:88 — 🟠 [perf] N+1: one query per attendee. Batch with inArray(). (~3 lines)147- src/lib/log.ts:20 — 🔴 [security] studentId logged to console; drop the field. (1 line)148149### 🔴 Crucial (must fix before merge)150- src/app/api/.../route.ts:42 — [security] No server-side role check; any logged-in151 user can hit this. proxy.ts doesn't cover it either. Fix: gate with <helper>.152- src/app/api/.../route.ts:61 — [bug] medical detail returned in payload to a153 non-admin caller — PDPA leak. Fix: strip to signal unless isAdmin.154155### 🟠 Moderate (should fix)156- src/db/queries.ts:120 — [perf] Unbounded attendance query, no limit. Page it.157158### 🟡 Low (defer ok)159- src/components/X.tsx:12 — [refactor] Duplicates formatThaiDate(); reuse it.160161### Clean162Correctness ✓ PDPA/medical ✓ Build ✓163```164165(Quick wins are also listed under their own tier — the ⚡ section is a curated166shortlist, not a separate set of findings. Don't double-count them in the scoreboard:167the tier counts cover everything; `⚡` counts how many of those are quick wins.)168169End with a one-line verdict: **ship**, **ship after crucials**, or **needs work**.170171## Notes172- Default is **report-only**. Apply fixes only if the user asks (then keep edits173 minimal and matched to surrounding style).174- This skill does not deploy or migrate. For schema/deploy safety use [[safe-deploy]].175- For a deeper cloud multi-agent pass, the user can run `/code-review ultra`176 themselves — you cannot launch it.