+}
+```
+
+Calling a component as a function makes React treat it as part of the parent's render. This means:
+- No separate fiber node for reconciliation
+- State and effects are tied to the parent
+- Keys and refs don't work as expected
+
+### Rule 2: Never pass Hooks as regular values
+
+**Incorrect (passing hook as prop):**
+
+```tsx
+function ChatRoom({ useStatus }) {
+ const status = useStatus() // Hook passed as value
+ return
{status}
+}
+
+
+```
+
+**Correct (call hook directly, pass result as prop):**
+
+```tsx
+function ChatRoom({ status }) {
+ return
{status}
+}
+
+function ChatRoomWrapper() {
+ const status = useOnlineStatus()
+ return
+}
+```
+
+Passing hooks as values makes them opaque to React's static analysis, breaks the Rules of Hooks, and prevents the compiler from optimizing correctly.
+
+Reference: [React calls Components and Hooks](https://react.dev/reference/rules/react-calls-components-and-hooks)
diff --git a/.agents/skills/review-react/rules/react-rules-hooks.md b/.agents/skills/review-react/rules/react-rules-hooks.md
new file mode 100644
index 000000000..6d98c5c7a
--- /dev/null
+++ b/.agents/skills/review-react/rules/react-rules-hooks.md
@@ -0,0 +1,91 @@
+---
+title: Rules of Hooks
+impact: CRITICAL
+impactDescription: violating causes runtime errors and broken state
+tags: react-rules, hooks, top-level
+---
+
+## Rules of Hooks
+
+**Impact: CRITICAL (violating causes runtime errors and broken state)**
+
+Hooks rely on a stable call order. Calling them conditionally or in loops breaks React's ability to track state correctly.
+
+### Rule 1: Only call Hooks at the top level
+
+**Incorrect (hook inside condition):**
+
+```tsx
+function Form({ showName }) {
+ if (showName) {
+ const [name, setName] = useState('') // Conditional hook call
+ }
+ const [email, setEmail] = useState('')
+ return setEmail(e.target.value)} />
+}
+```
+
+**Correct (always call hooks, conditionally use values):**
+
+```tsx
+function Form({ showName }) {
+ const [name, setName] = useState('')
+ const [email, setEmail] = useState('')
+ return (
+ <>
+ {showName && setName(e.target.value)} />}
+ setEmail(e.target.value)} />
+ >
+ )
+}
+```
+
+**Incorrect (hook inside loop):**
+
+```tsx
+function Filters({ filters }) {
+ const values = []
+ for (const f of filters) {
+ values.push(useState(f.default)) // Hook in loop
+ }
+ return <>{/* ... */}>
+}
+```
+
+**Correct (extract to child component or use single state):**
+
+```tsx
+function Filters({ filters }) {
+ const [values, setValues] = useState(() =>
+ Object.fromEntries(filters.map(f => [f.id, f.default]))
+ )
+ return <>{/* ... */}>
+}
+```
+
+### Rule 2: Only call Hooks from React functions
+
+**Incorrect (hook in regular function):**
+
+```tsx
+function getUser() {
+ const [user, setUser] = useState(null) // Not a React component
+ return user
+}
+```
+
+**Correct (hook in component or custom hook):**
+
+```tsx
+function useUser() {
+ const [user, setUser] = useState(null)
+ return user
+}
+
+function Profile() {
+ const user = useUser()
+ return
{user?.name}
+}
+```
+
+Reference: [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)
diff --git a/.agents/skills/review-react/rules/react-rules-purity.md b/.agents/skills/review-react/rules/react-rules-purity.md
new file mode 100644
index 000000000..9f16fd5b9
--- /dev/null
+++ b/.agents/skills/review-react/rules/react-rules-purity.md
@@ -0,0 +1,121 @@
+---
+title: Components and Hooks Must Be Pure
+impact: CRITICAL
+impactDescription: prevents bugs from non-deterministic rendering
+tags: react-rules, purity, side-effects, immutability
+---
+
+## Components and Hooks Must Be Pure
+
+**Impact: CRITICAL (prevents bugs from non-deterministic rendering)**
+
+React assumes components are pure functions: same inputs, same output. Side effects during render cause bugs that are hard to reproduce, especially with concurrent features and Strict Mode.
+
+### Rule 1: Components must be idempotent
+
+**Incorrect (mutating external variable during render):**
+
+```tsx
+let count = 0
+
+function Counter() {
+ count++ // Side effect during render
+ return
+}
+```
+
+References: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
diff --git a/.agents/skills/review-react/rules/rerender-derived-state.md b/.agents/skills/review-react/rules/rerender-derived-state.md
new file mode 100644
index 000000000..e5c899f6c
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-derived-state.md
@@ -0,0 +1,29 @@
+---
+title: Subscribe to Derived State
+impact: MEDIUM
+impactDescription: reduces re-render frequency
+tags: rerender, derived-state, media-query, optimization
+---
+
+## Subscribe to Derived State
+
+Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
+
+**Incorrect (re-renders on every pixel change):**
+
+```tsx
+function Sidebar() {
+ const width = useWindowWidth() // updates continuously
+ const isMobile = width < 768
+ return
+}
+```
+
+**Correct (re-renders only when boolean changes):**
+
+```tsx
+function Sidebar() {
+ const isMobile = useMediaQuery('(max-width: 767px)')
+ return
+}
+```
diff --git a/.agents/skills/review-react/rules/rerender-functional-setstate.md b/.agents/skills/review-react/rules/rerender-functional-setstate.md
new file mode 100644
index 000000000..b004ef45e
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-functional-setstate.md
@@ -0,0 +1,74 @@
+---
+title: Use Functional setState Updates
+impact: MEDIUM
+impactDescription: prevents stale closures and unnecessary callback recreations
+tags: react, hooks, useState, useCallback, callbacks, closures
+---
+
+## Use Functional setState Updates
+
+When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
+
+**Incorrect (requires state as dependency):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Callback must depend on items, recreated on every items change
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems([...items, ...newItems])
+ }, [items]) // ❌ items dependency causes recreations
+
+ // Risk of stale closure if dependency is forgotten
+ const removeItem = useCallback((id: string) => {
+ setItems(items.filter(item => item.id !== id))
+ }, []) // ❌ Missing items dependency - will use stale items!
+
+ return
+}
+```
+
+The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
+
+**Correct (stable callbacks, no stale closures):**
+
+```tsx
+function TodoList() {
+ const [items, setItems] = useState(initialItems)
+
+ // Stable callback, never recreated
+ const addItems = useCallback((newItems: Item[]) => {
+ setItems(curr => [...curr, ...newItems])
+ }, []) // ✅ No dependencies needed
+
+ // Always uses latest state, no stale closure risk
+ const removeItem = useCallback((id: string) => {
+ setItems(curr => curr.filter(item => item.id !== id))
+ }, []) // ✅ Safe and stable
+
+ return
+}
+```
+
+**Benefits:**
+
+1. **Stable callback references** - Callbacks don't need to be recreated when state changes
+2. **No stale closures** - Always operates on the latest state value
+3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
+4. **Prevents bugs** - Eliminates the most common source of React closure bugs
+
+**When to use functional updates:**
+
+- Any setState that depends on the current state value
+- Inside useCallback/useMemo when state is needed
+- Event handlers that reference state
+- Async operations that update state
+
+**When direct updates are fine:**
+
+- Setting state to a static value: `setCount(0)`
+- Setting state from props/arguments only: `setName(newName)`
+- State doesn't depend on previous value
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
diff --git a/.agents/skills/review-react/rules/rerender-lazy-state-init.md b/.agents/skills/review-react/rules/rerender-lazy-state-init.md
new file mode 100644
index 000000000..4ecb350fb
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-lazy-state-init.md
@@ -0,0 +1,58 @@
+---
+title: Use Lazy State Initialization
+impact: MEDIUM
+impactDescription: wasted computation on every render
+tags: react, hooks, useState, performance, initialization
+---
+
+## Use Lazy State Initialization
+
+Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
+
+**Incorrect (runs on every render):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs on EVERY render, even after initialization
+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ // When query changes, buildSearchIndex runs again unnecessarily
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs on every render
+ const [settings, setSettings] = useState(
+ JSON.parse(localStorage.getItem('settings') || '{}')
+ )
+
+ return
+}
+```
+
+**Correct (runs only once):**
+
+```tsx
+function FilteredList({ items }: { items: Item[] }) {
+ // buildSearchIndex() runs ONLY on initial render
+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
+ const [query, setQuery] = useState('')
+
+ return
+}
+
+function UserProfile() {
+ // JSON.parse runs only on initial render
+ const [settings, setSettings] = useState(() => {
+ const stored = localStorage.getItem('settings')
+ return stored ? JSON.parse(stored) : {}
+ })
+
+ return
+}
+```
+
+Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
+
+For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
diff --git a/.agents/skills/review-react/rules/rerender-memo-with-default-value.md b/.agents/skills/review-react/rules/rerender-memo-with-default-value.md
new file mode 100644
index 000000000..635704918
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-memo-with-default-value.md
@@ -0,0 +1,38 @@
+---
+
+title: Extract Default Non-primitive Parameter Value from Memoized Component to Constant
+impact: MEDIUM
+impactDescription: restores memoization by using a constant for default value
+tags: rerender, memo, optimization
+
+---
+
+## Extract Default Non-primitive Parameter Value from Memoized Component to Constant
+
+When memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.
+
+To address this issue, extract the default value into a constant.
+
+**Incorrect (`onClick` has different values on every rerender):**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
+ // ...
+})
+
+// Used without optional onClick
+
+```
+
+**Correct (stable default value):**
+
+```tsx
+const NOOP = () => {};
+
+const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
+ // ...
+})
+
+// Used without optional onClick
+
+```
diff --git a/.agents/skills/review-react/rules/rerender-memo.md b/.agents/skills/review-react/rules/rerender-memo.md
new file mode 100644
index 000000000..f8982ab61
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-memo.md
@@ -0,0 +1,44 @@
+---
+title: Extract to Memoized Components
+impact: MEDIUM
+impactDescription: enables early returns
+tags: rerender, memo, useMemo, optimization
+---
+
+## Extract to Memoized Components
+
+Extract expensive work into memoized components to enable early returns before computation.
+
+**Incorrect (computes avatar even when loading):**
+
+```tsx
+function Profile({ user, loading }: Props) {
+ const avatar = useMemo(() => {
+ const id = computeAvatarId(user)
+ return
+ }, [user])
+
+ if (loading) return
+ return
{avatar}
+}
+```
+
+**Correct (skips computation when loading):**
+
+```tsx
+const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
+ const id = useMemo(() => computeAvatarId(user), [user])
+ return
+})
+
+function Profile({ user, loading }: Props) {
+ if (loading) return
+ return (
+
+
+
+ )
+}
+```
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
diff --git a/.agents/skills/review-react/rules/rerender-move-effect-to-event.md b/.agents/skills/review-react/rules/rerender-move-effect-to-event.md
new file mode 100644
index 000000000..dd58a1af0
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-move-effect-to-event.md
@@ -0,0 +1,45 @@
+---
+title: Put Interaction Logic in Event Handlers
+impact: MEDIUM
+impactDescription: avoids effect re-runs and duplicate side effects
+tags: rerender, useEffect, events, side-effects, dependencies
+---
+
+## Put Interaction Logic in Event Handlers
+
+If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.
+
+**Incorrect (event modeled as state + effect):**
+
+```tsx
+function Form() {
+ const [submitted, setSubmitted] = useState(false)
+ const theme = useContext(ThemeContext)
+
+ useEffect(() => {
+ if (submitted) {
+ post('/api/register')
+ showToast('Registered', theme)
+ }
+ }, [submitted, theme])
+
+ return
+}
+```
+
+**Correct (do it in the handler):**
+
+```tsx
+function Form() {
+ const theme = useContext(ThemeContext)
+
+ function handleSubmit() {
+ post('/api/register')
+ showToast('Registered', theme)
+ }
+
+ return
+}
+```
+
+Reference: [Should this code move to an event handler?](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
diff --git a/.agents/skills/review-react/rules/rerender-no-inline-components.md b/.agents/skills/review-react/rules/rerender-no-inline-components.md
new file mode 100644
index 000000000..d97592ace
--- /dev/null
+++ b/.agents/skills/review-react/rules/rerender-no-inline-components.md
@@ -0,0 +1,82 @@
+---
+title: Don't Define Components Inside Components
+impact: HIGH
+impactDescription: prevents remount on every render
+tags: rerender, components, remount, performance
+---
+
+## Don't Define Components Inside Components
+
+**Impact: HIGH (prevents remount on every render)**
+
+Defining a component inside another component creates a new component type on every render. React sees a different component each time and fully remounts it, destroying all state and DOM.
+
+A common reason developers do this is to access parent variables without passing props. Always pass props instead.
+
+**Incorrect (remounts on every render):**
+
+```tsx
+function UserProfile({ user, theme }) {
+ // Defined inside to access `theme` - BAD
+ const Avatar = () => (
+
+ )
+
+ // Defined inside to access `user` - BAD
+ const Stats = () => (
+