diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md
new file mode 100644
index 0000000..14b2a60
--- /dev/null
+++ b/.claude/commands/commit.md
@@ -0,0 +1,36 @@
+# Commit
+
+변경사항을 분석하여 Conventional Commits 형식으로 커밋을 생성한다.
+
+## 절차
+
+1. `git status`와 `git diff`로 변경사항 확인
+2. 변경사항을 논리적 단위로 분리
+3. 각 단위별로 커밋 생성
+
+## 커밋 메시지 형식
+
+```
+{type}: {subject}
+
+{body}
+```
+
+### 타입
+
+- `feat` - 새로운 기능
+- `fix` - 버그 수정
+- `refactor` - 코드 리팩토링
+- `style` - 코드 포맷팅 (동작 변경 없음)
+- `docs` - 문서 변경
+- `test` - 테스트 추가/수정
+- `chore` - 빌드, 설정 변경
+- `perf` - 성능 개선
+
+### 규칙
+
+- 제목은 한국어, 50자 이내
+- 이모지 사용 금지
+- 한꺼번에 커밋하지 않고 작은 작업 단위로 분리
+- body는 선택사항이며, 필요시 "왜" 변경했는지 설명
+- 커밋 후 `git status`로 결과 확인
diff --git a/.claude/commands/create-pr.md b/.claude/commands/create-pr.md
new file mode 100644
index 0000000..1ef16bc
--- /dev/null
+++ b/.claude/commands/create-pr.md
@@ -0,0 +1,46 @@
+# Create PR
+
+현재 브랜치의 변경사항으로 Pull Request를 생성한다.
+
+## 절차
+
+1. `git log main..HEAD --oneline`으로 커밋 이력 확인
+2. `git diff main...HEAD`로 전체 변경사항 파악
+3. PR 제목과 본문 작성
+4. `gh pr create`로 PR 생성
+
+## 브랜치 컨벤션
+
+```
+{type}/{description}
+```
+
+- `feat/` - 새 기능
+- `fix/` - 버그 수정
+- `refactor/` - 리팩토링
+- `chore/` - 설정/빌드
+- `docs/` - 문서
+
+예: `feat/timeline-page`, `fix/search-filter`
+
+## PR 형식
+
+```markdown
+## Summary
+
+- 변경사항 1~3줄 요약
+
+## Changes
+
+- 주요 변경 항목 나열
+
+## Test Plan
+
+- [ ] 검증 항목
+```
+
+## 규칙
+
+- PR 제목은 70자 이내
+- base 브랜치는 `main`
+- push 전 `pnpm lint && pnpm type:check && pnpm format:check` 확인
diff --git a/.claude/commands/self-review.md b/.claude/commands/self-review.md
new file mode 100644
index 0000000..ffdf9a2
--- /dev/null
+++ b/.claude/commands/self-review.md
@@ -0,0 +1,54 @@
+# Self Review
+
+현재 변경사항을 다각도로 리뷰한다.
+
+## 절차
+
+1. `git diff`로 변경사항 확인
+2. 아래 관점별로 리뷰 수행
+3. 발견된 이슈를 심각도별로 분류하여 보고
+
+## 리뷰 관점
+
+### Frontend Architecture
+
+- Server/Client Component 경계가 적절한가
+- feature 간 순환 의존이 없는가
+- `app/`에 비즈니스 로직이 들어가지 않았는가
+- import 경로가 `@/*` 절대 경로를 사용하는가 (상대 경로 `../`, `./` 사용 금지, barrel file `index.ts` 내부 re-export 제외)
+- barrel file(`index.ts`)을 경유하여 import하는가 (내부 파일 직접 접근 금지)
+
+### UX/UI
+
+- 로딩/빈/에러 상태가 모두 처리되었는가
+- 모바일 반응형이 적용되었는가
+- 시맨틱 HTML을 사용했는가
+- 접근성 속성이 적절한가
+
+### Code Quality
+
+- TypeScript strict mode 에러가 없는가
+- 미사용 변수/import가 없는가
+- `import type`이 적절히 사용되었는가
+- 불필요한 주석이 없는가
+
+### Security
+
+- 사용자 입력을 적절히 검증하는가
+- XSS 취약점이 없는가
+- 민감 정보가 클라이언트에 노출되지 않는가
+
+## 보고 형식
+
+```
+## 리뷰 결과
+
+### 심각 (즉시 수정)
+- ...
+
+### 권장 (개선 사항)
+- ...
+
+### 참고
+- ...
+```
diff --git a/.claude/hooks/mark-lint-needed.sh b/.claude/hooks/mark-lint-needed.sh
new file mode 100755
index 0000000..d3254dc
--- /dev/null
+++ b/.claude/hooks/mark-lint-needed.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+# 파일 수정 시 플래그만 생성하고, 실제 lint는 나중에 한 번만 실행
+
+SESSION_ID=${CLAUDE_SESSION_ID:-default}
+PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
+FLAG_FILE="${PROJECT_DIR}/.claude/.lint-needed-${SESSION_ID}"
+
+# 플래그 파일 생성 (lint 필요함을 표시)
+touch "$FLAG_FILE"
+
+# 조용히 종료 (사용자 경험 방해 안 함)
+exit 0
diff --git a/.claude/hooks/post-stop-lint.sh b/.claude/hooks/post-stop-lint.sh
new file mode 100755
index 0000000..2f301a0
--- /dev/null
+++ b/.claude/hooks/post-stop-lint.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# 대화 종료 시 실행: 누적된 lint 작업을 한 번에 실행
+# 성능 최적화: 여러 파일 수정 시에도 lint는 단 1회만 실행
+
+SESSION_ID=${CLAUDE_SESSION_ID:-default}
+PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
+FLAG_FILE="${PROJECT_DIR}/.claude/.lint-needed-${SESSION_ID}"
+
+# 플래그 파일 확인
+if [ -f "$FLAG_FILE" ]; then
+ echo "🔍 [Deferred Linting] 파일 수정이 감지되었습니다. Lint & Format 실행 중..."
+
+ # 프로젝트 루트에서 실행
+ cd "$PROJECT_DIR" || exit 1
+
+ # Lint 자동 수정
+ pnpm lint:fix 2>/dev/null || echo "⚠️ lint:fix 스킵됨"
+
+ # Prettier 포맷팅
+ pnpm format 2>/dev/null || echo "⚠️ format 스킵됨"
+
+ # 플래그 파일 제거
+ rm "$FLAG_FILE"
+
+ echo "✅ Lint & Format 완료!"
+else
+ echo "✨ [Deferred Linting] 변경사항 없음. 스킵."
+fi
+
+exit 0
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..265dd85
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
+ "hooks": {
+ "PostToolUse": [
+ {
+ "matcher": "Edit|Write",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/mark-lint-needed.sh",
+ "timeout": 5
+ }
+ ]
+ }
+ ],
+ "Stop": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-stop-lint.sh",
+ "timeout": 120
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.claude/skills/code-review/SKILL.md b/.claude/skills/code-review/SKILL.md
new file mode 100644
index 0000000..c1e8f58
--- /dev/null
+++ b/.claude/skills/code-review/SKILL.md
@@ -0,0 +1,132 @@
+---
+name: code-review
+description: Provides structured code review guidelines for TypeScript projects. Use when reviewing pull requests, analyzing code quality, or suggesting improvements.
+license: MIT
+---
+
+# Code Review Guidelines
+
+## Overview
+
+This skill provides structured guidelines for reviewing TypeScript code. Apply these standards when reviewing pull requests, analyzing code quality, or suggesting improvements.
+
+**Keywords**: code review, pull request, PR review, TypeScript, code quality, best practices, refactoring
+
+## Review Checklist
+
+### 1. Code Correctness
+
+**Before approving, verify:**
+
+- [ ] Logic is correct and handles edge cases
+- [ ] Error handling is appropriate
+- [ ] No obvious bugs or race conditions
+- [ ] Tests cover the changes adequately
+
+### 2. Code Quality
+
+**Check for:**
+
+- [ ] Clear, descriptive variable and function names
+- [ ] Functions do one thing well (single responsibility)
+- [ ] No excessive nesting (max 3 levels)
+- [ ] DRY - no unnecessary duplication
+- [ ] YAGNI - no speculative features
+
+### 3. TypeScript Specific
+
+**Ensure:**
+
+- [ ] Proper type annotations (avoid `any`)
+- [ ] Interfaces/types defined for complex objects
+- [ ] Generics used appropriately
+- [ ] Null/undefined handled safely
+- [ ] `strict` mode compatible
+
+### 4. Performance
+
+**Look for:**
+
+- [ ] Unnecessary re-renders (React)
+- [ ] Missing memoization for expensive operations
+- [ ] Inefficient loops or data structures
+- [ ] Memory leaks (event listeners, subscriptions)
+
+## Review Comments
+
+### Comment Format
+
+Use this format for review comments:
+
+```
+[severity]: brief description
+
+Why: explanation of the issue
+Suggestion: how to fix it (with code if helpful)
+```
+
+**Severity levels:**
+
+- `[critical]` - Must fix before merge
+- `[suggestion]` - Recommended improvement
+- `[nit]` - Minor style preference
+- `[question]` - Need clarification
+
+### Example Comments
+
+**Good comment:**
+
+```
+[suggestion]: Consider extracting this validation logic
+
+Why: This 15-line validation block is hard to test in isolation
+Suggestion: Move to a `validateUserInput(data)` function
+```
+
+**Bad comment:**
+
+```
+This is wrong, fix it.
+```
+
+## Common Issues
+
+### Anti-patterns to Flag
+
+1. **God functions** - Functions over 50 lines doing multiple things
+2. **Prop drilling** - Passing props through 3+ component levels
+3. **Magic numbers** - Unexplained literal values
+4. **Catch-all error handling** - `catch(e) { console.log(e) }`
+5. **Implicit any** - Missing type annotations on function parameters
+
+### Security Concerns
+
+Always flag:
+
+- SQL/NoSQL injection vulnerabilities
+- XSS opportunities (unsanitized user input in DOM)
+- Hardcoded secrets or API keys
+- Insecure randomness for security contexts
+- Missing input validation on API endpoints
+
+## Approval Guidelines
+
+### Approve When
+
+- All critical issues resolved
+- Tests pass
+- Code meets team standards
+- No security concerns
+
+### Request Changes When
+
+- Critical bugs found
+- Security vulnerabilities present
+- Missing required tests
+- Significant performance issues
+
+### Leave Comments When
+
+- Minor improvements possible
+- Design alternatives worth discussing
+- Documentation could be clearer
diff --git a/.claude/skills/frontend-design/LICENSE.txt b/.claude/skills/frontend-design/LICENSE.txt
new file mode 100644
index 0000000..f433b1a
--- /dev/null
+++ b/.claude/skills/frontend-design/LICENSE.txt
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/.claude/skills/frontend-design/SKILL.md b/.claude/skills/frontend-design/SKILL.md
new file mode 100644
index 0000000..f709fde
--- /dev/null
+++ b/.claude/skills/frontend-design/SKILL.md
@@ -0,0 +1,45 @@
+---
+name: frontend-design
+description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
+license: Complete terms in LICENSE.txt
+---
+
+This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
+
+The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
+
+## Design Thinking
+
+Before coding, understand the context and commit to a BOLD aesthetic direction:
+
+- **Purpose**: What problem does this interface solve? Who uses it?
+- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
+- **Constraints**: Technical requirements (framework, performance, accessibility).
+- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
+
+**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
+
+Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
+
+- Production-grade and functional
+- Visually striking and memorable
+- Cohesive with a clear aesthetic point-of-view
+- Meticulously refined in every detail
+
+## Frontend Aesthetics Guidelines
+
+Focus on:
+
+- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
+- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
+- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
+- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
+- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
+
+NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
+
+Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
+
+**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
+
+Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
diff --git a/.claude/skills/nextjs-best-practices/SKILL.md b/.claude/skills/nextjs-best-practices/SKILL.md
new file mode 100644
index 0000000..f2128cf
--- /dev/null
+++ b/.claude/skills/nextjs-best-practices/SKILL.md
@@ -0,0 +1,203 @@
+---
+name: nextjs-best-practices
+description: Next.js App Router principles. Server Components, data fetching, routing patterns.
+allowed-tools: Read, Write, Edit, Glob, Grep
+---
+
+# Next.js Best Practices
+
+> Principles for Next.js App Router development.
+
+---
+
+## 1. Server vs Client Components
+
+### Decision Tree
+
+```
+Does it need...?
+│
+├── useState, useEffect, event handlers
+│ └── Client Component ('use client')
+│
+├── Direct data fetching, no interactivity
+│ └── Server Component (default)
+│
+└── Both?
+ └── Split: Server parent + Client child
+```
+
+### By Default
+
+| Type | Use |
+| ---------- | ------------------------------------- |
+| **Server** | Data fetching, layout, static content |
+| **Client** | Forms, buttons, interactive UI |
+
+---
+
+## 2. Data Fetching Patterns
+
+### Fetch Strategy
+
+| Pattern | Use |
+| -------------- | ------------------------ |
+| **Default** | Static (cached at build) |
+| **Revalidate** | ISR (time-based refresh) |
+| **No-store** | Dynamic (every request) |
+
+### Data Flow
+
+| Source | Pattern |
+| ---------- | ---------------------------- |
+| Database | Server Component fetch |
+| API | fetch with caching |
+| User input | Client state + server action |
+
+---
+
+## 3. Routing Principles
+
+### File Conventions
+
+| File | Purpose |
+| --------------- | -------------- |
+| `page.tsx` | Route UI |
+| `layout.tsx` | Shared layout |
+| `loading.tsx` | Loading state |
+| `error.tsx` | Error boundary |
+| `not-found.tsx` | 404 page |
+
+### Route Organization
+
+| Pattern | Use |
+| ----------------------- | ------------------------- |
+| Route groups `(name)` | Organize without URL |
+| Parallel routes `@slot` | Multiple same-level pages |
+| Intercepting `(.)` | Modal overlays |
+
+---
+
+## 4. API Routes
+
+### Route Handlers
+
+| Method | Use |
+| --------- | ----------- |
+| GET | Read data |
+| POST | Create data |
+| PUT/PATCH | Update data |
+| DELETE | Remove data |
+
+### Best Practices
+
+- Validate input with Zod
+- Return proper status codes
+- Handle errors gracefully
+- Use Edge runtime when possible
+
+---
+
+## 5. Performance Principles
+
+### Image Optimization
+
+- Use next/image component
+- Set priority for above-fold
+- Provide blur placeholder
+- Use responsive sizes
+
+### Bundle Optimization
+
+- Dynamic imports for heavy components
+- Route-based code splitting (automatic)
+- Analyze with bundle analyzer
+
+---
+
+## 6. Metadata
+
+### Static vs Dynamic
+
+| Type | Use |
+| ---------------- | ----------------- |
+| Static export | Fixed metadata |
+| generateMetadata | Dynamic per-route |
+
+### Essential Tags
+
+- title (50-60 chars)
+- description (150-160 chars)
+- Open Graph images
+- Canonical URL
+
+---
+
+## 7. Caching Strategy
+
+### Cache Layers
+
+| Layer | Control |
+| ---------- | --------------- |
+| Request | fetch options |
+| Data | revalidate/tags |
+| Full route | route config |
+
+### Revalidation
+
+| Method | Use |
+| ---------- | -------------------- |
+| Time-based | `revalidate: 60` |
+| On-demand | `revalidatePath/Tag` |
+| No cache | `no-store` |
+
+---
+
+## 8. Server Actions
+
+### Use Cases
+
+- Form submissions
+- Data mutations
+- Revalidation triggers
+
+### Best Practices
+
+- Mark with 'use server'
+- Validate all inputs
+- Return typed responses
+- Handle errors
+
+---
+
+## 9. Anti-Patterns
+
+| ❌ Don't | ✅ Do |
+| -------------------------- | ----------------- |
+| 'use client' everywhere | Server by default |
+| Fetch in client components | Fetch in server |
+| Skip loading states | Use loading.tsx |
+| Ignore error boundaries | Use error.tsx |
+| Large client bundles | Dynamic imports |
+
+---
+
+## 10. Project Structure
+
+```
+app/
+├── (marketing)/ # Route group
+│ └── page.tsx
+├── (dashboard)/
+│ ├── layout.tsx # Dashboard layout
+│ └── page.tsx
+├── api/
+│ └── [resource]/
+│ └── route.ts
+└── components/
+ └── ui/
+```
+
+---
+
+> **Remember:** Server Components are the default for a reason. Start there, add client only when needed.
diff --git a/.claude/skills/react-19/SKILL.md b/.claude/skills/react-19/SKILL.md
new file mode 100644
index 0000000..519ae9f
--- /dev/null
+++ b/.claude/skills/react-19/SKILL.md
@@ -0,0 +1,124 @@
+---
+name: react-19
+description: >
+ React 19 patterns with React Compiler.
+ Trigger: When writing React 19 components/hooks in .tsx (React Compiler rules, hook patterns, refs as props). If using Next.js App Router/Server Actions, also use nextjs-15.
+license: Apache-2.0
+metadata:
+ author: prowler-cloud
+ version: "1.0"
+ scope: [root, ui]
+ auto_invoke: "Writing React components"
+allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
+---
+
+## No Manual Memoization (REQUIRED)
+
+```typescript
+// ✅ React Compiler handles optimization automatically
+function Component({ items }) {
+ const filtered = items.filter(x => x.active);
+ const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
+
+ const handleClick = (id) => {
+ console.log(id);
+ };
+
+ return ;
+}
+
+// ❌ NEVER: Manual memoization
+const filtered = useMemo(() => items.filter(x => x.active), [items]);
+const handleClick = useCallback((id) => console.log(id), []);
+```
+
+## Imports (REQUIRED)
+
+```typescript
+// ✅ ALWAYS: Named imports
+import { useState, useEffect, useRef } from "react";
+
+// ❌ NEVER
+import React from "react";
+import * as React from "react";
+```
+
+## Server Components First
+
+```typescript
+// ✅ Server Component (default) - no directive
+export default async function Page() {
+ const data = await fetchData();
+ return ;
+}
+
+// ✅ Client Component - only when needed
+"use client";
+export function Interactive() {
+ const [state, setState] = useState(false);
+ return ;
+}
+```
+
+## When to use "use client"
+
+- useState, useEffect, useRef, useContext
+- Event handlers (onClick, onChange)
+- Browser APIs (window, localStorage)
+
+## use() Hook
+
+```typescript
+import { use } from "react";
+
+// Read promises (suspends until resolved)
+function Comments({ promise }) {
+ const comments = use(promise);
+ return comments.map(c =>
{c.text}
);
+}
+
+// Conditional context (not possible with useContext!)
+function Theme({ showTheme }) {
+ if (showTheme) {
+ const theme = use(ThemeContext);
+ return
Themed
;
+ }
+ return
Plain
;
+}
+```
+
+## Actions & useActionState
+
+```typescript
+"use server";
+async function submitForm(formData: FormData) {
+ await saveToDatabase(formData);
+ revalidatePath("/");
+}
+
+// With pending state
+import { useActionState } from "react";
+
+function Form() {
+ const [state, action, isPending] = useActionState(submitForm, null);
+ return (
+
+ );
+}
+```
+
+## ref as Prop (No forwardRef)
+
+```typescript
+// ✅ React 19: ref is just a prop
+function Input({ ref, ...props }) {
+ return ;
+}
+
+// ❌ Old way (unnecessary now)
+const Input = forwardRef((props, ref) => );
+```
diff --git a/.claude/skills/tailwind-4/SKILL.md b/.claude/skills/tailwind-4/SKILL.md
new file mode 100644
index 0000000..66c4f4e
--- /dev/null
+++ b/.claude/skills/tailwind-4/SKILL.md
@@ -0,0 +1,199 @@
+---
+name: tailwind-4
+description: >
+ Tailwind CSS 4 patterns and best practices.
+ Trigger: When styling with Tailwind (className, variants, cn()), especially when dynamic styling or CSS variables are involved (no var() in className).
+license: Apache-2.0
+metadata:
+ author: prowler-cloud
+ version: "1.0"
+ scope: [root, ui]
+ auto_invoke: "Working with Tailwind classes"
+allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
+---
+
+## Styling Decision Tree
+
+```
+Tailwind class exists? → className="..."
+Dynamic value? → style={{ width: `${x}%` }}
+Conditional styles? → cn("base", condition && "variant")
+Static only? → className="..." (no cn() needed)
+Library can't use class?→ style prop with var() constants
+```
+
+## Critical Rules
+
+### Never Use var() in className
+
+```typescript
+// ❌ NEVER: var() in className
+
+
+
+// ✅ ALWAYS: Use Tailwind semantic classes
+
+
+```
+
+### Never Use Hex Colors
+
+```typescript
+// ❌ NEVER: Hex colors in className
+
+
+
+// ✅ ALWAYS: Use Tailwind color classes
+
+
+```
+
+## The cn() Utility
+
+```typescript
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+```
+
+### When to Use cn()
+
+```typescript
+// ✅ Conditional classes
+
+
+// ✅ Merging with potential conflicts
+ // className might override
+
+// ✅ Multiple conditions
+
+```
+
+### When NOT to Use cn()
+
+```typescript
+// ❌ Static classes - unnecessary wrapper
+
+
+// ✅ Just use className directly
+
+```
+
+## Style Constants for Charts/Libraries
+
+When libraries don't accept className (like Recharts):
+
+```typescript
+// ✅ Constants with var() - ONLY for library props
+const CHART_COLORS = {
+ primary: "var(--color-primary)",
+ secondary: "var(--color-secondary)",
+ text: "var(--color-text)",
+ gridLine: "var(--color-border)",
+};
+
+// Usage with Recharts (can't use className)
+
+
+```
+
+## Dynamic Values
+
+```typescript
+// ✅ style prop for truly dynamic values
+
+
+
+// ✅ CSS custom properties for theming
+
+```
+
+## Common Patterns
+
+### Flexbox
+
+```typescript
+
+
+
+```
+
+### Grid
+
+```typescript
+
+
+```
+
+### Spacing
+
+```typescript
+// Padding
+ // All sides
+ // Horizontal, vertical
+ // Top, bottom
+
+// Margin
+
+ // Center horizontally
+
+```
+
+### Typography
+
+```typescript
+
+
+
+```
+
+### Borders & Shadows
+
+```typescript
+
+
+
+```
+
+### States
+
+```typescript
+
+
+
+```
+
+### Responsive
+
+```typescript
+
+
+
+```
+
+### Dark Mode
+
+```typescript
+
+
+```
+
+## Arbitrary Values (Escape Hatch)
+
+```typescript
+// ✅ OK for one-off values not in design system
+
+
+
+
+// ❌ Don't use for colors - use theme instead
+ // NO
+```
diff --git a/.claude/skills/typescript-expert/SKILL.md b/.claude/skills/typescript-expert/SKILL.md
new file mode 100644
index 0000000..d3ec343
--- /dev/null
+++ b/.claude/skills/typescript-expert/SKILL.md
@@ -0,0 +1,463 @@
+---
+name: typescript-expert
+description: >-
+ TypeScript and JavaScript expert with deep knowledge of type-level
+ programming, performance optimization, monorepo management, migration
+ strategies, and modern tooling. Use PROACTIVELY for any TypeScript/JavaScript
+ issues including complex type gymnastics, build performance, debugging, and
+ architectural decisions. If a specialized expert is a better fit, I will
+ recommend switching and stop.
+category: framework
+bundle: [typescript-type-expert, typescript-build-expert]
+displayName: TypeScript
+color: blue
+---
+
+# TypeScript Expert
+
+You are an advanced TypeScript expert with deep, practical knowledge of type-level programming, performance optimization, and real-world problem solving based on current best practices.
+
+## When invoked:
+
+0. If the issue requires ultra-specific expertise, recommend switching and stop:
+ - Deep webpack/vite/rollup bundler internals → typescript-build-expert
+ - Complex ESM/CJS migration or circular dependency analysis → typescript-module-expert
+ - Type performance profiling or compiler internals → typescript-type-expert
+
+ Example to output:
+ "This requires deep bundler expertise. Please invoke: 'Use the typescript-build-expert subagent.' Stopping here."
+
+1. Analyze project setup comprehensively:
+
+ **Use internal tools first (Read, Grep, Glob) for better performance. Shell commands are fallbacks.**
+
+ ```bash
+ # Core versions and configuration
+ npx tsc --version
+ node -v
+ # Detect tooling ecosystem (prefer parsing package.json)
+ node -e "const p=require('./package.json');console.log(Object.keys({...p.devDependencies,...p.dependencies}||{}).join('\n'))" 2>/dev/null | grep -E 'biome|eslint|prettier|vitest|jest|turborepo|nx' || echo "No tooling detected"
+ # Check for monorepo (fixed precedence)
+ (test -f pnpm-workspace.yaml || test -f lerna.json || test -f nx.json || test -f turbo.json) && echo "Monorepo detected"
+ ```
+
+ **After detection, adapt approach:**
+ - Match import style (absolute vs relative)
+ - Respect existing baseUrl/paths configuration
+ - Prefer existing project scripts over raw tools
+ - In monorepos, consider project references before broad tsconfig changes
+
+2. Identify the specific problem category and complexity level
+
+3. Apply the appropriate solution strategy from my expertise
+
+4. Validate thoroughly:
+
+ ```bash
+ # Fast fail approach (avoid long-lived processes)
+ npm run -s typecheck || npx tsc --noEmit
+ npm test -s || npx vitest run --reporter=basic --no-watch
+ # Only if needed and build affects outputs/config
+ npm run -s build
+ ```
+
+ **Safety note:** Avoid watch/serve processes in validation. Use one-shot diagnostics only.
+
+## Advanced Type System Expertise
+
+### Type-Level Programming Patterns
+
+**Branded Types for Domain Modeling**
+
+```typescript
+// Create nominal types to prevent primitive obsession
+type Brand = K & { __brand: T };
+type UserId = Brand;
+type OrderId = Brand;
+
+// Prevents accidental mixing of domain primitives
+function processOrder(orderId: OrderId, userId: UserId) {}
+```
+
+- Use for: Critical domain primitives, API boundaries, currency/units
+- Resource: https://egghead.io/blog/using-branded-types-in-typescript
+
+**Advanced Conditional Types**
+
+```typescript
+// Recursive type manipulation
+type DeepReadonly = T extends (...args: any[]) => any
+ ? T
+ : T extends object
+ ? { readonly [K in keyof T]: DeepReadonly }
+ : T;
+
+// Template literal type magic
+type PropEventSource = {
+ on(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void): void;
+};
+```
+
+- Use for: Library APIs, type-safe event systems, compile-time validation
+- Watch for: Type instantiation depth errors (limit recursion to 10 levels)
+
+**Type Inference Techniques**
+
+```typescript
+// Use 'satisfies' for constraint validation (TS 5.0+)
+const config = {
+ api: "https://api.example.com",
+ timeout: 5000
+} satisfies Record;
+// Preserves literal types while ensuring constraints
+
+// Const assertions for maximum inference
+const routes = ["/home", "/about", "/contact"] as const;
+type Route = (typeof routes)[number]; // '/home' | '/about' | '/contact'
+```
+
+### Performance Optimization Strategies
+
+**Type Checking Performance**
+
+```bash
+# Diagnose slow type checking
+npx tsc --extendedDiagnostics --incremental false | grep -E "Check time|Files:|Lines:|Nodes:"
+
+# Common fixes for "Type instantiation is excessively deep"
+# 1. Replace type intersections with interfaces
+# 2. Split large union types (>100 members)
+# 3. Avoid circular generic constraints
+# 4. Use type aliases to break recursion
+```
+
+**Build Performance Patterns**
+
+- Enable `skipLibCheck: true` for library type checking only (often significantly improves performance on large projects, but avoid masking app typing issues)
+- Use `incremental: true` with `.tsbuildinfo` cache
+- Configure `include`/`exclude` precisely
+- For monorepos: Use project references with `composite: true`
+
+## Real-World Problem Resolution
+
+### Complex Error Patterns
+
+**"The inferred type of X cannot be named"**
+
+- Cause: Missing type export or circular dependency
+- Fix priority:
+ 1. Export the required type explicitly
+ 2. Use `ReturnType` helper
+ 3. Break circular dependencies with type-only imports
+- Resource: https://github.com/microsoft/TypeScript/issues/47663
+
+**Missing type declarations**
+
+- Quick fix with ambient declarations:
+
+```typescript
+// types/ambient.d.ts
+declare module "some-untyped-package" {
+ const value: unknown;
+ export default value;
+ export = value; // if CJS interop is needed
+}
+```
+
+- For more details: [Declaration Files Guide](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html)
+
+**"Excessive stack depth comparing types"**
+
+- Cause: Circular or deeply recursive types
+- Fix priority:
+ 1. Limit recursion depth with conditional types
+ 2. Use `interface` extends instead of type intersection
+ 3. Simplify generic constraints
+
+```typescript
+// Bad: Infinite recursion
+type InfiniteArray = T | InfiniteArray[];
+
+// Good: Limited recursion
+type NestedArray = D extends 0 ? T : T | NestedArray[];
+```
+
+**Module Resolution Mysteries**
+
+- "Cannot find module" despite file existing:
+ 1. Check `moduleResolution` matches your bundler
+ 2. Verify `baseUrl` and `paths` alignment
+ 3. For monorepos: Ensure workspace protocol (workspace:\*)
+ 4. Try clearing cache: `rm -rf node_modules/.cache .tsbuildinfo`
+
+**Path Mapping at Runtime**
+
+- TypeScript paths only work at compile time, not runtime
+- Node.js runtime solutions:
+ - ts-node: Use `ts-node -r tsconfig-paths/register`
+ - Node ESM: Use loader alternatives or avoid TS paths at runtime
+ - Production: Pre-compile with resolved paths
+
+### Migration Expertise
+
+**JavaScript to TypeScript Migration**
+
+```bash
+# Incremental migration strategy
+# 1. Enable allowJs and checkJs (merge into existing tsconfig.json):
+# Add to existing tsconfig.json:
+# {
+# "compilerOptions": {
+# "allowJs": true,
+# "checkJs": true
+# }
+# }
+
+# 2. Rename files gradually (.js → .ts)
+# 3. Add types file by file using AI assistance
+# 4. Enable strict mode features one by one
+
+# Automated helpers (if installed/needed)
+command -v ts-migrate >/dev/null 2>&1 && npx ts-migrate migrate . --sources 'src/**/*.js'
+command -v typesync >/dev/null 2>&1 && npx typesync # Install missing @types packages
+```
+
+**Tool Migration Decisions**
+
+| From | To | When | Migration Effort |
+| ----------------- | --------------- | --------------------------------------------- | ----------------- |
+| ESLint + Prettier | Biome | Need much faster speed, okay with fewer rules | Low (1 day) |
+| TSC for linting | Type-check only | Have 100+ files, need faster feedback | Medium (2-3 days) |
+| Lerna | Nx/Turborepo | Need caching, parallel builds | High (1 week) |
+| CJS | ESM | Node 18+, modern tooling | High (varies) |
+
+### Monorepo Management
+
+**Nx vs Turborepo Decision Matrix**
+
+- Choose **Turborepo** if: Simple structure, need speed, <20 packages
+- Choose **Nx** if: Complex dependencies, need visualization, plugins required
+- Performance: Nx often performs better on large monorepos (>50 packages)
+
+**TypeScript Monorepo Configuration**
+
+```json
+// Root tsconfig.json
+{
+ "references": [{ "path": "./packages/core" }, { "path": "./packages/ui" }, { "path": "./apps/web" }],
+ "compilerOptions": {
+ "composite": true,
+ "declaration": true,
+ "declarationMap": true
+ }
+}
+```
+
+## Modern Tooling Expertise
+
+### Biome vs ESLint
+
+**Use Biome when:**
+
+- Speed is critical (often faster than traditional setups)
+- Want single tool for lint + format
+- TypeScript-first project
+- Okay with 64 TS rules vs 100+ in typescript-eslint
+
+**Stay with ESLint when:**
+
+- Need specific rules/plugins
+- Have complex custom rules
+- Working with Vue/Angular (limited Biome support)
+- Need type-aware linting (Biome doesn't have this yet)
+
+### Type Testing Strategies
+
+**Vitest Type Testing (Recommended)**
+
+```typescript
+// in avatar.test-d.ts
+import { expectTypeOf } from "vitest";
+import type { Avatar } from "./avatar";
+
+test("Avatar props are correctly typed", () => {
+ expectTypeOf().toHaveProperty("size");
+ expectTypeOf().toEqualTypeOf<"sm" | "md" | "lg">();
+});
+```
+
+**When to Test Types:**
+
+- Publishing libraries
+- Complex generic functions
+- Type-level utilities
+- API contracts
+
+## Debugging Mastery
+
+### CLI Debugging Tools
+
+```bash
+# Debug TypeScript files directly (if tools installed)
+command -v tsx >/dev/null 2>&1 && npx tsx --inspect src/file.ts
+command -v ts-node >/dev/null 2>&1 && npx ts-node --inspect-brk src/file.ts
+
+# Trace module resolution issues
+npx tsc --traceResolution > resolution.log 2>&1
+grep "Module resolution" resolution.log
+
+# Debug type checking performance (use --incremental false for clean trace)
+npx tsc --generateTrace trace --incremental false
+# Analyze trace (if installed)
+command -v @typescript/analyze-trace >/dev/null 2>&1 && npx @typescript/analyze-trace trace
+
+# Memory usage analysis
+node --max-old-space-size=8192 node_modules/typescript/lib/tsc.js
+```
+
+### Custom Error Classes
+
+```typescript
+// Proper error class with stack preservation
+class DomainError extends Error {
+ constructor(
+ message: string,
+ public code: string,
+ public statusCode: number
+ ) {
+ super(message);
+ this.name = "DomainError";
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
+```
+
+## Current Best Practices
+
+### Strict by Default
+
+```json
+{
+ "compilerOptions": {
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+ "exactOptionalPropertyTypes": true,
+ "noPropertyAccessFromIndexSignature": true
+ }
+}
+```
+
+### ESM-First Approach
+
+- Set `"type": "module"` in package.json
+- Use `.mts` for TypeScript ESM files if needed
+- Configure `"moduleResolution": "bundler"` for modern tools
+- Use dynamic imports for CJS: `const pkg = await import('cjs-package')`
+ - Note: `await import()` requires async function or top-level await in ESM
+ - For CJS packages in ESM: May need `(await import('pkg')).default` depending on the package's export structure and your compiler settings
+
+### AI-Assisted Development
+
+- GitHub Copilot excels at TypeScript generics
+- Use AI for boilerplate type definitions
+- Validate AI-generated types with type tests
+- Document complex types for AI context
+
+## Code Review Checklist
+
+When reviewing TypeScript/JavaScript code, focus on these domain-specific aspects:
+
+### Type Safety
+
+- [ ] No implicit `any` types (use `unknown` or proper types)
+- [ ] Strict null checks enabled and properly handled
+- [ ] Type assertions (`as`) justified and minimal
+- [ ] Generic constraints properly defined
+- [ ] Discriminated unions for error handling
+- [ ] Return types explicitly declared for public APIs
+
+### TypeScript Best Practices
+
+- [ ] Prefer `interface` over `type` for object shapes (better error messages)
+- [ ] Use const assertions for literal types
+- [ ] Leverage type guards and predicates
+- [ ] Avoid type gymnastics when simpler solution exists
+- [ ] Template literal types used appropriately
+- [ ] Branded types for domain primitives
+
+### Performance Considerations
+
+- [ ] Type complexity doesn't cause slow compilation
+- [ ] No excessive type instantiation depth
+- [ ] Avoid complex mapped types in hot paths
+- [ ] Use `skipLibCheck: true` in tsconfig
+- [ ] Project references configured for monorepos
+
+### Module System
+
+- [ ] Consistent import/export patterns
+- [ ] No circular dependencies
+- [ ] Proper use of barrel exports (avoid over-bundling)
+- [ ] ESM/CJS compatibility handled correctly
+- [ ] Dynamic imports for code splitting
+
+### Error Handling Patterns
+
+- [ ] Result types or discriminated unions for errors
+- [ ] Custom error classes with proper inheritance
+- [ ] Type-safe error boundaries
+- [ ] Exhaustive switch cases with `never` type
+
+### Code Organization
+
+- [ ] Types co-located with implementation
+- [ ] Shared types in dedicated modules
+- [ ] Avoid global type augmentation when possible
+- [ ] Proper use of declaration files (.d.ts)
+
+## Quick Decision Trees
+
+### "Which tool should I use?"
+
+```
+Type checking only? → tsc
+Type checking + linting speed critical? → Biome
+Type checking + comprehensive linting? → ESLint + typescript-eslint
+Type testing? → Vitest expectTypeOf
+Build tool? → Project size <10 packages? Turborepo. Else? Nx
+```
+
+### "How do I fix this performance issue?"
+
+```
+Slow type checking? → skipLibCheck, incremental, project references
+Slow builds? → Check bundler config, enable caching
+Slow tests? → Vitest with threads, avoid type checking in tests
+Slow language server? → Exclude node_modules, limit files in tsconfig
+```
+
+## Expert Resources
+
+### Performance
+
+- [TypeScript Wiki Performance](https://github.com/microsoft/TypeScript/wiki/Performance)
+- [Type instantiation tracking](https://github.com/microsoft/TypeScript/pull/48077)
+
+### Advanced Patterns
+
+- [Type Challenges](https://github.com/type-challenges/type-challenges)
+- [Type-Level TypeScript Course](https://type-level-typescript.com)
+
+### Tools
+
+- [Biome](https://biomejs.dev) - Fast linter/formatter
+- [TypeStat](https://github.com/JoshuaKGoldberg/TypeStat) - Auto-fix TypeScript types
+- [ts-migrate](https://github.com/airbnb/ts-migrate) - Migration toolkit
+
+### Testing
+
+- [Vitest Type Testing](https://vitest.dev/guide/testing-types)
+- [tsd](https://github.com/tsdjs/tsd) - Standalone type testing
+
+Always validate changes don't break existing functionality before considering the issue resolved.
diff --git a/.claude/skills/typescript-expert/references/tsconfig-strict.json b/.claude/skills/typescript-expert/references/tsconfig-strict.json
new file mode 100644
index 0000000..2e603c4
--- /dev/null
+++ b/.claude/skills/typescript-expert/references/tsconfig-strict.json
@@ -0,0 +1,67 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "display": "Strict TypeScript 5.x",
+ "compilerOptions": {
+ // =========================================================================
+ // STRICTNESS (Maximum Type Safety)
+ // =========================================================================
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "exactOptionalPropertyTypes": true,
+ "noFallthroughCasesInSwitch": true,
+ "forceConsistentCasingInFileNames": true,
+ // =========================================================================
+ // MODULE SYSTEM (Modern ESM)
+ // =========================================================================
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true,
+ // =========================================================================
+ // OUTPUT
+ // =========================================================================
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ // =========================================================================
+ // PERFORMANCE
+ // =========================================================================
+ "skipLibCheck": true,
+ "incremental": true,
+ // =========================================================================
+ // PATH ALIASES
+ // =========================================================================
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@/components/*": ["./src/components/*"],
+ "@/lib/*": ["./src/lib/*"],
+ "@/types/*": ["./src/types/*"],
+ "@/utils/*": ["./src/utils/*"]
+ },
+ // =========================================================================
+ // JSX (for React projects)
+ // =========================================================================
+ // "jsx": "react-jsx",
+ // =========================================================================
+ // EMIT
+ // =========================================================================
+ "noEmit": true // Let bundler handle emit
+ // "outDir": "./dist",
+ // "rootDir": "./src",
+ // =========================================================================
+ // DECORATORS (if needed)
+ // =========================================================================
+ // "experimentalDecorators": true,
+ // "emitDecoratorMetadata": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"],
+ "exclude": ["node_modules", "dist", "build", "coverage", "**/*.test.ts", "**/*.spec.ts"]
+}
diff --git a/.claude/skills/typescript-expert/references/typescript-cheatsheet.md b/.claude/skills/typescript-expert/references/typescript-cheatsheet.md
new file mode 100644
index 0000000..49c6ed5
--- /dev/null
+++ b/.claude/skills/typescript-expert/references/typescript-cheatsheet.md
@@ -0,0 +1,393 @@
+# TypeScript Cheatsheet
+
+## Type Basics
+
+```typescript
+// Primitives
+const name: string = "John";
+const age: number = 30;
+const isActive: boolean = true;
+const nothing: null = null;
+const notDefined: undefined = undefined;
+
+// Arrays
+const numbers: number[] = [1, 2, 3];
+const strings: Array = ["a", "b", "c"];
+
+// Tuple
+const tuple: [string, number] = ["hello", 42];
+
+// Object
+const user: { name: string; age: number } = { name: "John", age: 30 };
+
+// Union
+const value: string | number = "hello";
+
+// Literal
+const direction: "up" | "down" | "left" | "right" = "up";
+
+// Any vs Unknown
+const anyValue: any = "anything"; // ❌ Avoid
+const unknownValue: unknown = "safe"; // ✅ Prefer, requires narrowing
+```
+
+## Type Aliases & Interfaces
+
+```typescript
+// Type Alias
+type Point = {
+ x: number;
+ y: number;
+};
+
+// Interface (preferred for objects)
+interface User {
+ id: string;
+ name: string;
+ email?: string; // Optional
+ readonly createdAt: Date; // Readonly
+}
+
+// Extending
+interface Admin extends User {
+ permissions: string[];
+}
+
+// Intersection
+type AdminUser = User & { permissions: string[] };
+```
+
+## Generics
+
+```typescript
+// Generic function
+function identity(value: T): T {
+ return value;
+}
+
+// Generic with constraint
+function getLength(item: T): number {
+ return item.length;
+}
+
+// Generic interface
+interface ApiResponse {
+ data: T;
+ status: number;
+ message: string;
+}
+
+// Generic with default
+type Container = {
+ value: T;
+};
+
+// Multiple generics
+function merge(obj1: T, obj2: U): T & U {
+ return { ...obj1, ...obj2 };
+}
+```
+
+## Utility Types
+
+```typescript
+interface User {
+ id: string;
+ name: string;
+ email: string;
+ age: number;
+}
+
+// Partial - all optional
+type PartialUser = Partial;
+
+// Required - all required
+type RequiredUser = Required;
+
+// Readonly - all readonly
+type ReadonlyUser = Readonly;
+
+// Pick - select properties
+type UserName = Pick;
+
+// Omit - exclude properties
+type UserWithoutEmail = Omit;
+
+// Record - key-value map
+type UserMap = Record;
+
+// Extract - extract from union
+type StringOrNumber = string | number | boolean;
+type OnlyStrings = Extract;
+
+// Exclude - exclude from union
+type NotString = Exclude;
+
+// NonNullable - remove null/undefined
+type MaybeString = string | null | undefined;
+type DefinitelyString = NonNullable;
+
+// ReturnType - get function return type
+function getUser() {
+ return { name: "John" };
+}
+type UserReturn = ReturnType;
+
+// Parameters - get function parameters
+type GetUserParams = Parameters;
+
+// Awaited - unwrap Promise
+type ResolvedUser = Awaited>;
+```
+
+## Conditional Types
+
+```typescript
+// Basic conditional
+type IsString = T extends string ? true : false;
+
+// Infer keyword
+type UnwrapPromise = T extends Promise ? U : T;
+
+// Distributive conditional
+type ToArray = T extends any ? T[] : never;
+type Result = ToArray; // string[] | number[]
+
+// NonDistributive
+type ToArrayNonDist = [T] extends [any] ? T[] : never;
+```
+
+## Template Literal Types
+
+```typescript
+type Color = "red" | "green" | "blue";
+type Size = "small" | "medium" | "large";
+
+// Combine
+type ColorSize = `${Color}-${Size}`;
+// 'red-small' | 'red-medium' | 'red-large' | ...
+
+// Event handlers
+type EventName = "click" | "focus" | "blur";
+type EventHandler = `on${Capitalize}`;
+// 'onClick' | 'onFocus' | 'onBlur'
+```
+
+## Mapped Types
+
+```typescript
+// Basic mapped type
+type Optional = {
+ [K in keyof T]?: T[K];
+};
+
+// With key remapping
+type Getters = {
+ [K in keyof T as `get${Capitalize}`]: () => T[K];
+};
+
+// Filter keys
+type OnlyStrings = {
+ [K in keyof T as T[K] extends string ? K : never]: T[K];
+};
+```
+
+## Type Guards
+
+```typescript
+// typeof guard
+function process(value: string | number) {
+ if (typeof value === "string") {
+ return value.toUpperCase(); // string
+ }
+ return value.toFixed(2); // number
+}
+
+// instanceof guard
+class Dog {
+ bark() {}
+}
+class Cat {
+ meow() {}
+}
+
+function makeSound(animal: Dog | Cat) {
+ if (animal instanceof Dog) {
+ animal.bark();
+ } else {
+ animal.meow();
+ }
+}
+
+// in guard
+interface Bird {
+ fly(): void;
+}
+interface Fish {
+ swim(): void;
+}
+
+function move(animal: Bird | Fish) {
+ if ("fly" in animal) {
+ animal.fly();
+ } else {
+ animal.swim();
+ }
+}
+
+// Custom type guard
+function isString(value: unknown): value is string {
+ return typeof value === "string";
+}
+
+// Assertion function
+function assertIsString(value: unknown): asserts value is string {
+ if (typeof value !== "string") {
+ throw new Error("Not a string");
+ }
+}
+```
+
+## Discriminated Unions
+
+```typescript
+// With type discriminant
+type Success = { type: "success"; data: T };
+type Error = { type: "error"; message: string };
+type Loading = { type: "loading" };
+
+type State = Success | Error | Loading;
+
+function handle(state: State) {
+ switch (state.type) {
+ case "success":
+ return state.data; // T
+ case "error":
+ return state.message; // string
+ case "loading":
+ return null;
+ }
+}
+
+// Exhaustive check
+function assertNever(value: never): never {
+ throw new Error(`Unexpected value: ${value}`);
+}
+```
+
+## Branded Types
+
+```typescript
+// Create branded type
+type Brand = K & { __brand: T };
+
+type UserId = Brand;
+type OrderId = Brand;
+
+// Constructor functions
+function createUserId(id: string): UserId {
+ return id as UserId;
+}
+
+function createOrderId(id: string): OrderId {
+ return id as OrderId;
+}
+
+// Usage - prevents mixing
+function getOrder(orderId: OrderId, userId: UserId) {}
+
+const userId = createUserId("user-123");
+const orderId = createOrderId("order-456");
+
+getOrder(orderId, userId); // ✅ OK
+// getOrder(userId, orderId) // ❌ Error - types don't match
+```
+
+## Module Declarations
+
+```typescript
+// Declare module for untyped package
+declare module "untyped-package" {
+ export function doSomething(): void;
+ export const value: string;
+}
+
+// Augment existing module
+declare module "express" {
+ interface Request {
+ user?: { id: string };
+ }
+}
+
+// Declare global
+declare global {
+ interface Window {
+ myGlobal: string;
+ }
+}
+```
+
+## TSConfig Essentials
+
+```json
+{
+ "compilerOptions": {
+ // Strictness
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+
+ // Modules
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "esModuleInterop": true,
+
+ // Output
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM"],
+
+ // Performance
+ "skipLibCheck": true,
+ "incremental": true,
+
+ // Paths
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
+```
+
+## Best Practices
+
+```typescript
+// ✅ Prefer interface for objects
+interface User {
+ name: string;
+}
+
+// ✅ Use const assertions
+const routes = ["home", "about"] as const;
+
+// ✅ Use satisfies for validation
+const config = {
+ api: "https://api.example.com"
+} satisfies Record;
+
+// ✅ Use unknown over any
+function parse(input: unknown) {
+ if (typeof input === "string") {
+ return JSON.parse(input);
+ }
+}
+
+// ✅ Explicit return types for public APIs
+export function getUser(id: string): User | null {
+ // ...
+}
+
+// ❌ Avoid
+const data: any = fetchData();
+data.anything.goes.wrong; // No type safety
+```
diff --git a/.claude/skills/typescript-expert/references/utility-types.ts b/.claude/skills/typescript-expert/references/utility-types.ts
new file mode 100644
index 0000000..858da20
--- /dev/null
+++ b/.claude/skills/typescript-expert/references/utility-types.ts
@@ -0,0 +1,298 @@
+/**
+ * TypeScript Utility Types Library
+ *
+ * A collection of commonly used utility types for TypeScript projects.
+ * Copy and use as needed in your projects.
+ */
+
+// =============================================================================
+// BRANDED TYPES
+// =============================================================================
+
+/**
+ * Create nominal/branded types to prevent primitive obsession.
+ *
+ * @example
+ * type UserId = Brand
+ * type OrderId = Brand
+ */
+export type Brand = K & { readonly __brand: T };
+
+// Branded type constructors
+export type UserId = Brand;
+export type Email = Brand;
+export type UUID = Brand;
+export type Timestamp = Brand;
+export type PositiveNumber = Brand;
+
+// =============================================================================
+// RESULT TYPE (Error Handling)
+// =============================================================================
+
+/**
+ * Type-safe error handling without exceptions.
+ */
+export type Result = { success: true; data: T } | { success: false; error: E };
+
+export const ok = (data: T): Result => ({
+ success: true,
+ data
+});
+
+export const err = (error: E): Result => ({
+ success: false,
+ error
+});
+
+// =============================================================================
+// OPTION TYPE (Nullable Handling)
+// =============================================================================
+
+/**
+ * Explicit optional value handling.
+ */
+export type Option = Some | None;
+
+export type Some = { type: "some"; value: T };
+export type None = { type: "none" };
+
+export const some = (value: T): Some => ({ type: "some", value });
+export const none: None = { type: "none" };
+
+// =============================================================================
+// DEEP UTILITIES
+// =============================================================================
+
+/**
+ * Make all properties deeply readonly.
+ */
+export type DeepReadonly = T extends (...args: any[]) => any
+ ? T
+ : T extends object
+ ? { readonly [K in keyof T]: DeepReadonly }
+ : T;
+
+/**
+ * Make all properties deeply optional.
+ */
+export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T;
+
+/**
+ * Make all properties deeply required.
+ */
+export type DeepRequired = T extends object ? { [K in keyof T]-?: DeepRequired } : T;
+
+/**
+ * Make all properties deeply mutable (remove readonly).
+ */
+export type DeepMutable = T extends object ? { -readonly [K in keyof T]: DeepMutable } : T;
+
+// =============================================================================
+// OBJECT UTILITIES
+// =============================================================================
+
+/**
+ * Get keys of object where value matches type.
+ */
+export type KeysOfType = {
+ [K in keyof T]: T[K] extends V ? K : never;
+}[keyof T];
+
+/**
+ * Pick properties by value type.
+ */
+export type PickByType = Pick>;
+
+/**
+ * Omit properties by value type.
+ */
+export type OmitByType = Omit>;
+
+/**
+ * Make specific keys optional.
+ */
+export type PartialBy = Omit & Partial>;
+
+/**
+ * Make specific keys required.
+ */
+export type RequiredBy = Omit & Required>;
+
+/**
+ * Make specific keys readonly.
+ */
+export type ReadonlyBy = Omit & Readonly>;
+
+/**
+ * Merge two types (second overrides first).
+ */
+export type Merge = Omit & U;
+
+// =============================================================================
+// ARRAY UTILITIES
+// =============================================================================
+
+/**
+ * Get element type from array.
+ */
+export type ElementOf = T extends (infer E)[] ? E : never;
+
+/**
+ * Tuple of specific length.
+ */
+export type Tuple = N extends N ? (number extends N ? T[] : _TupleOf) : never;
+
+type _TupleOf = R["length"] extends N ? R : _TupleOf;
+
+/**
+ * Non-empty array.
+ */
+export type NonEmptyArray = [T, ...T[]];
+
+/**
+ * At least N elements.
+ */
+export type AtLeast = [...Tuple, ...T[]];
+
+// =============================================================================
+// FUNCTION UTILITIES
+// =============================================================================
+
+/**
+ * Get function arguments as tuple.
+ */
+export type Arguments = T extends (...args: infer A) => any ? A : never;
+
+/**
+ * Get first argument of function.
+ */
+export type FirstArgument = T extends (first: infer F, ...args: any[]) => any ? F : never;
+
+/**
+ * Async version of function.
+ */
+export type AsyncFunction any> = (
+ ...args: Parameters
+) => Promise>>;
+
+/**
+ * Promisify return type.
+ */
+export type Promisify = T extends (...args: infer A) => infer R ? (...args: A) => Promise> : never;
+
+// =============================================================================
+// STRING UTILITIES
+// =============================================================================
+
+/**
+ * Split string by delimiter.
+ */
+export type Split = S extends `${infer T}${D}${infer U}`
+ ? [T, ...Split]
+ : [S];
+
+/**
+ * Join tuple to string.
+ */
+export type Join = T extends []
+ ? ""
+ : T extends [infer F extends string]
+ ? F
+ : T extends [infer F extends string, ...infer R extends string[]]
+ ? `${F}${D}${Join}`
+ : never;
+
+/**
+ * Path to nested object.
+ */
+export type PathOf = K extends string
+ ? T[K] extends object
+ ? K | `${K}.${PathOf}`
+ : K
+ : never;
+
+// =============================================================================
+// UNION UTILITIES
+// =============================================================================
+
+/**
+ * Last element of union.
+ */
+export type UnionLast = UnionToIntersection T : never> extends () => infer R ? R : never;
+
+/**
+ * Union to intersection.
+ */
+export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
+
+/**
+ * Union to tuple.
+ */
+export type UnionToTuple> = [T] extends [never] ? [] : [...UnionToTuple>, L];
+
+// =============================================================================
+// VALIDATION UTILITIES
+// =============================================================================
+
+/**
+ * Assert type at compile time.
+ */
+export type AssertEqual = (() => V extends T ? 1 : 2) extends () => V extends U ? 1 : 2 ? true : false;
+
+/**
+ * Ensure type is not never.
+ */
+export type IsNever = [T] extends [never] ? true : false;
+
+/**
+ * Ensure type is any.
+ */
+export type IsAny = 0 extends 1 & T ? true : false;
+
+/**
+ * Ensure type is unknown.
+ */
+export type IsUnknown = IsAny extends true ? false : unknown extends T ? true : false;
+
+// =============================================================================
+// JSON UTILITIES
+// =============================================================================
+
+/**
+ * JSON-safe types.
+ */
+export type JsonPrimitive = string | number | boolean | null;
+export type JsonArray = JsonValue[];
+export type JsonObject = { [key: string]: JsonValue };
+export type JsonValue = JsonPrimitive | JsonArray | JsonObject;
+
+/**
+ * Make type JSON-serializable.
+ */
+export type Jsonify = T extends JsonPrimitive
+ ? T
+ : T extends undefined | ((...args: any[]) => any) | symbol
+ ? never
+ : T extends { toJSON(): infer R }
+ ? R
+ : T extends object
+ ? { [K in keyof T]: Jsonify }
+ : never;
+
+// =============================================================================
+// EXHAUSTIVE CHECK
+// =============================================================================
+
+/**
+ * Ensure all cases are handled in switch/if.
+ */
+export function assertNever(value: never, message?: string): never {
+ throw new Error(message ?? `Unexpected value: ${value}`);
+}
+
+/**
+ * Exhaustive check without throwing.
+ */
+export function exhaustiveCheck(_value: never): void {
+ // This function should never be called
+}
diff --git a/.claude/skills/typescript-expert/scripts/ts_diagnostic.py b/.claude/skills/typescript-expert/scripts/ts_diagnostic.py
new file mode 100644
index 0000000..3d42e90
--- /dev/null
+++ b/.claude/skills/typescript-expert/scripts/ts_diagnostic.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+"""
+TypeScript Project Diagnostic Script
+Analyzes TypeScript projects for configuration, performance, and common issues.
+"""
+
+import subprocess
+import sys
+import os
+import json
+from pathlib import Path
+
+def run_cmd(cmd: str) -> str:
+ """Run shell command and return output."""
+ try:
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
+ return result.stdout + result.stderr
+ except Exception as e:
+ return str(e)
+
+def check_versions():
+ """Check TypeScript and Node versions."""
+ print("\n📦 Versions:")
+ print("-" * 40)
+
+ ts_version = run_cmd("npx tsc --version 2>/dev/null").strip()
+ node_version = run_cmd("node -v 2>/dev/null").strip()
+
+ print(f" TypeScript: {ts_version or 'Not found'}")
+ print(f" Node.js: {node_version or 'Not found'}")
+
+def check_tsconfig():
+ """Analyze tsconfig.json settings."""
+ print("\n⚙️ TSConfig Analysis:")
+ print("-" * 40)
+
+ tsconfig_path = Path("tsconfig.json")
+ if not tsconfig_path.exists():
+ print("⚠️ tsconfig.json not found")
+ return
+
+ try:
+ with open(tsconfig_path) as f:
+ config = json.load(f)
+
+ compiler_opts = config.get("compilerOptions", {})
+
+ # Check strict mode
+ if compiler_opts.get("strict"):
+ print("✅ Strict mode enabled")
+ else:
+ print("⚠️ Strict mode NOT enabled")
+
+ # Check important flags
+ flags = {
+ "noUncheckedIndexedAccess": "Unchecked index access protection",
+ "noImplicitOverride": "Implicit override protection",
+ "skipLibCheck": "Skip lib check (performance)",
+ "incremental": "Incremental compilation"
+ }
+
+ for flag, desc in flags.items():
+ status = "✅" if compiler_opts.get(flag) else "⚪"
+ print(f" {status} {desc}: {compiler_opts.get(flag, 'not set')}")
+
+ # Check module settings
+ print(f"\n Module: {compiler_opts.get('module', 'not set')}")
+ print(f" Module Resolution: {compiler_opts.get('moduleResolution', 'not set')}")
+ print(f" Target: {compiler_opts.get('target', 'not set')}")
+
+ except json.JSONDecodeError:
+ print("❌ Invalid JSON in tsconfig.json")
+
+def check_tooling():
+ """Detect TypeScript tooling ecosystem."""
+ print("\n🛠️ Tooling Detection:")
+ print("-" * 40)
+
+ pkg_path = Path("package.json")
+ if not pkg_path.exists():
+ print("⚠️ package.json not found")
+ return
+
+ try:
+ with open(pkg_path) as f:
+ pkg = json.load(f)
+
+ all_deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
+
+ tools = {
+ "biome": "Biome (linter/formatter)",
+ "eslint": "ESLint",
+ "prettier": "Prettier",
+ "vitest": "Vitest (testing)",
+ "jest": "Jest (testing)",
+ "turborepo": "Turborepo (monorepo)",
+ "turbo": "Turbo (monorepo)",
+ "nx": "Nx (monorepo)",
+ "lerna": "Lerna (monorepo)"
+ }
+
+ for tool, desc in tools.items():
+ for dep in all_deps:
+ if tool in dep.lower():
+ print(f" ✅ {desc}")
+ break
+
+ except json.JSONDecodeError:
+ print("❌ Invalid JSON in package.json")
+
+def check_monorepo():
+ """Check for monorepo configuration."""
+ print("\n📦 Monorepo Check:")
+ print("-" * 40)
+
+ indicators = [
+ ("pnpm-workspace.yaml", "PNPM Workspace"),
+ ("lerna.json", "Lerna"),
+ ("nx.json", "Nx"),
+ ("turbo.json", "Turborepo")
+ ]
+
+ found = False
+ for file, name in indicators:
+ if Path(file).exists():
+ print(f" ✅ {name} detected")
+ found = True
+
+ if not found:
+ print(" ⚪ No monorepo configuration detected")
+
+def check_type_errors():
+ """Run quick type check."""
+ print("\n🔍 Type Check:")
+ print("-" * 40)
+
+ result = run_cmd("npx tsc --noEmit 2>&1 | head -20")
+ if "error TS" in result:
+ errors = result.count("error TS")
+ print(f" ❌ {errors}+ type errors found")
+ print(result[:500])
+ else:
+ print(" ✅ No type errors")
+
+def check_any_usage():
+ """Check for any type usage."""
+ print("\n⚠️ 'any' Type Usage:")
+ print("-" * 40)
+
+ result = run_cmd("grep -r ': any' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | wc -l")
+ count = result.strip()
+ if count and count != "0":
+ print(f" ⚠️ Found {count} occurrences of ': any'")
+ sample = run_cmd("grep -rn ': any' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | head -5")
+ if sample:
+ print(sample)
+ else:
+ print(" ✅ No explicit 'any' types found")
+
+def check_type_assertions():
+ """Check for type assertions."""
+ print("\n⚠️ Type Assertions (as):")
+ print("-" * 40)
+
+ result = run_cmd("grep -r ' as ' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | grep -v 'import' | wc -l")
+ count = result.strip()
+ if count and count != "0":
+ print(f" ⚠️ Found {count} type assertions")
+ else:
+ print(" ✅ No type assertions found")
+
+def check_performance():
+ """Check type checking performance."""
+ print("\n⏱️ Type Check Performance:")
+ print("-" * 40)
+
+ result = run_cmd("npx tsc --extendedDiagnostics --noEmit 2>&1 | grep -E 'Check time|Files:|Lines:|Nodes:'")
+ if result.strip():
+ for line in result.strip().split('\n'):
+ print(f" {line}")
+ else:
+ print(" ⚠️ Could not measure performance")
+
+def main():
+ print("=" * 50)
+ print("🔍 TypeScript Project Diagnostic Report")
+ print("=" * 50)
+
+ check_versions()
+ check_tsconfig()
+ check_tooling()
+ check_monorepo()
+ check_any_usage()
+ check_type_assertions()
+ check_type_errors()
+ check_performance()
+
+ print("\n" + "=" * 50)
+ print("✅ Diagnostic Complete")
+ print("=" * 50)
+
+if __name__ == "__main__":
+ main()
diff --git a/.claude/skills/vercel-react-best-practices/AGENTS.md b/.claude/skills/vercel-react-best-practices/AGENTS.md
new file mode 100644
index 0000000..5dcd3ed
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/AGENTS.md
@@ -0,0 +1,2365 @@
+# React Best Practices
+
+**Version 1.0.0**\
+Vercel Engineering\
+January 2026
+
+> **Note:**\
+> This document is mainly for agents and LLMs to follow when maintaining,\
+> generating, or refactoring React and Next.js codebases at Vercel. Humans\
+> may also find it useful, but guidance here is optimized for automation\
+> and consistency by AI-assisted workflows.
+
+---
+
+## Abstract
+
+Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.
+
+---
+
+## Table of Contents
+
+1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**
+ - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)
+ - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)
+ - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)
+ - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)
+ - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)
+2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**
+ - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)
+ - 2.2 [Conditional Module Loading](#22-conditional-module-loading)
+ - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)
+ - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)
+ - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)
+3. [Server-Side Performance](#3-server-side-performance) — **HIGH**
+ - 3.1 [Cross-Request LRU Caching](#31-cross-request-lru-caching)
+ - 3.2 [Minimize Serialization at RSC Boundaries](#32-minimize-serialization-at-rsc-boundaries)
+ - 3.3 [Parallel Data Fetching with Component Composition](#33-parallel-data-fetching-with-component-composition)
+ - 3.4 [Per-Request Deduplication with React.cache()](#34-per-request-deduplication-with-reactcache)
+ - 3.5 [Use after() for Non-Blocking Operations](#35-use-after-for-non-blocking-operations)
+4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**
+ - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)
+ - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)
+ - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)
+ - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)
+5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**
+ - 5.1 [Defer State Reads to Usage Point](#51-defer-state-reads-to-usage-point)
+ - 5.2 [Extract to Memoized Components](#52-extract-to-memoized-components)
+ - 5.3 [Narrow Effect Dependencies](#53-narrow-effect-dependencies)
+ - 5.4 [Subscribe to Derived State](#54-subscribe-to-derived-state)
+ - 5.5 [Use Functional setState Updates](#55-use-functional-setstate-updates)
+ - 5.6 [Use Lazy State Initialization](#56-use-lazy-state-initialization)
+ - 5.7 [Use Transitions for Non-Urgent Updates](#57-use-transitions-for-non-urgent-updates)
+6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**
+ - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)
+ - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)
+ - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)
+ - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)
+ - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)
+ - 6.6 [Use Activity Component for Show/Hide](#66-use-activity-component-for-showhide)
+ - 6.7 [Use Explicit Conditional Rendering](#67-use-explicit-conditional-rendering)
+7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**
+ - 7.1 [Batch DOM CSS Changes](#71-batch-dom-css-changes)
+ - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)
+ - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)
+ - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)
+ - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)
+ - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)
+ - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)
+ - 7.8 [Early Return from Functions](#78-early-return-from-functions)
+ - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)
+ - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)
+ - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)
+ - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)
+8. [Advanced Patterns](#8-advanced-patterns) — **LOW**
+ - 8.1 [Store Event Handlers in Refs](#81-store-event-handlers-in-refs)
+ - 8.2 [useLatest for Stable Callback Refs](#82-uselatest-for-stable-callback-refs)
+
+---
+
+## 1. Eliminating Waterfalls
+
+**Impact: CRITICAL**
+
+Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.
+
+### 1.1 Defer Await Until Needed
+
+**Impact: HIGH (avoids blocking unused code paths)**
+
+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.
+
+### 1.2 Dependency-Based Parallelization
+
+**Impact: CRITICAL (2-10× improvement)**
+
+For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
+
+**Incorrect: profile waits for config unnecessarily**
+
+```typescript
+const [user, config] = await Promise.all([fetchUser(), fetchConfig()]);
+const profile = await fetchProfile(user.id);
+```
+
+**Correct: config and profile run in parallel**
+
+```typescript
+import { all } from "better-all";
+
+const { user, config, profile } = await all({
+ async user() {
+ return fetchUser();
+ },
+ async config() {
+ return fetchConfig();
+ },
+ async profile() {
+ return fetchProfile((await this.$.user).id);
+ }
+});
+```
+
+Reference:
+
+### 1.3 Prevent Waterfall Chains in API Routes
+
+**Impact: CRITICAL (2-10× improvement)**
+
+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();
+ const session = await sessionPromise;
+ const [config, data] = await Promise.all([configPromise, fetchData(session.user.id)]);
+ return Response.json({ data, config });
+}
+```
+
+For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
+
+### 1.4 Promise.all() for Independent Operations
+
+**Impact: CRITICAL (2-10× improvement)**
+
+When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
+
+**Incorrect: sequential execution, 3 round trips**
+
+```typescript
+const user = await fetchUser();
+const posts = await fetchPosts();
+const comments = await fetchComments();
+```
+
+**Correct: parallel execution, 1 round trip**
+
+```typescript
+const [user, posts, comments] = await Promise.all([fetchUser(), fetchPosts(), fetchComments()]);
+```
+
+### 1.5 Strategic Suspense Boundaries
+
+**Impact: HIGH (faster initial paint)**
+
+Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
+
+**Incorrect: wrapper blocked by data fetching**
+
+```tsx
+async function Page() {
+ const data = await fetchData(); // Blocks entire page
+
+ return (
+
+
Sidebar
+
Header
+
+
+
+
Footer
+
+ );
+}
+```
+
+The entire layout waits for data even though only the middle section needs it.
+
+**Correct: wrapper shows immediately, data streams in**
+
+```tsx
+function Page() {
+ return (
+
+
Sidebar
+
Header
+
+ }>
+
+
+
+
Footer
+
+ );
+}
+
+async function DataDisplay() {
+ const data = await fetchData(); // Only blocks this component
+ return
{data.content}
;
+}
+```
+
+Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
+
+**Alternative: share promise across components**
+
+```tsx
+function Page() {
+ // Start fetch immediately, but don't await
+ const dataPromise = fetchData();
+
+ return (
+
;
+}
+
+function DataSummary({ dataPromise }: { dataPromise: Promise }) {
+ const data = use(dataPromise); // Reuses the same promise
+ return
{data.summary}
;
+}
+```
+
+Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
+
+**When NOT to use this pattern:**
+
+- Critical data needed for layout decisions (affects positioning)
+
+- SEO-critical content above the fold
+
+- Small, fast queries where suspense overhead isn't worth it
+
+- When you want to avoid layout shift (loading → content jump)
+
+**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
+
+---
+
+## 2. Bundle Size Optimization
+
+**Impact: CRITICAL**
+
+Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.
+
+### 2.1 Avoid Barrel File Imports
+
+**Impact: CRITICAL (200-800ms import cost, slow builds)**
+
+Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
+
+Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
+
+**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
+
+**Incorrect: imports entire library**
+
+```tsx
+import { Check, X, Menu } from "lucide-react";
+// Loads 1,583 modules, takes ~2.8s extra in dev
+// Runtime cost: 200-800ms on every cold start
+
+import { Button, TextField } from "@mui/material";
+// Loads 2,225 modules, takes ~4.2s extra in dev
+```
+
+**Correct: imports only what you need**
+
+```tsx
+import Check from "lucide-react/dist/esm/icons/check";
+import X from "lucide-react/dist/esm/icons/x";
+import Menu from "lucide-react/dist/esm/icons/menu";
+// Loads only 3 modules (~2KB vs ~1MB)
+
+import Button from "@mui/material/Button";
+import TextField from "@mui/material/TextField";
+// Loads only what you use
+```
+
+**Alternative: Next.js 13.5+**
+
+```js
+// next.config.js - use optimizePackageImports
+module.exports = {
+ experimental: {
+ optimizePackageImports: ["lucide-react", "@mui/material"]
+ }
+};
+
+// Then you can keep the ergonomic barrel imports:
+import { Check, X, Menu } from "lucide-react";
+// Automatically transformed to direct imports at build time
+```
+
+Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
+
+Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
+
+Reference:
+
+### 2.2 Conditional Module Loading
+
+**Impact: HIGH (loads large data only when needed)**
+
+Load large data or modules only when a feature is activated.
+
+**Example: lazy-load animation frames**
+
+```tsx
+function AnimationPlayer({
+ enabled,
+ setEnabled
+}: {
+ enabled: boolean;
+ setEnabled: React.Dispatch>;
+}) {
+ const [frames, setFrames] = useState(null);
+
+ useEffect(() => {
+ if (enabled && !frames && typeof window !== "undefined") {
+ import("./animation-frames.js").then((mod) => setFrames(mod.frames)).catch(() => setEnabled(false));
+ }
+ }, [enabled, frames, setEnabled]);
+
+ if (!frames) return ;
+ return ;
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
+
+### 2.3 Defer Non-Critical Third-Party Libraries
+
+**Impact: MEDIUM (loads after hydration)**
+
+Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
+
+**Incorrect: blocks initial bundle**
+
+```tsx
+import { Analytics } from "@vercel/analytics/react";
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+**Correct: loads after hydration**
+
+```tsx
+import dynamic from "next/dynamic";
+
+const Analytics = dynamic(() => import("@vercel/analytics/react").then((m) => m.Analytics), {
+ ssr: false
+});
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+### 2.4 Dynamic Imports for Heavy Components
+
+**Impact: CRITICAL (directly affects TTI and LCP)**
+
+Use `next/dynamic` to lazy-load large components not needed on initial render.
+
+**Incorrect: Monaco bundles with main chunk \~300KB**
+
+```tsx
+import { MonacoEditor } from "./monaco-editor";
+
+function CodePanel({ code }: { code: string }) {
+ return ;
+}
+```
+
+**Correct: Monaco loads on demand**
+
+```tsx
+import dynamic from "next/dynamic";
+
+const MonacoEditor = dynamic(() => import("./monaco-editor").then((m) => m.MonacoEditor), {
+ ssr: false
+});
+
+function CodePanel({ code }: { code: string }) {
+ return ;
+}
+```
+
+### 2.5 Preload Based on User Intent
+
+**Impact: MEDIUM (reduces perceived latency)**
+
+Preload heavy bundles before they're needed to reduce perceived latency.
+
+**Example: preload on hover/focus**
+
+```tsx
+function EditorButton({ onClick }: { onClick: () => void }) {
+ const preload = () => {
+ if (typeof window !== "undefined") {
+ void import("./monaco-editor");
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+**Example: preload when feature flag is enabled**
+
+```tsx
+function FlagsProvider({ children, flags }: Props) {
+ useEffect(() => {
+ if (flags.editorEnabled && typeof window !== "undefined") {
+ void import("./monaco-editor").then((mod) => mod.init());
+ }
+ }, [flags.editorEnabled]);
+
+ return {children};
+}
+```
+
+The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
+
+---
+
+## 3. Server-Side Performance
+
+**Impact: HIGH**
+
+Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.
+
+### 3.1 Cross-Request LRU Caching
+
+**Impact: HIGH (caches across requests)**
+
+`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.
+
+**Implementation:**
+
+```typescript
+import { LRUCache } from "lru-cache";
+
+const cache = new LRUCache({
+ max: 1000,
+ ttl: 5 * 60 * 1000 // 5 minutes
+});
+
+export async function getUser(id: string) {
+ const cached = cache.get(id);
+ if (cached) return cached;
+
+ const user = await db.user.findUnique({ where: { id } });
+ cache.set(id, user);
+ return user;
+}
+
+// Request 1: DB query, result cached
+// Request 2: cache hit, no DB query
+```
+
+Use when sequential user actions hit multiple endpoints needing the same data within seconds.
+
+**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.
+
+**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.
+
+Reference:
+
+### 3.2 Minimize Serialization at RSC Boundaries
+
+**Impact: HIGH (reduces data transfer size)**
+
+The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
+
+**Incorrect: serializes all 50 fields**
+
+```tsx
+async function Page() {
+ const user = await fetchUser(); // 50 fields
+ return ;
+}
+
+("use client");
+function Profile({ user }: { user: User }) {
+ return
{user.name}
; // uses 1 field
+}
+```
+
+**Correct: serializes only 1 field**
+
+```tsx
+async function Page() {
+ const user = await fetchUser();
+ return ;
+}
+
+("use client");
+function Profile({ name }: { name: string }) {
+ return
{name}
;
+}
+```
+
+### 3.3 Parallel Data Fetching with Component Composition
+
+**Impact: CRITICAL (eliminates server-side waterfalls)**
+
+React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.
+
+**Incorrect: Sidebar waits for Page's fetch to complete**
+
+```tsx
+export default async function Page() {
+ const header = await fetchHeader();
+ return (
+
+
{header}
+
+
+ );
+}
+
+async function Sidebar() {
+ const items = await fetchSidebarItems();
+ return ;
+}
+```
+
+**Correct: both fetch simultaneously**
+
+```tsx
+async function Header() {
+ const data = await fetchHeader();
+ return
;
+}
+```
+
+This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
+
+### 6.4 Optimize SVG Precision
+
+**Impact: LOW (reduces file size)**
+
+Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
+
+**Incorrect: excessive precision**
+
+```svg
+
+```
+
+**Correct: 1 decimal place**
+
+```svg
+
+```
+
+**Automate with SVGO:**
+
+```bash
+npx svgo --precision=1 --multipass icon.svg
+```
+
+### 6.5 Prevent Hydration Mismatch Without Flickering
+
+**Impact: MEDIUM (avoids visual flicker and hydration errors)**
+
+When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
+
+**Incorrect: breaks SSR**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ // localStorage is not available on server - throws error
+ const theme = localStorage.getItem("theme") || "light";
+
+ return
;
+}
+```
+
+Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
+
+**Correct: no flicker, no hydration mismatch**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ return (
+ <>
+
{children}
+
+ >
+ );
+}
+```
+
+The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
+
+This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
+
+### 6.6 Use Activity Component for Show/Hide
+
+**Impact: MEDIUM (preserves state/DOM)**
+
+Use React's `` to preserve state/DOM for expensive components that frequently toggle visibility.
+
+**Usage:**
+
+```tsx
+import { Activity } from "react";
+
+function Dropdown({ isOpen }: Props) {
+ return (
+
+
+
+ );
+}
+```
+
+Avoids expensive re-renders and state loss.
+
+### 6.7 Use Explicit Conditional Rendering
+
+**Impact: LOW (prevents rendering 0 or NaN)**
+
+Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
+
+**Incorrect: renders "0" when count is 0**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count && {count}}
;
+}
+
+// When count = 0, renders:
0
+// When count = 5, renders:
5
+```
+
+**Correct: renders nothing when count is 0**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count > 0 ? {count} : null}
;
+}
+
+// When count = 0, renders:
+// When count = 5, renders:
5
+```
+
+---
+
+## 7. JavaScript Performance
+
+**Impact: LOW-MEDIUM**
+
+Micro-optimizations for hot paths can add up to meaningful improvements.
+
+### 7.1 Batch DOM CSS Changes
+
+**Impact: MEDIUM (reduces reflows/repaints)**
+
+Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows.
+
+**Incorrect: multiple reflows**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ // Each line triggers a reflow
+ element.style.width = "100px";
+ element.style.height = "200px";
+ element.style.backgroundColor = "blue";
+ element.style.border = "1px solid black";
+}
+```
+
+**Correct: add class - single reflow**
+
+```typescript
+// CSS file
+.highlighted-box {
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+}
+
+// JavaScript
+function updateElementStyles(element: HTMLElement) {
+ element.classList.add('highlighted-box')
+}
+```
+
+**Correct: change cssText - single reflow**
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+ element.style.cssText = `
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ border: 1px solid black;
+ `;
+}
+```
+
+**React example:**
+
+```tsx
+// Incorrect: changing styles one by one
+function Box({ isHighlighted }: { isHighlighted: boolean }) {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (ref.current && isHighlighted) {
+ ref.current.style.width = "100px";
+ ref.current.style.height = "200px";
+ ref.current.style.backgroundColor = "blue";
+ }
+ }, [isHighlighted]);
+
+ return
+}
+```
+
+**Why this matters in React:**
+
+1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
+
+2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
+
+**Browser support: fallback for older browsers**
+
+```typescript
+// Fallback for older browsers
+const sorted = [...items].sort((a, b) => a.value - b.value);
+```
+
+`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
+
+**Other immutable array methods:**
+
+- `.toSorted()` - immutable sort
+
+- `.toReversed()` - immutable reverse
+
+- `.toSpliced()` - immutable splice
+
+- `.with()` - immutable element replacement
+
+---
+
+## 8. Advanced Patterns
+
+**Impact: LOW**
+
+Advanced patterns for specific cases that require careful implementation.
+
+### 8.1 Store Event Handlers in Refs
+
+**Impact: LOW (stable subscriptions)**
+
+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: () => void) {
+ useEffect(() => {
+ window.addEventListener(event, handler);
+ return () => window.removeEventListener(event, handler);
+ }, [event, handler]);
+}
+```
+
+**Correct: stable subscription**
+
+```tsx
+import { useEffectEvent } from "react";
+
+function useWindowEvent(event: string, handler: () => void) {
+ const onEvent = useEffectEvent(handler);
+
+ useEffect(() => {
+ window.addEventListener(event, onEvent);
+ return () => window.removeEventListener(event, onEvent);
+ }, [event]);
+}
+```
+
+**Alternative: use `useEffectEvent` if you're on latest React:**
+
+`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
+
+### 8.2 useLatest for Stable Callback Refs
+
+**Impact: LOW (prevents effect re-runs)**
+
+Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
+
+**Implementation:**
+
+```typescript
+function useLatest(value: T) {
+ const ref = useRef(value);
+ useEffect(() => {
+ ref.current = value;
+ }, [value]);
+ return ref;
+}
+```
+
+**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: stable effect, fresh callback**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState("");
+ const onSearchRef = useLatest(onSearch);
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearchRef.current(query), 300);
+ return () => clearTimeout(timeout);
+ }, [query]);
+}
+```
+
+---
+
+## References
+
+1.
+2.
+3.
+4.
+5.
+6.
+7.
diff --git a/.claude/skills/vercel-react-best-practices/SKILL.md b/.claude/skills/vercel-react-best-practices/SKILL.md
new file mode 100644
index 0000000..ba1ca10
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/SKILL.md
@@ -0,0 +1,127 @@
+---
+name: vercel-react-best-practices
+description: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
+license: MIT
+metadata:
+ author: vercel
+ version: "1.0.0"
+---
+
+# Vercel React Best Practices
+
+Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
+
+## When to Apply
+
+Reference these guidelines when:
+
+- Writing new React components or Next.js pages
+- Implementing data fetching (client or server-side)
+- Reviewing code for performance issues
+- Refactoring existing React/Next.js code
+- Optimizing bundle size or load times
+
+## Rule Categories by Priority
+
+| Priority | Category | Impact | Prefix |
+| -------- | ------------------------- | ----------- | ------------ |
+| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
+| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
+| 3 | Server-Side Performance | HIGH | `server-` |
+| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
+| 5 | Re-render Optimization | MEDIUM | `rerender-` |
+| 6 | Rendering Performance | MEDIUM | `rendering-` |
+| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
+| 8 | Advanced Patterns | LOW | `advanced-` |
+
+## Quick Reference
+
+### 1. Eliminating Waterfalls (CRITICAL)
+
+- `async-defer-await` - Move await into branches where actually used
+- `async-parallel` - Use Promise.all() for independent operations
+- `async-dependencies` - Use better-all for partial dependencies
+- `async-api-routes` - Start promises early, await late in API routes
+- `async-suspense-boundaries` - Use Suspense to stream content
+
+### 2. Bundle Size Optimization (CRITICAL)
+
+- `bundle-barrel-imports` - Import directly, avoid barrel files
+- `bundle-dynamic-imports` - Use next/dynamic for heavy components
+- `bundle-defer-third-party` - Load analytics/logging after hydration
+- `bundle-conditional` - Load modules only when feature is activated
+- `bundle-preload` - Preload on hover/focus for perceived speed
+
+### 3. Server-Side Performance (HIGH)
+
+- `server-cache-react` - Use React.cache() for per-request deduplication
+- `server-cache-lru` - Use LRU cache for cross-request caching
+- `server-serialization` - Minimize data passed to client components
+- `server-parallel-fetching` - Restructure components to parallelize fetches
+- `server-after-nonblocking` - Use after() for non-blocking operations
+
+### 4. Client-Side Data Fetching (MEDIUM-HIGH)
+
+- `client-swr-dedup` - Use SWR for automatic request deduplication
+- `client-event-listeners` - Deduplicate global event listeners
+
+### 5. Re-render Optimization (MEDIUM)
+
+- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
+- `rerender-memo` - Extract expensive work into memoized components
+- `rerender-dependencies` - Use primitive dependencies in effects
+- `rerender-derived-state` - Subscribe to derived booleans, not raw values
+- `rerender-functional-setstate` - Use functional setState for stable callbacks
+- `rerender-lazy-state-init` - Pass function to useState for expensive values
+- `rerender-transitions` - Use startTransition for non-urgent updates
+
+### 6. Rendering Performance (MEDIUM)
+
+- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
+- `rendering-content-visibility` - Use content-visibility for long lists
+- `rendering-hoist-jsx` - Extract static JSX outside components
+- `rendering-svg-precision` - Reduce SVG coordinate precision
+- `rendering-hydration-no-flicker` - Use inline script for client-only data
+- `rendering-activity` - Use Activity component for show/hide
+- `rendering-conditional-render` - Use ternary, not && for conditionals
+
+### 7. JavaScript Performance (LOW-MEDIUM)
+
+- `js-batch-dom-css` - Group CSS changes via classes or cssText
+- `js-index-maps` - Build Map for repeated lookups
+- `js-cache-property-access` - Cache object properties in loops
+- `js-cache-function-results` - Cache function results in module-level Map
+- `js-cache-storage` - Cache localStorage/sessionStorage reads
+- `js-combine-iterations` - Combine multiple filter/map into one loop
+- `js-length-check-first` - Check array length before expensive comparison
+- `js-early-exit` - Return early from functions
+- `js-hoist-regexp` - Hoist RegExp creation outside loops
+- `js-min-max-loop` - Use loop for min/max instead of sort
+- `js-set-map-lookups` - Use Set/Map for O(1) lookups
+- `js-tosorted-immutable` - Use toSorted() for immutability
+
+### 8. Advanced Patterns (LOW)
+
+- `advanced-event-handler-refs` - Store event handlers in refs
+- `advanced-use-latest` - useLatest for stable callback refs
+
+## How to Use
+
+Read individual rule files for detailed explanations and code examples:
+
+```
+rules/async-parallel.md
+rules/bundle-barrel-imports.md
+rules/_sections.md
+```
+
+Each rule file contains:
+
+- Brief explanation of why it matters
+- Incorrect code example with explanation
+- Correct code example with explanation
+- Additional context and references
+
+## Full Compiled Document
+
+For the complete guide with all rules expanded: `AGENTS.md`
diff --git a/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md b/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md
new file mode 100644
index 0000000..fbf2b62
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md
@@ -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: it creates a stable function reference that always calls the latest version of the handler.
diff --git a/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md b/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md
new file mode 100644
index 0000000..ef32deb
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md
@@ -0,0 +1,49 @@
+---
+title: useLatest for Stable Callback Refs
+impact: LOW
+impactDescription: prevents effect re-runs
+tags: advanced, hooks, useLatest, refs, optimization
+---
+
+## useLatest for Stable Callback Refs
+
+Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
+
+**Implementation:**
+
+```typescript
+function useLatest(value: T) {
+ const ref = useRef(value);
+ useLayoutEffect(() => {
+ ref.current = value;
+ }, [value]);
+ return ref;
+}
+```
+
+**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 (stable effect, fresh callback):**
+
+```tsx
+function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
+ const [query, setQuery] = useState("");
+ const onSearchRef = useLatest(onSearch);
+
+ useEffect(() => {
+ const timeout = setTimeout(() => onSearchRef.current(query), 300);
+ return () => clearTimeout(timeout);
+ }, [query]);
+}
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md b/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md
new file mode 100644
index 0000000..2c8c6d9
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md
@@ -0,0 +1,35 @@
+---
+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();
+ const session = await sessionPromise;
+ const [config, data] = await Promise.all([configPromise, fetchData(session.user.id)]);
+ return Response.json({ data, config });
+}
+```
+
+For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
diff --git a/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md b/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md
new file mode 100644
index 0000000..b6699b9
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md
@@ -0,0 +1,80 @@
+---
+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.
diff --git a/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md b/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md
new file mode 100644
index 0000000..5a614f4
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md
@@ -0,0 +1,37 @@
+---
+title: Dependency-Based Parallelization
+impact: CRITICAL
+impactDescription: 2-10× improvement
+tags: async, parallelization, dependencies, better-all
+---
+
+## Dependency-Based Parallelization
+
+For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
+
+**Incorrect (profile waits for config unnecessarily):**
+
+```typescript
+const [user, config] = await Promise.all([fetchUser(), fetchConfig()]);
+const profile = await fetchProfile(user.id);
+```
+
+**Correct (config and profile run in parallel):**
+
+```typescript
+import { all } from "better-all";
+
+const { user, config, profile } = await all({
+ async user() {
+ return fetchUser();
+ },
+ async config() {
+ return fetchConfig();
+ },
+ async profile() {
+ return fetchProfile((await this.$.user).id);
+ }
+});
+```
+
+Reference:
diff --git a/.claude/skills/vercel-react-best-practices/rules/async-parallel.md b/.claude/skills/vercel-react-best-practices/rules/async-parallel.md
new file mode 100644
index 0000000..f7d13a8
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/async-parallel.md
@@ -0,0 +1,24 @@
+---
+title: Promise.all() for Independent Operations
+impact: CRITICAL
+impactDescription: 2-10× improvement
+tags: async, parallelization, promises, waterfalls
+---
+
+## Promise.all() for Independent Operations
+
+When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
+
+**Incorrect (sequential execution, 3 round trips):**
+
+```typescript
+const user = await fetchUser();
+const posts = await fetchPosts();
+const comments = await fetchComments();
+```
+
+**Correct (parallel execution, 1 round trip):**
+
+```typescript
+const [user, posts, comments] = await Promise.all([fetchUser(), fetchPosts(), fetchComments()]);
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md b/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md
new file mode 100644
index 0000000..afe66a9
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md
@@ -0,0 +1,99 @@
+---
+title: Strategic Suspense Boundaries
+impact: HIGH
+impactDescription: faster initial paint
+tags: async, suspense, streaming, layout-shift
+---
+
+## Strategic Suspense Boundaries
+
+Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
+
+**Incorrect (wrapper blocked by data fetching):**
+
+```tsx
+async function Page() {
+ const data = await fetchData(); // Blocks entire page
+
+ return (
+
+
Sidebar
+
Header
+
+
+
+
Footer
+
+ );
+}
+```
+
+The entire layout waits for data even though only the middle section needs it.
+
+**Correct (wrapper shows immediately, data streams in):**
+
+```tsx
+function Page() {
+ return (
+
+
Sidebar
+
Header
+
+ }>
+
+
+
+
Footer
+
+ );
+}
+
+async function DataDisplay() {
+ const data = await fetchData(); // Only blocks this component
+ return
{data.content}
;
+}
+```
+
+Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
+
+**Alternative (share promise across components):**
+
+```tsx
+function Page() {
+ // Start fetch immediately, but don't await
+ const dataPromise = fetchData();
+
+ return (
+
+ );
+}
+```
+
+This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
diff --git a/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md b/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md
new file mode 100644
index 0000000..7097a9b
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md
@@ -0,0 +1,32 @@
+---
+title: Use Explicit Conditional Rendering
+impact: LOW
+impactDescription: prevents rendering 0 or NaN
+tags: rendering, conditional, jsx, falsy-values
+---
+
+## Use Explicit Conditional Rendering
+
+Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
+
+**Incorrect (renders "0" when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count && {count}}
;
+}
+
+// When count = 0, renders:
0
+// When count = 5, renders:
5
+```
+
+**Correct (renders nothing when count is 0):**
+
+```tsx
+function Badge({ count }: { count: number }) {
+ return
{count > 0 ? {count} : null}
;
+}
+
+// When count = 0, renders:
+// When count = 5, renders:
;
+}
+```
+
+This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
+
+**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
diff --git a/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md b/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md
new file mode 100644
index 0000000..203a227
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md
@@ -0,0 +1,72 @@
+---
+title: Prevent Hydration Mismatch Without Flickering
+impact: MEDIUM
+impactDescription: avoids visual flicker and hydration errors
+tags: rendering, ssr, hydration, localStorage, flicker
+---
+
+## Prevent Hydration Mismatch Without Flickering
+
+When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
+
+**Incorrect (breaks SSR):**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ // localStorage is not available on server - throws error
+ const theme = localStorage.getItem("theme") || "light";
+
+ return
;
+}
+```
+
+Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
+
+**Correct (no flicker, no hydration mismatch):**
+
+```tsx
+function ThemeWrapper({ children }: { children: ReactNode }) {
+ return (
+ <>
+
{children}
+
+ >
+ );
+}
+```
+
+The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
+
+This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
diff --git a/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md b/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md
new file mode 100644
index 0000000..6d77128
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md
@@ -0,0 +1,28 @@
+---
+title: Optimize SVG Precision
+impact: LOW
+impactDescription: reduces file size
+tags: rendering, svg, optimization, svgo
+---
+
+## Optimize SVG Precision
+
+Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
+
+**Incorrect (excessive precision):**
+
+```svg
+
+```
+
+**Correct (1 decimal place):**
+
+```svg
+
+```
+
+**Automate with SVGO:**
+
+```bash
+npx svgo --precision=1 --multipass icon.svg
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md b/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md
new file mode 100644
index 0000000..a271f86
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md
@@ -0,0 +1,39 @@
+---
+title: Defer State Reads to Usage Point
+impact: MEDIUM
+impactDescription: avoids unnecessary subscriptions
+tags: rerender, searchParams, localStorage, optimization
+---
+
+## Defer State Reads to Usage Point
+
+Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
+
+**Incorrect (subscribes to all searchParams changes):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const searchParams = useSearchParams();
+
+ const handleShare = () => {
+ const ref = searchParams.get("ref");
+ shareChat(chatId, { ref });
+ };
+
+ return ;
+}
+```
+
+**Correct (reads on demand, no subscription):**
+
+```tsx
+function ShareButton({ chatId }: { chatId: string }) {
+ const handleShare = () => {
+ const params = new URLSearchParams(window.location.search);
+ const ref = params.get("ref");
+ shareChat(chatId, { ref });
+ };
+
+ return ;
+}
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md b/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md
new file mode 100644
index 0000000..26d24a6
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md
@@ -0,0 +1,45 @@
+---
+title: Narrow Effect Dependencies
+impact: LOW
+impactDescription: minimizes effect re-runs
+tags: rerender, useEffect, dependencies, optimization
+---
+
+## Narrow Effect Dependencies
+
+Specify primitive dependencies instead of objects to minimize effect re-runs.
+
+**Incorrect (re-runs on any user field change):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id);
+}, [user]);
+```
+
+**Correct (re-runs only when id changes):**
+
+```tsx
+useEffect(() => {
+ console.log(user.id);
+}, [user.id]);
+```
+
+**For derived state, compute outside effect:**
+
+```tsx
+// Incorrect: runs on width=767, 766, 765...
+useEffect(() => {
+ if (width < 768) {
+ enableMobileMode();
+ }
+}, [width]);
+
+// Correct: runs only on boolean transition
+const isMobile = width < 768;
+useEffect(() => {
+ if (isMobile) {
+ enableMobileMode();
+ }
+}, [isMobile]);
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md b/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md
new file mode 100644
index 0000000..c632860
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/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/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md b/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md
new file mode 100644
index 0000000..57c9047
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md
@@ -0,0 +1,77 @@
+---
+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/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md b/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md
new file mode 100644
index 0000000..59ac965
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md
@@ -0,0 +1,56 @@
+---
+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/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md b/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md
new file mode 100644
index 0000000..7387426
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/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
+ );
+}
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
+```
diff --git a/.claude/skills/vercel-react-best-practices/rules/server-serialization.md b/.claude/skills/vercel-react-best-practices/rules/server-serialization.md
new file mode 100644
index 0000000..aa82d3b
--- /dev/null
+++ b/.claude/skills/vercel-react-best-practices/rules/server-serialization.md
@@ -0,0 +1,38 @@
+---
+title: Minimize Serialization at RSC Boundaries
+impact: HIGH
+impactDescription: reduces data transfer size
+tags: server, rsc, serialization, props
+---
+
+## Minimize Serialization at RSC Boundaries
+
+The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
+
+**Incorrect (serializes all 50 fields):**
+
+```tsx
+async function Page() {
+ const user = await fetchUser(); // 50 fields
+ return ;
+}
+
+("use client");
+function Profile({ user }: { user: User }) {
+ return
{user.name}
; // uses 1 field
+}
+```
+
+**Correct (serializes only 1 field):**
+
+```tsx
+async function Page() {
+ const user = await fetchUser();
+ return ;
+}
+
+("use client");
+function Profile({ name }: { name: string }) {
+ return