Jordi Enric

Software Engineer at Supabase logoSupabase

Back

March 19, 2026

code-review skill

Here's my personal, opinionated code-review skill for code agents. You can ask your agent to install it.

---
name: code-review
description: Run a structured code review on the current branch's changes. Use when asked to "review my code", "review my changes", "check my PR", "run a code review", or "review this branch". Covers formatting, type safety, duplication, readability, test coverage, and comment quality. Always use this skill when the user wants a code review — don't just eyeball it.
---

# Studio Code Review

Perform a thorough code review on the changed files in the current branch. Work through each check below in order, fixing issues as you find them rather than just reporting them.

## Review Checklist

### 1. Prettier + TypeScript

Run formatting and type checks on the changed files only.

```bash
# From the repo root
npx prettier --check <changed files>
npx tsc --noEmit
```

Fix any prettier violations. Report any TypeScript errors  if they're in code you touched, fix them.

### 2. No Type Assertions

Search for type assertions (`as any`, `as SomeType`, `!` non-null assertions) in the changed files. These are a red flag — they silence the type system instead of satisfying it.

When you find one, ask: *why does this assertion exist?* The right fix is usually one of:
- Validate the value at runtime (zod works well for external data) and let TypeScript infer the narrowed type
- Fix the upstream type so the assertion isn't needed
- Use a proper type guard

Avoid mechanical "add zod everywhere"  use the approach that best fits the context.

### 3. Duplicate Code and Types

Look across the diff for:
- Repeated logic that could be shared (a function, a hook, a util)
- Types defined more than once for the same shape
- Copy-pasted blocks with minor variations

Consolidate where it makes sense. Don't over-abstract — three similar lines are fine; a pattern repeated four or more times probably warrants a shared helper.

### 4. Readable Conditions

Complex boolean expressions are hard to reason about. When you see an `if` statement (or ternary) with multiple conditions joined by `&&` / `||`, especially with negations, extract it to a named constant that explains the intent:

```ts
// Hard to scan
if (!isLoading && user !== null && user.role !== 'guest') { ... }

// Clear intent
const canAccessDashboard = !isLoading && user !== null && user.role !== 'guest'
if (canAccessDashboard) { ... }
```

Apply this when the expression has 3+ conditions, involves negation, or when the intent isn't immediately obvious from the raw conditions alone. Simple two-part conditions are usually fine as-is.

### 5. Test Coverage with Vitest

For each changed file, ask: *is there logic here that could fail silently?* If yes, write a test.

Good candidates:
- Utility functions with branching logic
- Data transformations or formatters
- Custom hooks with non-trivial state
- Validation logic

Tests live next to the file they test:
```
my-module.tsx        ← source
my-module.test.tsx   ← tests (same directory)
```

Don't write tests for things that are trivially correct or are better covered by E2E tests. Focus on logic that's easy to get wrong and cheap to verify.

### 6. Logic Out of React Components

React components should describe UI, not implement business logic. If a component contains complex data transformations, multi-step calculations, or branching logic, move that logic to a utility function outside the component.

This makes the logic:
- Testable in isolation (no render needed)
- Reusable by other components
- Easier to read in both places

```tsx
// Before: logic buried in component
function UserCard({ user }) {
  const label = user.subscriptions.filter(s => !s.expired).length > 1
    ? `${user.subscriptions.filter(s => !s.expired).length} active plans`
    : 'Free tier'
  return <div>{label}</div>
}

// After: logic extracted and testable
function getSubscriptionLabel(subscriptions: Subscription[]): string {
  const active = subscriptions.filter(s => !s.expired)
  return active.length > 1 ? `${active.length} active plans` : 'Free tier'
}

function UserCard({ user }) {
  return <div>{getSubscriptionLabel(user.subscriptions)}</div>
}
```

### 7. Comment Quality

Remove comments that just restate what the next line does  they add noise and go stale:

```ts
// Bad: says what, not why
// Increment counter
count++

// Increment retry count before re-entering the queue
// to prevent infinite retries when the handler always throws
count++
```

Keep comments that explain *why* something non-obvious is done that way. Delete the rest. If you're unsure whether a comment adds value, it probably doesn't.

---

## Output Format

After completing all checks, give a brief summary:

- What you fixed (with file references)
- Any issues you found but couldn't fix, and why
- Anything the author should know or decide

Keep it concise. The user can see the diff.

Back to all posts