Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ future coding agent read the same instructions from here.
| Directory | Contains |
| ------------- | --------------------------------------------------------------------- |
| `skills/` | Reusable **operational playbooks** — how to do a kind of work well. |
| `references/` | Supporting material loaded only when a skill needs it. |
| `roles/` | Reusable **agent personas** — focused roles with a mission and scope. |
| `workflows/` | **Task workflows** — how to execute common end-to-end tasks. |

Expand Down Expand Up @@ -55,6 +56,11 @@ Area skills:
| [prompt-rendering](skills/prompt-rendering.md) | Prompt templates and rendering. |
| [sidecar-auth-context](skills/sidecar-auth-context.md) | Plugin context and protected-node access. |
| [mcp-tools](skills/mcp-tools.md) | Tool plugins and MCP servers. |
| [vercel-react-best-practices](skills/vercel-react-best-practices.md) | React chat widget implementation, review, and performance. |

The Vercel skill includes 70 vendored rules under
`references/vercel-react-best-practices/rules/`. Its canonical entrypoint records
the upstream revision and license; generated adapters use the same references.

### `roles/` — agent personas

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Do Not Put Effect Events in Dependency Arrays
impact: LOW
impactDescription: avoids unnecessary effect re-runs and lint errors
tags: advanced, hooks, useEffectEvent, dependencies, effects
---

## Do Not Put Effect Events in Dependency Arrays

Effect Event functions do not have a stable identity. Their identity intentionally changes on every render. Do not include the function returned by `useEffectEvent` in a `useEffect` dependency array. Keep the actual reactive values as dependencies and call the Effect Event from inside the effect body or subscriptions created by that effect.

**Incorrect (Effect Event added as a dependency):**

```tsx
import { useEffect, useEffectEvent } from 'react'

function ChatRoom({ roomId, onConnected }: {
roomId: string
onConnected: () => void
}) {
const handleConnected = useEffectEvent(onConnected)

useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', handleConnected)
connection.connect()

return () => connection.disconnect()
}, [roomId, handleConnected])
}
```

Including the Effect Event in dependencies makes the effect re-run every render and triggers the React Hooks lint rule.

**Correct (depend on reactive values, not the Effect Event):**

```tsx
import { useEffect, useEffectEvent } from 'react'

function ChatRoom({ roomId, onConnected }: {
roomId: string
onConnected: () => void
}) {
const handleConnected = useEffectEvent(onConnected)

useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', handleConnected)
connection.connect()

return () => connection.disconnect()
}, [roomId])
}
```

Reference: [React useEffectEvent: Effect Event in deps](https://react.dev/reference/react/useEffectEvent#effect-event-in-deps)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Store Event Handlers in Refs
impact: LOW
impactDescription: stable subscriptions
tags: advanced, hooks, refs, event-handlers, optimization
---

## Store Event Handlers in Refs

Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.

**Incorrect (re-subscribes on every render):**

```tsx
function useWindowEvent(event: string, handler: (e) => void) {
useEffect(() => {
window.addEventListener(event, handler)
return () => window.removeEventListener(event, handler)
}, [event, handler])
}
```

**Correct (stable subscription):**

```tsx
function useWindowEvent(event: string, handler: (e) => void) {
const handlerRef = useRef(handler)
useEffect(() => {
handlerRef.current = handler
}, [handler])

useEffect(() => {
const listener = (e) => handlerRef.current(e)
window.addEventListener(event, listener)
return () => window.removeEventListener(event, listener)
}, [event])
}
```

**Alternative: use `useEffectEvent` if you're on latest React:**

```tsx
import { useEffectEvent } from 'react'

function useWindowEvent(event: string, handler: (e) => void) {
const onEvent = useEffectEvent(handler)

useEffect(() => {
window.addEventListener(event, onEvent)
return () => window.removeEventListener(event, onEvent)
}, [event])
}
```

`useEffectEvent` provides a cleaner API for the same pattern. Its returned function is not stable: React returns a new one on every render, so keep it out of dependency arrays (see `advanced-effect-event-deps.md`). The subscription still works because the function registered by the effect, from whichever render, always runs the latest handler with the latest props and state.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: Initialize App Once, Not Per Mount
impact: LOW-MEDIUM
impactDescription: avoids duplicate init in development
tags: initialization, useEffect, app-startup, side-effects
---

## Initialize App Once, Not Per Mount

Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.

**Incorrect (runs twice in dev, re-runs on remount):**

```tsx
function Comp() {
useEffect(() => {
loadFromStorage()
checkAuthToken()
}, [])

// ...
}
```

**Correct (once per app load):**

```tsx
let didInit = false

function Comp() {
useEffect(() => {
if (didInit) return
didInit = true
loadFromStorage()
checkAuthToken()
}, [])

// ...
}
```

Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: useEffectEvent for Latest-Value Callbacks
impact: LOW
impactDescription: prevents effect re-runs
tags: advanced, hooks, useEffectEvent, refs, optimization
---

## useEffectEvent for Latest-Value Callbacks

Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.

The function returned by `useEffectEvent` is not stable: React intentionally returns a new one on every render. Call it only from inside effects, and keep it out of dependency arrays (see `advanced-effect-event-deps.md`). It avoids re-runs because it always reads the latest props and state, not because its identity is stable.

**Incorrect (effect re-runs on every callback change):**

```tsx
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState('')

useEffect(() => {
const timeout = setTimeout(() => onSearch(query), 300)
return () => clearTimeout(timeout)
}, [query, onSearch])
}
```

**Correct (using React's useEffectEvent):**

```tsx
import { useEffectEvent } from 'react';

function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState('')
const onSearchEvent = useEffectEvent(onSearch)

useEffect(() => {
const timeout = setTimeout(() => onSearchEvent(query), 300)
return () => clearTimeout(timeout)
}, [query])
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Prevent Waterfall Chains in API Routes
impact: CRITICAL
impactDescription: 2-10× improvement
tags: api-routes, server-actions, waterfalls, parallelization
---

## Prevent Waterfall Chains in API Routes

In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.

**Incorrect (config waits for auth, data waits for both):**

```typescript
export async function GET(request: Request) {
const session = await auth()
const config = await fetchConfig()
const data = await fetchData(session.user.id)
return Response.json({ data, config })
}
```

**Correct (auth and config start immediately):**

```typescript
export async function GET(request: Request) {
const sessionPromise = auth()
const configPromise = fetchConfig()
Comment thread
AmitAvital1 marked this conversation as resolved.
// Mark the early start as handled: if auth fails or returns early, a later
// config rejection must not become an unhandled rejection.
configPromise.catch(() => {})
const session = await sessionPromise
if (!session) return new Response('Unauthorized', { status: 401 })
const [config, data] = await Promise.all([
configPromise,
fetchData(session.user.id)
])
return Response.json({ data, config })
}
```

Start work before authentication only when it is safe for an unauthenticated caller to trigger: cheap, free of side effects, and independent of the caller. Otherwise wait for authentication first.

For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Check Cheap Conditions Before Async Flags
impact: HIGH
impactDescription: avoids unnecessary async work when a synchronous guard already fails
tags: async, await, feature-flags, short-circuit, conditional
---

## Check Cheap Conditions Before Async Flags

When a branch uses `await` for a flag or remote value and also requires a **cheap synchronous** condition (local props, request metadata, already-loaded state), evaluate the cheap condition **first**. Otherwise you pay for the async call even when the compound condition can never be true.

This is a specialization of [Defer Await Until Needed](./async-defer-await.md) for `flag && cheapCondition` style checks.

**Incorrect:**

```typescript
const someFlag = await getFlag()

if (someFlag && someCondition) {
// ...
}
```

**Correct:**

```typescript
if (someCondition) {
const someFlag = await getFlag()
if (someFlag) {
// ...
}
}
```

This matters when `getFlag` hits the network, a feature-flag service, or `React.cache` / DB work: skipping it when `someCondition` is false removes that cost on the cold path.

Keep the original order if `someCondition` is expensive, depends on the flag, or you must run side effects in a fixed order.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Defer Await Until Needed
impact: HIGH
impactDescription: avoids blocking unused code paths
tags: async, await, conditional, optimization
---

## Defer Await Until Needed

Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.

**Incorrect (blocks both branches):**

```typescript
async function handleRequest(userId: string, skipProcessing: boolean) {
const userData = await fetchUserData(userId)

if (skipProcessing) {
// Returns immediately but still waited for userData
return { skipped: true }
}

// Only this branch uses userData
return processUserData(userData)
}
```

**Correct (only blocks when needed):**

```typescript
async function handleRequest(userId: string, skipProcessing: boolean) {
if (skipProcessing) {
// Returns immediately without waiting
return { skipped: true }
}

// Fetch only when needed
const userData = await fetchUserData(userId)
return processUserData(userData)
}
```

**Another example (early return optimization):**

```typescript
// Incorrect: always fetches permissions
async function updateResource(resourceId: string, userId: string) {
const permissions = await fetchPermissions(userId)
const resource = await getResource(resourceId)

if (!resource) {
return { error: 'Not found' }
}

if (!permissions.canEdit) {
return { error: 'Forbidden' }
}

return await updateResourceData(resource, permissions)
}

// Correct: fetches only when needed
async function updateResource(resourceId: string, userId: string) {
const resource = await getResource(resourceId)

if (!resource) {
return { error: 'Not found' }
}

const permissions = await fetchPermissions(userId)

if (!permissions.canEdit) {
return { error: 'Forbidden' }
}

return await updateResourceData(resource, permissions)
}
```

This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.

For `await getFlag()` combined with a cheap synchronous guard (`flag && someCondition`), see [Check Cheap Conditions Before Async Flags](./async-cheap-condition-before-await.md).
Loading
Loading