mirror of
https://github.com/OneWave-AI/claude-skills.git
synced 2026-09-14 15:58:36 +08:00
Add 14 starter pack skills to library
Adds development-focused skills from the starter pack that were missing from the repo: accessibility-auditor, api-endpoint-scaffolder, css-animation-creator, dependency-auditor, design-system-generator, docker-debugger, env-setup-wizard, error-boundary-creator, git-pr-reviewer, landing-page-optimizer, performance-profiler, react-component-generator, responsive-layout-builder, test-coverage-improver. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
---
|
||||
name: accessibility-auditor
|
||||
description: Audit websites for accessibility issues and WCAG compliance. Use when checking accessibility, fixing a11y issues, or ensuring WCAG compliance.
|
||||
---
|
||||
|
||||
# Accessibility Auditor
|
||||
|
||||
## Instructions
|
||||
|
||||
When auditing accessibility:
|
||||
|
||||
1. **Run automated checks** (axe, Lighthouse)
|
||||
2. **Manual keyboard testing**
|
||||
3. **Screen reader testing**
|
||||
4. **Check WCAG criteria**
|
||||
5. **Provide fixes**
|
||||
|
||||
## Automated Testing
|
||||
|
||||
```bash
|
||||
# Lighthouse accessibility audit
|
||||
npx lighthouse https://yoursite.com --only-categories=accessibility --view
|
||||
|
||||
# axe-core CLI
|
||||
npx @axe-core/cli https://yoursite.com
|
||||
|
||||
# Pa11y
|
||||
npx pa11y https://yoursite.com
|
||||
```
|
||||
|
||||
### React Testing Library + jest-axe
|
||||
|
||||
```typescript
|
||||
import { render } from '@testing-library/react';
|
||||
import { axe, toHaveNoViolations } from 'jest-axe';
|
||||
|
||||
expect.extend(toHaveNoViolations);
|
||||
|
||||
test('Button has no accessibility violations', async () => {
|
||||
const { container } = render(<Button>Click me</Button>);
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
```
|
||||
|
||||
### Playwright Accessibility Testing
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
|
||||
test('page has no accessibility violations', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
```
|
||||
|
||||
## WCAG Checklist
|
||||
|
||||
### Level A (Minimum)
|
||||
|
||||
#### Perceivable
|
||||
- [ ] **1.1.1** Non-text content has alt text
|
||||
- [ ] **1.3.1** Info and relationships are programmatically determined
|
||||
- [ ] **1.3.2** Meaningful reading sequence
|
||||
- [ ] **1.4.1** Color is not the only way to convey info
|
||||
|
||||
#### Operable
|
||||
- [ ] **2.1.1** All functionality available via keyboard
|
||||
- [ ] **2.1.2** No keyboard traps
|
||||
- [ ] **2.4.1** Skip navigation link provided
|
||||
- [ ] **2.4.2** Pages have descriptive titles
|
||||
- [ ] **2.4.3** Focus order is logical
|
||||
- [ ] **2.4.4** Link purpose is clear
|
||||
|
||||
#### Understandable
|
||||
- [ ] **3.1.1** Page language is specified
|
||||
- [ ] **3.2.1** Focus doesn't cause unexpected changes
|
||||
- [ ] **3.3.1** Errors are identified and described
|
||||
- [ ] **3.3.2** Labels or instructions provided
|
||||
|
||||
#### Robust
|
||||
- [ ] **4.1.1** Valid HTML (no duplicate IDs, proper nesting)
|
||||
- [ ] **4.1.2** Name, role, value for all UI components
|
||||
|
||||
### Level AA (Standard Target)
|
||||
|
||||
- [ ] **1.4.3** Contrast ratio 4.5:1 for text
|
||||
- [ ] **1.4.4** Text resizable to 200%
|
||||
- [ ] **1.4.10** Content reflows at 320px width
|
||||
- [ ] **2.4.6** Headings and labels are descriptive
|
||||
- [ ] **2.4.7** Focus indicator is visible
|
||||
- [ ] **3.2.3** Navigation is consistent
|
||||
- [ ] **3.2.4** Components identified consistently
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
### 1. Missing Alt Text
|
||||
|
||||
```tsx
|
||||
// Bad
|
||||
<img src="/hero.jpg" />
|
||||
|
||||
// Good - Informative image
|
||||
<img src="/hero.jpg" alt="Team collaborating in modern office" />
|
||||
|
||||
// Good - Decorative image
|
||||
<img src="/decoration.jpg" alt="" role="presentation" />
|
||||
|
||||
// Good - Icon button
|
||||
<button aria-label="Close dialog">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
```
|
||||
|
||||
### 2. Missing Form Labels
|
||||
|
||||
```tsx
|
||||
// Bad
|
||||
<input type="email" placeholder="Email" />
|
||||
|
||||
// Good - Visible label
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="email" />
|
||||
</div>
|
||||
|
||||
// Good - Visually hidden label
|
||||
<div>
|
||||
<label htmlFor="search" className="sr-only">Search</label>
|
||||
<input id="search" type="search" placeholder="Search..." />
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. Poor Color Contrast
|
||||
|
||||
```tsx
|
||||
// Bad - 2.5:1 ratio
|
||||
<p className="text-gray-400 bg-white">Low contrast text</p>
|
||||
|
||||
// Good - 4.5:1+ ratio
|
||||
<p className="text-gray-700 bg-white">Accessible text</p>
|
||||
|
||||
// Check contrast: https://webaim.org/resources/contrastchecker/
|
||||
```
|
||||
|
||||
### 4. Missing Focus Styles
|
||||
|
||||
```css
|
||||
/* Bad - Removes focus */
|
||||
*:focus { outline: none; }
|
||||
|
||||
/* Good - Custom focus style */
|
||||
*:focus-visible {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Tailwind */
|
||||
.btn {
|
||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Non-semantic HTML
|
||||
|
||||
```tsx
|
||||
// Bad
|
||||
<div onClick={handleClick}>Click me</div>
|
||||
|
||||
// Good
|
||||
<button onClick={handleClick}>Click me</button>
|
||||
|
||||
// Bad
|
||||
<div className="header">...</div>
|
||||
|
||||
// Good
|
||||
<header>...</header>
|
||||
```
|
||||
|
||||
### 6. Missing ARIA for Dynamic Content
|
||||
|
||||
```tsx
|
||||
// Loading state
|
||||
<button disabled aria-busy="true">
|
||||
<span className="sr-only">Loading</span>
|
||||
<Spinner aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
// Live region for updates
|
||||
<div aria-live="polite" aria-atomic="true">
|
||||
{message && <p>{message}</p>}
|
||||
</div>
|
||||
|
||||
// Modal
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<h2 id="modal-title">Dialog Title</h2>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 7. Skip Link
|
||||
|
||||
```tsx
|
||||
// Add as first focusable element
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-white"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
<main id="main-content">
|
||||
...
|
||||
</main>
|
||||
```
|
||||
|
||||
## Screen Reader Only Class
|
||||
|
||||
```css
|
||||
/* Visually hidden but accessible */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Show on focus (for skip links) */
|
||||
.sr-only-focusable:focus {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: inherit;
|
||||
margin: inherit;
|
||||
overflow: visible;
|
||||
clip: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
```
|
||||
|
||||
## Keyboard Testing Checklist
|
||||
|
||||
1. **Tab through page** - Logical order?
|
||||
2. **Enter/Space on buttons** - Activates?
|
||||
3. **Arrow keys in menus** - Navigates?
|
||||
4. **Escape** - Closes modals/dropdowns?
|
||||
5. **Focus visible** - Always visible?
|
||||
6. **No traps** - Can tab out of all components?
|
||||
|
||||
## Common ARIA Patterns
|
||||
|
||||
```tsx
|
||||
// Tabs
|
||||
<div role="tablist">
|
||||
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
|
||||
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
|
||||
</div>
|
||||
<div role="tabpanel" id="panel-1">Content 1</div>
|
||||
<div role="tabpanel" id="panel-2" hidden>Content 2</div>
|
||||
|
||||
// Accordion
|
||||
<button aria-expanded="true" aria-controls="content-1">Section 1</button>
|
||||
<div id="content-1">Content</div>
|
||||
|
||||
// Menu
|
||||
<button aria-haspopup="menu" aria-expanded="false">Options</button>
|
||||
<ul role="menu" hidden>
|
||||
<li role="menuitem">Option 1</li>
|
||||
<li role="menuitem">Option 2</li>
|
||||
</ul>
|
||||
|
||||
// Alert
|
||||
<div role="alert">Error: Please fix the form</div>
|
||||
|
||||
// Progress
|
||||
<div role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
|
||||
50%
|
||||
</div>
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
- **axe DevTools** - Browser extension
|
||||
- **WAVE** - Browser extension
|
||||
- **Lighthouse** - Built into Chrome
|
||||
- **NVDA** - Free Windows screen reader
|
||||
- **VoiceOver** - Built into macOS (Cmd+F5)
|
||||
- **Color Contrast Analyzer** - Desktop app
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: api-endpoint-scaffolder
|
||||
description: Generate REST API endpoints with proper structure, validation, error handling, and types. Use when creating new API routes, endpoints, or backend services.
|
||||
---
|
||||
|
||||
# API Endpoint Scaffolder
|
||||
|
||||
## Instructions
|
||||
|
||||
When creating a new API endpoint:
|
||||
|
||||
1. **Identify the framework** (Express, Next.js, FastAPI, etc.)
|
||||
2. **Determine HTTP method** (GET, POST, PUT, PATCH, DELETE)
|
||||
3. **Define request/response types**
|
||||
4. **Implement with best practices**
|
||||
|
||||
## Templates
|
||||
|
||||
### Next.js App Router (TypeScript)
|
||||
|
||||
```typescript
|
||||
// app/api/[resource]/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
const RequestSchema = z.object({
|
||||
// Define your schema
|
||||
});
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
// Implementation
|
||||
return NextResponse.json({ data }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('[API] Error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const validated = RequestSchema.parse(body);
|
||||
// Implementation
|
||||
return NextResponse.json({ data }, { status: 201 });
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Validation failed', details: error.errors },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Express (TypeScript)
|
||||
|
||||
```typescript
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { z } from 'zod';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const CreateSchema = z.object({
|
||||
// Define schema
|
||||
});
|
||||
|
||||
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = CreateSchema.parse(req.body);
|
||||
// Implementation
|
||||
res.status(201).json({ success: true, data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always validate input** using Zod, Yup, or similar
|
||||
2. **Use proper HTTP status codes**:
|
||||
- 200: Success
|
||||
- 201: Created
|
||||
- 400: Bad Request
|
||||
- 401: Unauthorized
|
||||
- 403: Forbidden
|
||||
- 404: Not Found
|
||||
- 500: Server Error
|
||||
3. **Log errors** but don't expose internals to clients
|
||||
4. **Use consistent response format**
|
||||
5. **Add rate limiting** for public endpoints
|
||||
6. **Document with OpenAPI/Swagger** when possible
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
---
|
||||
name: dependency-auditor
|
||||
description: Audit npm dependencies for security vulnerabilities, outdated packages, and unused dependencies. Use when checking for security issues, updating packages, or cleaning up dependencies.
|
||||
---
|
||||
|
||||
# Dependency Auditor
|
||||
|
||||
## Instructions
|
||||
|
||||
When auditing dependencies:
|
||||
|
||||
1. **Run security audit**
|
||||
2. **Check for outdated packages**
|
||||
3. **Find unused dependencies**
|
||||
4. **Analyze bundle size impact**
|
||||
5. **Review and update**
|
||||
|
||||
## Security Audit
|
||||
|
||||
```bash
|
||||
# NPM audit
|
||||
npm audit
|
||||
|
||||
# Get JSON output for processing
|
||||
npm audit --json
|
||||
|
||||
# Fix automatically (safe fixes only)
|
||||
npm audit fix
|
||||
|
||||
# Force fix (may have breaking changes)
|
||||
npm audit fix --force
|
||||
|
||||
# PNPM
|
||||
pnpm audit
|
||||
|
||||
# Yarn
|
||||
yarn audit
|
||||
```
|
||||
|
||||
## Check Outdated Packages
|
||||
|
||||
```bash
|
||||
# NPM
|
||||
npm outdated
|
||||
|
||||
# Interactive update
|
||||
npx npm-check-updates -i
|
||||
|
||||
# Update all to latest
|
||||
npx npm-check-updates -u
|
||||
npm install
|
||||
|
||||
# Check specific package
|
||||
npm view <package> versions
|
||||
```
|
||||
|
||||
## Find Unused Dependencies
|
||||
|
||||
```bash
|
||||
# Using depcheck
|
||||
npx depcheck
|
||||
|
||||
# With details
|
||||
npx depcheck --detailed
|
||||
|
||||
# Ignore patterns
|
||||
npx depcheck --ignores="@types/*,eslint-*"
|
||||
```
|
||||
|
||||
### Common False Positives
|
||||
|
||||
Depcheck may flag these as unused when they're actually needed:
|
||||
- `@types/*` packages (used by TypeScript)
|
||||
- ESLint/Prettier plugins (referenced in config)
|
||||
- PostCSS plugins (referenced in config)
|
||||
- Next.js plugins
|
||||
- Babel presets
|
||||
|
||||
## Analyze Bundle Size
|
||||
|
||||
```bash
|
||||
# For Next.js
|
||||
npx @next/bundle-analyzer
|
||||
|
||||
# General purpose
|
||||
npx source-map-explorer dist/**/*.js
|
||||
|
||||
# Check package size before installing
|
||||
npx package-phobia <package-name>
|
||||
|
||||
# Compare alternatives
|
||||
npx bundlephobia-cli compare lodash ramda
|
||||
```
|
||||
|
||||
## Dependency Review Checklist
|
||||
|
||||
### Security
|
||||
- [ ] No critical/high vulnerabilities
|
||||
- [ ] Dependencies actively maintained
|
||||
- [ ] No known malicious packages
|
||||
- [ ] Lock file committed
|
||||
|
||||
### Freshness
|
||||
- [ ] No major version behind (unless intentional)
|
||||
- [ ] Security patches applied
|
||||
- [ ] Deprecated packages replaced
|
||||
|
||||
### Cleanliness
|
||||
- [ ] No unused dependencies
|
||||
- [ ] No duplicate packages (check lock file)
|
||||
- [ ] devDependencies vs dependencies correct
|
||||
|
||||
## Update Strategies
|
||||
|
||||
### Conservative (Recommended)
|
||||
|
||||
```bash
|
||||
# Update patch versions only
|
||||
npm update
|
||||
|
||||
# Update specific package
|
||||
npm install package@latest
|
||||
```
|
||||
|
||||
### Aggressive
|
||||
|
||||
```bash
|
||||
# Update everything
|
||||
npx npm-check-updates -u
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
### Interactive
|
||||
|
||||
```bash
|
||||
npx npm-check-updates -i
|
||||
|
||||
# Options:
|
||||
# a - update all
|
||||
# space - toggle selection
|
||||
# enter - apply selected
|
||||
```
|
||||
|
||||
## Package.json Cleanup
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
// Runtime dependencies only
|
||||
},
|
||||
"devDependencies": {
|
||||
// Build/test tools only
|
||||
},
|
||||
"peerDependencies": {
|
||||
// For libraries only
|
||||
},
|
||||
"optionalDependencies": {
|
||||
// Platform-specific (rare)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Lock File Best Practices
|
||||
|
||||
1. **Always commit** lock files (package-lock.json, pnpm-lock.yaml, yarn.lock)
|
||||
2. **Use `npm ci`** in CI/CD (not `npm install`)
|
||||
3. **Regenerate** if corrupted: delete lock file + node_modules, reinstall
|
||||
4. **Single lock file** per project (don't mix package managers)
|
||||
|
||||
## Automated Monitoring
|
||||
|
||||
```yaml
|
||||
# .github/dependabot.yml
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
dev-dependencies:
|
||||
dependency-type: "development"
|
||||
```
|
||||
@@ -0,0 +1,388 @@
|
||||
---
|
||||
name: design-system-generator
|
||||
description: Create design systems with tokens, components, and documentation. Use when building design systems, creating component libraries, or establishing design tokens.
|
||||
---
|
||||
|
||||
# Design System Generator
|
||||
|
||||
## Instructions
|
||||
|
||||
When creating a design system:
|
||||
|
||||
1. **Define design tokens** (colors, typography, spacing)
|
||||
2. **Create base components** (Button, Input, Card, etc.)
|
||||
3. **Establish patterns** (forms, navigation, layouts)
|
||||
4. **Document usage** and variants
|
||||
|
||||
## Design Tokens
|
||||
|
||||
### Colors
|
||||
|
||||
```typescript
|
||||
// tokens/colors.ts
|
||||
export const colors = {
|
||||
// Brand
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6', // Main
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
},
|
||||
|
||||
// Semantic
|
||||
success: {
|
||||
light: '#dcfce7',
|
||||
main: '#22c55e',
|
||||
dark: '#15803d',
|
||||
},
|
||||
warning: {
|
||||
light: '#fef3c7',
|
||||
main: '#f59e0b',
|
||||
dark: '#b45309',
|
||||
},
|
||||
error: {
|
||||
light: '#fee2e2',
|
||||
main: '#ef4444',
|
||||
dark: '#b91c1c',
|
||||
},
|
||||
|
||||
// Neutral
|
||||
gray: {
|
||||
50: '#f9fafb',
|
||||
100: '#f3f4f6',
|
||||
200: '#e5e7eb',
|
||||
300: '#d1d5db',
|
||||
400: '#9ca3af',
|
||||
500: '#6b7280',
|
||||
600: '#4b5563',
|
||||
700: '#374151',
|
||||
800: '#1f2937',
|
||||
900: '#111827',
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
```typescript
|
||||
// tokens/typography.ts
|
||||
export const typography = {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'monospace'],
|
||||
},
|
||||
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1rem' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.25rem' }],
|
||||
base: ['1rem', { lineHeight: '1.5rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
||||
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
||||
'5xl': ['3rem', { lineHeight: '1.15' }],
|
||||
},
|
||||
|
||||
fontWeight: {
|
||||
normal: '400',
|
||||
medium: '500',
|
||||
semibold: '600',
|
||||
bold: '700',
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
```typescript
|
||||
// tokens/spacing.ts
|
||||
export const spacing = {
|
||||
px: '1px',
|
||||
0: '0',
|
||||
0.5: '0.125rem', // 2px
|
||||
1: '0.25rem', // 4px
|
||||
1.5: '0.375rem', // 6px
|
||||
2: '0.5rem', // 8px
|
||||
2.5: '0.625rem', // 10px
|
||||
3: '0.75rem', // 12px
|
||||
4: '1rem', // 16px
|
||||
5: '1.25rem', // 20px
|
||||
6: '1.5rem', // 24px
|
||||
8: '2rem', // 32px
|
||||
10: '2.5rem', // 40px
|
||||
12: '3rem', // 48px
|
||||
16: '4rem', // 64px
|
||||
20: '5rem', // 80px
|
||||
24: '6rem', // 96px
|
||||
} as const;
|
||||
```
|
||||
|
||||
### Shadows & Radii
|
||||
|
||||
```typescript
|
||||
// tokens/effects.ts
|
||||
export const shadows = {
|
||||
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
||||
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
|
||||
};
|
||||
|
||||
export const radii = {
|
||||
none: '0',
|
||||
sm: '0.125rem',
|
||||
DEFAULT: '0.25rem',
|
||||
md: '0.375rem',
|
||||
lg: '0.5rem',
|
||||
xl: '0.75rem',
|
||||
'2xl': '1rem',
|
||||
full: '9999px',
|
||||
};
|
||||
```
|
||||
|
||||
## CSS Variables Setup
|
||||
|
||||
```css
|
||||
/* globals.css */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: 59 130 246;
|
||||
--color-primary-foreground: 255 255 255;
|
||||
|
||||
--color-background: 255 255 255;
|
||||
--color-foreground: 17 24 39;
|
||||
|
||||
--color-muted: 243 244 246;
|
||||
--color-muted-foreground: 107 114 128;
|
||||
|
||||
--color-border: 229 231 235;
|
||||
--color-ring: 59 130 246;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-unit: 0.25rem;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: 'Inter', system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
/* Radii */
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-background: 17 24 39;
|
||||
--color-foreground: 243 244 246;
|
||||
--color-muted: 31 41 55;
|
||||
--color-muted-foreground: 156 163 175;
|
||||
--color-border: 55 65 81;
|
||||
}
|
||||
```
|
||||
|
||||
## Base Components
|
||||
|
||||
### Button
|
||||
|
||||
```tsx
|
||||
// components/ui/button.tsx
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'bg-primary-500 text-white hover:bg-primary-600',
|
||||
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
|
||||
outline: 'border border-gray-300 bg-transparent hover:bg-gray-50',
|
||||
ghost: 'hover:bg-gray-100',
|
||||
destructive: 'bg-red-500 text-white hover:bg-red-600',
|
||||
link: 'text-primary-500 underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
sm: 'h-8 px-3 text-sm',
|
||||
md: 'h-10 px-4',
|
||||
lg: 'h-12 px-6 text-lg',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
isLoading,
|
||||
children,
|
||||
disabled,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
disabled={disabled || isLoading}
|
||||
{...props}
|
||||
>
|
||||
{isLoading && <Spinner className="mr-2 h-4 w-4" />}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Input
|
||||
|
||||
```tsx
|
||||
// components/ui/input.tsx
|
||||
import { cn } from '@/lib/utils';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, error, ...props }, ref) => {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-10 w-full rounded-md border bg-white px-3 py-2 text-sm',
|
||||
'placeholder:text-gray-400',
|
||||
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
error ? 'border-red-500' : 'border-gray-300',
|
||||
className
|
||||
)}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-sm text-red-500" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
// components/ui/card.tsx
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export function Card({ className, ...props }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-lg border bg-white shadow-sm',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader({ className, ...props }: CardProps) {
|
||||
return <div className={cn('p-6 pb-0', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardTitle({ className, ...props }: CardProps) {
|
||||
return <h3 className={cn('text-lg font-semibold', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardContent({ className, ...props }: CardProps) {
|
||||
return <div className={cn('p-6', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardFooter({ className, ...props }: CardProps) {
|
||||
return <div className={cn('p-6 pt-0 flex gap-2', className)} {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Tailwind Config
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
const { colors, typography, spacing, shadows, radii } = require('./tokens');
|
||||
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: colors.primary,
|
||||
gray: colors.gray,
|
||||
success: colors.success,
|
||||
warning: colors.warning,
|
||||
error: colors.error,
|
||||
},
|
||||
fontFamily: typography.fontFamily,
|
||||
fontSize: typography.fontSize,
|
||||
spacing: spacing,
|
||||
boxShadow: shadows,
|
||||
borderRadius: radii,
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
```
|
||||
|
||||
## Utility Function
|
||||
|
||||
```typescript
|
||||
// lib/utils.ts
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
```
|
||||
|
||||
## Component Index
|
||||
|
||||
```typescript
|
||||
// components/ui/index.ts
|
||||
export { Button } from './button';
|
||||
export { Input } from './input';
|
||||
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './card';
|
||||
export { Badge } from './badge';
|
||||
export { Avatar } from './avatar';
|
||||
export { Select } from './select';
|
||||
export { Checkbox } from './checkbox';
|
||||
export { Radio } from './radio';
|
||||
export { Switch } from './switch';
|
||||
export { Modal } from './modal';
|
||||
export { Toast } from './toast';
|
||||
export { Tabs } from './tabs';
|
||||
export { Dropdown } from './dropdown';
|
||||
```
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
name: docker-debugger
|
||||
description: Debug Docker containers, fix Dockerfile issues, optimize images, and troubleshoot docker-compose. Use when having Docker problems, container issues, or optimizing Docker builds.
|
||||
---
|
||||
|
||||
# Docker Debugger
|
||||
|
||||
## Instructions
|
||||
|
||||
When debugging Docker issues:
|
||||
|
||||
1. **Identify the problem type**: Build, runtime, networking, or performance
|
||||
2. **Gather information** using diagnostic commands
|
||||
3. **Analyze logs and errors**
|
||||
4. **Apply fixes**
|
||||
|
||||
## Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Check running containers
|
||||
docker ps -a
|
||||
|
||||
# View container logs
|
||||
docker logs <container_id> --tail 100 -f
|
||||
|
||||
# Inspect container
|
||||
docker inspect <container_id>
|
||||
|
||||
# Check resource usage
|
||||
docker stats
|
||||
|
||||
# View container processes
|
||||
docker top <container_id>
|
||||
|
||||
# Execute shell in running container
|
||||
docker exec -it <container_id> /bin/sh
|
||||
|
||||
# Check Docker disk usage
|
||||
docker system df
|
||||
|
||||
# View build history
|
||||
docker history <image_name>
|
||||
```
|
||||
|
||||
## Common Issues & Solutions
|
||||
|
||||
### 1. Container exits immediately
|
||||
|
||||
```bash
|
||||
# Check exit code
|
||||
docker inspect <container_id> --format='{{.State.ExitCode}}'
|
||||
|
||||
# View last logs
|
||||
docker logs <container_id>
|
||||
```
|
||||
|
||||
**Fixes**:
|
||||
- Ensure CMD/ENTRYPOINT runs a foreground process
|
||||
- Check for missing dependencies
|
||||
- Verify environment variables
|
||||
|
||||
### 2. Build fails
|
||||
|
||||
```dockerfile
|
||||
# Use multi-stage builds for smaller images
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
### 3. Networking issues
|
||||
|
||||
```bash
|
||||
# List networks
|
||||
docker network ls
|
||||
|
||||
# Inspect network
|
||||
docker network inspect <network_name>
|
||||
|
||||
# Check container IP
|
||||
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container>
|
||||
```
|
||||
|
||||
### 4. Volume permission issues
|
||||
|
||||
```dockerfile
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 -S appgroup && \
|
||||
adduser -u 1001 -S appuser -G appgroup
|
||||
|
||||
# Set ownership
|
||||
COPY --chown=appuser:appgroup . .
|
||||
|
||||
USER appuser
|
||||
```
|
||||
|
||||
## Dockerfile Best Practices
|
||||
|
||||
```dockerfile
|
||||
# 1. Use specific tags, not :latest
|
||||
FROM node:20.10-alpine
|
||||
|
||||
# 2. Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# 3. Copy dependency files first (better caching)
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# 4. Copy source after dependencies
|
||||
COPY . .
|
||||
|
||||
# 5. Use non-root user
|
||||
USER node
|
||||
|
||||
# 6. Set proper labels
|
||||
LABEL maintainer="you@example.com"
|
||||
LABEL version="1.0"
|
||||
|
||||
# 7. Use HEALTHCHECK
|
||||
HEALTHCHECK --interval=30s --timeout=3s \
|
||||
CMD wget -q --spider http://localhost:3000/health || exit 1
|
||||
|
||||
# 8. Expose ports
|
||||
EXPOSE 3000
|
||||
|
||||
# 9. Use exec form for CMD
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
## docker-compose Debugging
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
# Add for debugging
|
||||
stdin_open: true
|
||||
tty: true
|
||||
# Override command for debugging
|
||||
command: /bin/sh
|
||||
volumes:
|
||||
- .:/app
|
||||
environment:
|
||||
- DEBUG=true
|
||||
```
|
||||
|
||||
```bash
|
||||
# Rebuild without cache
|
||||
docker-compose build --no-cache
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f app
|
||||
|
||||
# Restart single service
|
||||
docker-compose restart app
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
name: env-setup-wizard
|
||||
description: Set up environment variables, .env files, and configuration management. Use when configuring environment variables, creating .env files, or managing app configuration.
|
||||
---
|
||||
|
||||
# Environment Setup Wizard
|
||||
|
||||
## Instructions
|
||||
|
||||
When setting up environment configuration:
|
||||
|
||||
1. **Identify required variables** for the project
|
||||
2. **Create .env structure** with proper organization
|
||||
3. **Set up type-safe access** to env vars
|
||||
4. **Add validation** on startup
|
||||
5. **Document all variables**
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── .env # Local development (git-ignored)
|
||||
├── .env.example # Template (committed to git)
|
||||
├── .env.local # Local overrides (git-ignored)
|
||||
├── .env.development # Development defaults
|
||||
├── .env.production # Production defaults
|
||||
└── src/
|
||||
└── lib/
|
||||
└── env.ts # Type-safe env access
|
||||
```
|
||||
|
||||
## .env.example Template
|
||||
|
||||
```bash
|
||||
# ===================
|
||||
# Application
|
||||
# ===================
|
||||
NODE_ENV=development
|
||||
APP_URL=http://localhost:3000
|
||||
PORT=3000
|
||||
|
||||
# ===================
|
||||
# Database
|
||||
# ===================
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
||||
|
||||
# ===================
|
||||
# Authentication
|
||||
# ===================
|
||||
# Generate with: openssl rand -base64 32
|
||||
JWT_SECRET=
|
||||
NEXTAUTH_SECRET=
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# ===================
|
||||
# Third-party APIs
|
||||
# ===================
|
||||
# Get from: https://stripe.com/dashboard
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_PUBLISHABLE_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
|
||||
# Get from: https://resend.com
|
||||
RESEND_API_KEY=
|
||||
|
||||
# ===================
|
||||
# Storage
|
||||
# ===================
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=us-east-1
|
||||
S3_BUCKET_NAME=
|
||||
```
|
||||
|
||||
## Type-Safe Environment (Zod)
|
||||
|
||||
```typescript
|
||||
// src/lib/env.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
// App
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
APP_URL: z.string().url(),
|
||||
PORT: z.coerce.number().default(3000),
|
||||
|
||||
// Database
|
||||
DATABASE_URL: z.string().min(1),
|
||||
|
||||
// Auth
|
||||
JWT_SECRET: z.string().min(32),
|
||||
NEXTAUTH_SECRET: z.string().min(32),
|
||||
NEXTAUTH_URL: z.string().url(),
|
||||
|
||||
// APIs (optional in dev)
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
RESEND_API_KEY: z.string().optional(),
|
||||
});
|
||||
|
||||
// Validate on import
|
||||
const parsed = envSchema.safeParse(process.env);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error('❌ Invalid environment variables:');
|
||||
console.error(parsed.error.flatten().fieldErrors);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export const env = parsed.data;
|
||||
|
||||
// Type export for use elsewhere
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
```
|
||||
|
||||
## Next.js Specific
|
||||
|
||||
```typescript
|
||||
// src/lib/env.ts for Next.js
|
||||
import { z } from 'zod';
|
||||
|
||||
// Server-side variables
|
||||
const serverSchema = z.object({
|
||||
DATABASE_URL: z.string(),
|
||||
JWT_SECRET: z.string(),
|
||||
});
|
||||
|
||||
// Client-side variables (must start with NEXT_PUBLIC_)
|
||||
const clientSchema = z.object({
|
||||
NEXT_PUBLIC_APP_URL: z.string().url(),
|
||||
NEXT_PUBLIC_STRIPE_KEY: z.string(),
|
||||
});
|
||||
|
||||
export const serverEnv = serverSchema.parse(process.env);
|
||||
export const clientEnv = clientSchema.parse({
|
||||
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
||||
NEXT_PUBLIC_STRIPE_KEY: process.env.NEXT_PUBLIC_STRIPE_KEY,
|
||||
});
|
||||
```
|
||||
|
||||
## T3 Env (Recommended for Next.js)
|
||||
|
||||
```typescript
|
||||
// src/env.mjs
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
DATABASE_URL: z.string().url(),
|
||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||
},
|
||||
client: {
|
||||
NEXT_PUBLIC_APP_URL: z.string().url(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## .gitignore
|
||||
|
||||
```gitignore
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Keep example
|
||||
!.env.example
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Never commit secrets** to git
|
||||
2. **Use different values** per environment
|
||||
3. **Rotate secrets regularly**
|
||||
4. **Use secret managers** in production (Vault, AWS Secrets Manager)
|
||||
5. **Validate on startup** to fail fast
|
||||
6. **Prefix client vars** (NEXT_PUBLIC_, VITE_, REACT_APP_)
|
||||
@@ -0,0 +1,324 @@
|
||||
---
|
||||
name: error-boundary-creator
|
||||
description: Create error boundaries, error handling, and fallback UIs for React applications. Use when implementing error handling, creating fallback components, or setting up error reporting.
|
||||
---
|
||||
|
||||
# Error Boundary Creator
|
||||
|
||||
## Instructions
|
||||
|
||||
When implementing error handling:
|
||||
|
||||
1. **Identify error-prone areas** (async operations, third-party integrations)
|
||||
2. **Create appropriate error boundaries**
|
||||
3. **Design fallback UIs**
|
||||
4. **Set up error reporting**
|
||||
|
||||
## Basic Error Boundary
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { Component, ErrorInfo, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('Error caught by boundary:', error, errorInfo);
|
||||
// Send to error reporting service
|
||||
// reportError(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || <DefaultErrorFallback error={this.state.error} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function DefaultErrorFallback({ error }: { error?: Error }) {
|
||||
return (
|
||||
<div role="alert" className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<h2 className="text-lg font-semibold text-red-800">Something went wrong</h2>
|
||||
<p className="text-red-600 mt-1">{error?.message || 'An unexpected error occurred'}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
||||
>
|
||||
Reload page
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Error Boundary with Reset
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { Component, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export class ResettableErrorBoundary extends Component<Props, State> {
|
||||
state: State = { hasError: false };
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.props.onReset?.();
|
||||
this.setState({ hasError: false, error: undefined });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div role="alert" className="p-6 text-center">
|
||||
<h2 className="text-xl font-bold">Oops!</h2>
|
||||
<p className="text-gray-600 mt-2">{this.state.error?.message}</p>
|
||||
<button
|
||||
onClick={this.reset}
|
||||
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## react-error-boundary Library
|
||||
|
||||
```tsx
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
return (
|
||||
<div role="alert" className="p-4 bg-red-50 rounded-lg">
|
||||
<p className="font-medium">Something went wrong:</p>
|
||||
<pre className="text-sm text-red-600 mt-2">{error.message}</pre>
|
||||
<button onClick={resetErrorBoundary} className="mt-4 btn-primary">
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage
|
||||
function App() {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorFallback}
|
||||
onReset={() => {
|
||||
// Reset app state here
|
||||
}}
|
||||
onError={(error, info) => {
|
||||
// Log to error reporting service
|
||||
console.error(error, info);
|
||||
}}
|
||||
>
|
||||
<MyComponent />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Next.js Error Handling
|
||||
|
||||
### App Router error.tsx
|
||||
|
||||
```tsx
|
||||
// app/error.tsx (or any route segment)
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log error to reporting service
|
||||
console.error(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px]">
|
||||
<h2 className="text-2xl font-bold">Something went wrong!</h2>
|
||||
<button
|
||||
onClick={reset}
|
||||
className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Global Error (app/global-error.tsx)
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
<h2 className="text-2xl font-bold">Something went wrong!</h2>
|
||||
<button onClick={reset} className="mt-4 btn-primary">
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found (app/not-found.tsx)
|
||||
|
||||
```tsx
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px]">
|
||||
<h2 className="text-4xl font-bold">404</h2>
|
||||
<p className="text-gray-600 mt-2">Page not found</p>
|
||||
<Link href="/" className="mt-4 text-blue-600 hover:underline">
|
||||
Go home
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Async Error Handling
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface AsyncState<T> {
|
||||
data: T | null;
|
||||
error: Error | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
function useAsync<T>() {
|
||||
const [state, setState] = useState<AsyncState<T>>({
|
||||
data: null,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const execute = async (promise: Promise<T>) => {
|
||||
setState({ data: null, error: null, isLoading: true });
|
||||
try {
|
||||
const data = await promise;
|
||||
setState({ data, error: null, isLoading: false });
|
||||
return data;
|
||||
} catch (error) {
|
||||
setState({ data: null, error: error as Error, isLoading: false });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return { ...state, execute };
|
||||
}
|
||||
|
||||
// Usage
|
||||
function DataComponent() {
|
||||
const { data, error, isLoading, execute } = useAsync<User[]>();
|
||||
|
||||
const loadData = () => execute(fetchUsers());
|
||||
|
||||
if (isLoading) return <Spinner />;
|
||||
if (error) return <ErrorMessage error={error} onRetry={loadData} />;
|
||||
if (!data) return <button onClick={loadData}>Load</button>;
|
||||
|
||||
return <UserList users={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Reporting Integration
|
||||
|
||||
```typescript
|
||||
// lib/error-reporting.ts
|
||||
export function reportError(error: Error, context?: Record<string, unknown>) {
|
||||
// Sentry
|
||||
// Sentry.captureException(error, { extra: context });
|
||||
|
||||
// LogRocket
|
||||
// LogRocket.captureException(error);
|
||||
|
||||
// Custom endpoint
|
||||
fetch('/api/errors', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
context,
|
||||
timestamp: new Date().toISOString(),
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
}),
|
||||
}).catch(console.error);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Wrap at route level** for page-level isolation
|
||||
2. **Wrap third-party components** separately
|
||||
3. **Provide meaningful fallbacks** with recovery options
|
||||
4. **Log errors** to monitoring service
|
||||
5. **Don't catch errors you can't handle**
|
||||
6. **Test error states** in development
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: git-pr-reviewer
|
||||
description: Review pull requests for code quality, security issues, and best practices. Use when reviewing PRs, checking code changes, or analyzing diffs before merge.
|
||||
allowed-tools: Read, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
# Git PR Reviewer
|
||||
|
||||
## Instructions
|
||||
|
||||
When reviewing a pull request:
|
||||
|
||||
1. **Get the diff**: Run `git diff main...HEAD` or `git diff <base-branch>...HEAD`
|
||||
2. **Analyze changed files**: Identify all modified, added, and deleted files
|
||||
3. **Review each file** for:
|
||||
- Logic errors and bugs
|
||||
- Security vulnerabilities (SQL injection, XSS, hardcoded secrets)
|
||||
- Performance issues (N+1 queries, unnecessary re-renders, memory leaks)
|
||||
- Code style and consistency
|
||||
- Missing error handling
|
||||
- Test coverage gaps
|
||||
|
||||
## Review Checklist
|
||||
|
||||
### Security
|
||||
- [ ] No hardcoded credentials or API keys
|
||||
- [ ] Input validation on user data
|
||||
- [ ] Proper authentication/authorization checks
|
||||
- [ ] No SQL injection vulnerabilities
|
||||
- [ ] XSS prevention in place
|
||||
|
||||
### Code Quality
|
||||
- [ ] Functions are small and focused
|
||||
- [ ] No code duplication
|
||||
- [ ] Clear variable/function naming
|
||||
- [ ] Proper error handling
|
||||
- [ ] No unused imports or dead code
|
||||
|
||||
### Performance
|
||||
- [ ] No unnecessary database queries
|
||||
- [ ] Efficient algorithms used
|
||||
- [ ] Proper caching where needed
|
||||
- [ ] No memory leaks
|
||||
|
||||
### Testing
|
||||
- [ ] New code has tests
|
||||
- [ ] Edge cases covered
|
||||
- [ ] Tests are meaningful, not just for coverage
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## PR Review Summary
|
||||
|
||||
### Overview
|
||||
[Brief summary of changes]
|
||||
|
||||
### Issues Found
|
||||
#### Critical
|
||||
- [Issue description + file:line]
|
||||
|
||||
#### Warnings
|
||||
- [Issue description + file:line]
|
||||
|
||||
#### Suggestions
|
||||
- [Improvement ideas]
|
||||
|
||||
### Approval Status
|
||||
[APPROVE / REQUEST CHANGES / NEEDS DISCUSSION]
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Review current branch against main
|
||||
git diff main...HEAD --stat
|
||||
git diff main...HEAD
|
||||
```
|
||||
@@ -0,0 +1,325 @@
|
||||
---
|
||||
name: landing-page-optimizer
|
||||
description: Optimize landing pages for conversions, performance, and SEO. Use when improving landing pages, increasing conversions, or optimizing page performance.
|
||||
---
|
||||
|
||||
# Landing Page Optimizer
|
||||
|
||||
## Instructions
|
||||
|
||||
When optimizing landing pages:
|
||||
|
||||
1. **Audit current page** (speed, UX, conversion elements)
|
||||
2. **Identify improvement areas**
|
||||
3. **Implement optimizations**
|
||||
4. **Set up tracking**
|
||||
|
||||
## Above the Fold Checklist
|
||||
|
||||
- [ ] **Clear headline** - Value proposition in <5 seconds
|
||||
- [ ] **Supporting subheadline** - Expand on benefit
|
||||
- [ ] **Hero image/video** - Relevant, high-quality
|
||||
- [ ] **Primary CTA** - Contrasting color, action-oriented
|
||||
- [ ] **Social proof** - Logos, ratings, testimonials
|
||||
- [ ] **No navigation distractions** - Minimal or hidden nav
|
||||
|
||||
## Page Structure Template
|
||||
|
||||
```tsx
|
||||
// Optimal landing page structure
|
||||
<main>
|
||||
{/* 1. Hero Section */}
|
||||
<section className="min-h-[80vh] flex items-center">
|
||||
<div className="max-w-6xl mx-auto px-4 grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<Badge>New Feature</Badge>
|
||||
<h1 className="text-4xl lg:text-6xl font-bold mt-4">
|
||||
Main Value Proposition
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mt-6">
|
||||
Supporting statement that expands on the benefit
|
||||
</p>
|
||||
<div className="flex gap-4 mt-8">
|
||||
<Button size="lg">Primary CTA</Button>
|
||||
<Button size="lg" variant="outline">Secondary CTA</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-6 mt-8">
|
||||
<div className="flex -space-x-2">
|
||||
{avatars.map(a => <Avatar key={a.id} src={a.src} />)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
<strong>2,000+</strong> happy customers
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/hero-image.png" alt="Product preview" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. Social Proof - Logos */}
|
||||
<section className="py-12 bg-gray-50">
|
||||
<p className="text-center text-gray-500 mb-8">Trusted by leading companies</p>
|
||||
<div className="flex justify-center gap-12 opacity-60">
|
||||
{logos.map(logo => <img key={logo.name} src={logo.src} alt={logo.name} />)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Problem/Solution */}
|
||||
<section className="py-20">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-3xl font-bold">The Problem</h2>
|
||||
<p className="text-xl text-gray-600 mt-4">
|
||||
Describe the pain point your audience faces
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Features/Benefits */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<h2 className="text-3xl font-bold text-center">How It Works</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8 mt-12 max-w-6xl mx-auto">
|
||||
{features.map(feature => (
|
||||
<Card key={feature.title}>
|
||||
<feature.icon className="w-12 h-12 text-primary-500" />
|
||||
<h3 className="text-xl font-semibold mt-4">{feature.title}</h3>
|
||||
<p className="text-gray-600 mt-2">{feature.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Testimonials */}
|
||||
<section className="py-20">
|
||||
<h2 className="text-3xl font-bold text-center">What Customers Say</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mt-12">
|
||||
{testimonials.map(t => (
|
||||
<TestimonialCard key={t.name} {...t} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 6. Pricing (if applicable) */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<PricingTable plans={plans} />
|
||||
</section>
|
||||
|
||||
{/* 7. FAQ */}
|
||||
<section className="py-20">
|
||||
<h2 className="text-3xl font-bold text-center">FAQ</h2>
|
||||
<Accordion items={faqs} className="max-w-3xl mx-auto mt-12" />
|
||||
</section>
|
||||
|
||||
{/* 8. Final CTA */}
|
||||
<section className="py-20 bg-primary-600 text-white text-center">
|
||||
<h2 className="text-3xl font-bold">Ready to Get Started?</h2>
|
||||
<p className="text-xl opacity-90 mt-4">Join 2,000+ companies already using our product</p>
|
||||
<Button size="lg" variant="secondary" className="mt-8">
|
||||
Start Free Trial
|
||||
</Button>
|
||||
<p className="text-sm opacity-75 mt-4">No credit card required</p>
|
||||
</section>
|
||||
</main>
|
||||
```
|
||||
|
||||
## CTA Optimization
|
||||
|
||||
### Button Best Practices
|
||||
|
||||
```tsx
|
||||
// Good CTAs
|
||||
<Button>Start Free Trial</Button>
|
||||
<Button>Get Started Free</Button>
|
||||
<Button>Try It Free for 14 Days</Button>
|
||||
<Button>Book a Demo</Button>
|
||||
|
||||
// Add urgency/value
|
||||
<Button>
|
||||
Get 50% Off Today
|
||||
<span className="text-sm opacity-75 block">Offer ends midnight</span>
|
||||
</Button>
|
||||
|
||||
// Reduce friction
|
||||
<div className="text-center">
|
||||
<Button size="lg">Start Free Trial</Button>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
No credit card required • Cancel anytime
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### CTA Placement
|
||||
|
||||
1. **Hero section** - Primary CTA above fold
|
||||
2. **After features** - Reinforce value
|
||||
3. **After testimonials** - Social proof boost
|
||||
4. **Sticky header/footer** - Always accessible
|
||||
5. **Exit intent popup** - Last chance
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Image Optimization
|
||||
|
||||
```tsx
|
||||
// Next.js Image component
|
||||
import Image from 'next/image';
|
||||
|
||||
<Image
|
||||
src="/hero.png"
|
||||
alt="Product screenshot"
|
||||
width={1200}
|
||||
height={800}
|
||||
priority // Above-fold images
|
||||
placeholder="blur"
|
||||
blurDataURL={blurData}
|
||||
/>
|
||||
|
||||
// Lazy load below-fold images
|
||||
<Image
|
||||
src="/feature.png"
|
||||
loading="lazy"
|
||||
...
|
||||
/>
|
||||
```
|
||||
|
||||
### Critical CSS
|
||||
|
||||
```tsx
|
||||
// Inline critical styles for above-fold
|
||||
<head>
|
||||
<style dangerouslySetInnerHTML={{ __html: criticalCSS }} />
|
||||
<link rel="preload" href="/fonts/inter.woff2" as="font" crossOrigin="" />
|
||||
</head>
|
||||
```
|
||||
|
||||
### Performance Targets
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| LCP | < 2.5s |
|
||||
| FID/INP | < 100ms |
|
||||
| CLS | < 0.1 |
|
||||
| Total Size | < 1MB |
|
||||
| Time to Interactive | < 3s |
|
||||
|
||||
## SEO Optimization
|
||||
|
||||
### Meta Tags
|
||||
|
||||
```tsx
|
||||
// app/layout.tsx or pages/_app.tsx
|
||||
export const metadata = {
|
||||
title: 'Product Name - Main Benefit | Brand',
|
||||
description: 'Clear description with keywords. 150-160 chars.',
|
||||
openGraph: {
|
||||
title: 'Product Name - Main Benefit',
|
||||
description: 'Description for social sharing',
|
||||
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Structured Data
|
||||
|
||||
```tsx
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Product Name",
|
||||
"applicationCategory": "BusinessApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "29",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": "4.8",
|
||||
"reviewCount": "1250"
|
||||
}
|
||||
})}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Conversion Tracking
|
||||
|
||||
```tsx
|
||||
// Google Analytics 4 events
|
||||
const trackCTA = (ctaName: string) => {
|
||||
gtag('event', 'cta_click', {
|
||||
cta_name: ctaName,
|
||||
page_location: window.location.href,
|
||||
});
|
||||
};
|
||||
|
||||
// Track scroll depth
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
gtag('event', 'section_viewed', {
|
||||
section_name: entry.target.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
|
||||
document.querySelectorAll('section[id]').forEach((section) => {
|
||||
observer.observe(section);
|
||||
});
|
||||
}, []);
|
||||
```
|
||||
|
||||
## A/B Testing Elements
|
||||
|
||||
Priority elements to test:
|
||||
1. **Headline copy** - Different value propositions
|
||||
2. **CTA text** - "Start Free" vs "Get Started" vs "Try Now"
|
||||
3. **CTA color** - High contrast options
|
||||
4. **Hero image** - Product vs people vs abstract
|
||||
5. **Social proof placement** - Above vs below fold
|
||||
6. **Pricing display** - Monthly vs annual default
|
||||
7. **Form length** - Email only vs full form
|
||||
|
||||
## Mobile Optimization
|
||||
|
||||
```tsx
|
||||
// Thumb-friendly CTAs
|
||||
<Button className="w-full md:w-auto h-14 text-lg">
|
||||
Get Started
|
||||
</Button>
|
||||
|
||||
// Sticky mobile CTA
|
||||
<div className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t md:hidden">
|
||||
<Button className="w-full">Start Free Trial</Button>
|
||||
</div>
|
||||
|
||||
// Reduce content on mobile
|
||||
<p className="hidden md:block">
|
||||
{fullDescription}
|
||||
</p>
|
||||
<p className="md:hidden">
|
||||
{shortDescription}
|
||||
</p>
|
||||
```
|
||||
|
||||
## Quick Wins Checklist
|
||||
|
||||
- [ ] Headline communicates value in 5 seconds
|
||||
- [ ] CTA button is high contrast and above fold
|
||||
- [ ] Page loads in under 3 seconds
|
||||
- [ ] Social proof visible above fold
|
||||
- [ ] Mobile-optimized with thumb-friendly CTAs
|
||||
- [ ] No broken images or links
|
||||
- [ ] Forms have minimal required fields
|
||||
- [ ] Trust badges near CTAs (security, guarantees)
|
||||
- [ ] Clear pricing (no hidden fees messaging)
|
||||
- [ ] Testimonials include photos and titles
|
||||
@@ -0,0 +1,181 @@
|
||||
---
|
||||
name: performance-profiler
|
||||
description: Profile and optimize application performance including load times, memory usage, and rendering. Use when debugging slow performance, memory leaks, or optimizing app speed.
|
||||
---
|
||||
|
||||
# Performance Profiler
|
||||
|
||||
## Instructions
|
||||
|
||||
When profiling performance:
|
||||
|
||||
1. **Identify the bottleneck type**: Network, rendering, memory, or compute
|
||||
2. **Measure baseline** before optimizing
|
||||
3. **Profile with appropriate tools**
|
||||
4. **Apply optimizations**
|
||||
5. **Measure improvement**
|
||||
|
||||
## Web Performance
|
||||
|
||||
### Core Web Vitals
|
||||
|
||||
```bash
|
||||
# Lighthouse CLI
|
||||
npx lighthouse https://yoursite.com --view
|
||||
|
||||
# With specific metrics
|
||||
npx lighthouse https://yoursite.com --only-categories=performance
|
||||
```
|
||||
|
||||
**Target Metrics**:
|
||||
| Metric | Good | Needs Work | Poor |
|
||||
|--------|------|------------|------|
|
||||
| LCP (Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s |
|
||||
| INP (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms |
|
||||
| CLS (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
```bash
|
||||
# Next.js
|
||||
ANALYZE=true npm run build
|
||||
|
||||
# Webpack
|
||||
npx webpack-bundle-analyzer stats.json
|
||||
|
||||
# Vite
|
||||
npx vite-bundle-visualizer
|
||||
```
|
||||
|
||||
## React Performance
|
||||
|
||||
### React DevTools Profiler
|
||||
|
||||
1. Install React DevTools browser extension
|
||||
2. Open DevTools → Profiler tab
|
||||
3. Click Record, interact with app, stop recording
|
||||
4. Analyze flame graph for slow components
|
||||
|
||||
### Common React Optimizations
|
||||
|
||||
```tsx
|
||||
// 1. Memoize expensive components
|
||||
const MemoizedList = React.memo(function List({ items }) {
|
||||
return items.map(item => <Item key={item.id} {...item} />);
|
||||
});
|
||||
|
||||
// 2. Use useMemo for expensive calculations
|
||||
const sortedItems = useMemo(() => {
|
||||
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [items]);
|
||||
|
||||
// 3. Use useCallback for stable function references
|
||||
const handleClick = useCallback((id: string) => {
|
||||
setSelected(id);
|
||||
}, []);
|
||||
|
||||
// 4. Virtualize long lists
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
||||
function VirtualList({ items }) {
|
||||
return (
|
||||
<FixedSizeList
|
||||
height={400}
|
||||
itemCount={items.length}
|
||||
itemSize={50}
|
||||
width="100%"
|
||||
>
|
||||
{({ index, style }) => (
|
||||
<div style={style}>{items[index].name}</div>
|
||||
)}
|
||||
</FixedSizeList>
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Lazy load components
|
||||
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<HeavyComponent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Node.js Performance
|
||||
|
||||
### Profiling
|
||||
|
||||
```bash
|
||||
# CPU profile
|
||||
node --prof app.js
|
||||
node --prof-process isolate-*.log > profile.txt
|
||||
|
||||
# Heap snapshot
|
||||
node --inspect app.js
|
||||
# Then use Chrome DevTools Memory tab
|
||||
|
||||
# Clinic.js (comprehensive)
|
||||
npx clinic doctor -- node app.js
|
||||
npx clinic flame -- node app.js
|
||||
npx clinic bubbleprof -- node app.js
|
||||
```
|
||||
|
||||
### Memory Leak Detection
|
||||
|
||||
```javascript
|
||||
// Add to app for debugging
|
||||
const used = process.memoryUsage();
|
||||
console.log({
|
||||
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
|
||||
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
|
||||
external: `${Math.round(used.external / 1024 / 1024)} MB`,
|
||||
});
|
||||
```
|
||||
|
||||
## Database Performance
|
||||
|
||||
```sql
|
||||
-- PostgreSQL: Analyze slow queries
|
||||
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
|
||||
|
||||
-- Find missing indexes
|
||||
SELECT relname, seq_scan, idx_scan
|
||||
FROM pg_stat_user_tables
|
||||
WHERE seq_scan > idx_scan;
|
||||
```
|
||||
|
||||
### Query Optimization
|
||||
|
||||
```typescript
|
||||
// Bad: N+1 query
|
||||
const users = await db.user.findMany();
|
||||
for (const user of users) {
|
||||
const posts = await db.post.findMany({ where: { userId: user.id } });
|
||||
}
|
||||
|
||||
// Good: Single query with include
|
||||
const users = await db.user.findMany({
|
||||
include: { posts: true }
|
||||
});
|
||||
|
||||
// Good: Select only needed fields
|
||||
const users = await db.user.findMany({
|
||||
select: { id: true, name: true, email: true }
|
||||
});
|
||||
```
|
||||
|
||||
## Quick Wins Checklist
|
||||
|
||||
- [ ] Enable gzip/brotli compression
|
||||
- [ ] Add caching headers
|
||||
- [ ] Lazy load images (`loading="lazy"`)
|
||||
- [ ] Preconnect to external domains
|
||||
- [ ] Use CDN for static assets
|
||||
- [ ] Minimize JavaScript bundle
|
||||
- [ ] Defer non-critical JS
|
||||
- [ ] Optimize images (WebP, proper sizing)
|
||||
- [ ] Add database indexes
|
||||
- [ ] Use connection pooling
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
name: react-component-generator
|
||||
description: Generate React components with TypeScript, proper props, hooks, and accessibility. Use when creating new React components, UI elements, or refactoring existing components.
|
||||
---
|
||||
|
||||
# React Component Generator
|
||||
|
||||
## Instructions
|
||||
|
||||
When creating React components:
|
||||
|
||||
1. **Determine component type**: Client or Server component
|
||||
2. **Define props interface** with TypeScript
|
||||
3. **Implement with best practices**
|
||||
4. **Add accessibility attributes**
|
||||
|
||||
## Templates
|
||||
|
||||
### Client Component
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
isLoading?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
isLoading = false,
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const baseStyles = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2';
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
||||
ghost: 'hover:bg-gray-100',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'h-8 px-3 text-sm',
|
||||
md: 'h-10 px-4',
|
||||
lg: 'h-12 px-6 text-lg',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(baseStyles, variants[variant], sizes[size], className)}
|
||||
disabled={disabled || isLoading}
|
||||
aria-busy={isLoading}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? <span className="animate-spin mr-2">⏳</span> : null}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Server Component
|
||||
|
||||
```tsx
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
interface UserListProps {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export async function UserList({ limit = 10 }: UserListProps) {
|
||||
const users = await db.user.findMany({ take: limit });
|
||||
|
||||
if (users.length === 0) {
|
||||
return <p className="text-gray-500">No users found.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul role="list" className="divide-y">
|
||||
{users.map((user) => (
|
||||
<li key={user.id} className="py-4">
|
||||
<span>{user.name}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Form Component with React Hook Form
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Min 8 characters'),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export function LoginForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} noValidate>
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
aria-invalid={!!errors.email}
|
||||
aria-describedby={errors.email ? 'email-error' : undefined}
|
||||
{...register('email')}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p id="email-error" role="alert">{errors.email.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Loading...' : 'Submit'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility Checklist
|
||||
|
||||
- [ ] Use semantic HTML elements
|
||||
- [ ] Add `aria-label` for icon-only buttons
|
||||
- [ ] Include `role` attributes where needed
|
||||
- [ ] Ensure keyboard navigation works
|
||||
- [ ] Add `aria-invalid` and `aria-describedby` for form errors
|
||||
- [ ] Use `aria-busy` for loading states
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
name: responsive-layout-builder
|
||||
description: Build responsive layouts with CSS Grid, Flexbox, and container queries. Use when creating responsive designs, fixing layout issues, or building mobile-first layouts.
|
||||
---
|
||||
|
||||
# Responsive Layout Builder
|
||||
|
||||
## Instructions
|
||||
|
||||
When building responsive layouts:
|
||||
|
||||
1. **Identify the layout pattern** (grid, sidebar, cards, etc.)
|
||||
2. **Start mobile-first**
|
||||
3. **Use appropriate CSS technique** (Grid vs Flexbox)
|
||||
4. **Add breakpoints** for larger screens
|
||||
5. **Test across viewports**
|
||||
|
||||
## Breakpoints
|
||||
|
||||
```css
|
||||
/* Tailwind defaults */
|
||||
sm: 640px /* Small devices */
|
||||
md: 768px /* Tablets */
|
||||
lg: 1024px /* Laptops */
|
||||
xl: 1280px /* Desktops */
|
||||
2xl: 1536px /* Large screens */
|
||||
|
||||
/* Custom CSS */
|
||||
@media (min-width: 640px) { }
|
||||
@media (min-width: 768px) { }
|
||||
@media (min-width: 1024px) { }
|
||||
```
|
||||
|
||||
## Common Layout Patterns
|
||||
|
||||
### 1. Holy Grail Layout
|
||||
|
||||
```tsx
|
||||
// Tailwind CSS
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<header className="h-16 bg-white border-b">Header</header>
|
||||
|
||||
<div className="flex-1 flex">
|
||||
<aside className="hidden md:block w-64 bg-gray-50 border-r">
|
||||
Sidebar
|
||||
</aside>
|
||||
<main className="flex-1 p-6">
|
||||
Main Content
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer className="h-16 bg-white border-t">Footer</footer>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
/* Plain CSS */
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
grid-template-columns: 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.layout {
|
||||
grid-template-columns: 250px 1fr;
|
||||
}
|
||||
|
||||
.header, .footer {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Responsive Card Grid
|
||||
|
||||
```tsx
|
||||
// Tailwind - Auto-fit cards
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{items.map(item => (
|
||||
<Card key={item.id} {...item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
// Auto-fill with minimum width
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-6">
|
||||
{items.map(item => (
|
||||
<Card key={item.id} {...item} />
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
/* Plain CSS */
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Sidebar Layout
|
||||
|
||||
```tsx
|
||||
// Fixed sidebar, scrollable content
|
||||
<div className="flex h-screen">
|
||||
<aside className="w-64 flex-shrink-0 overflow-y-auto border-r bg-gray-50">
|
||||
<nav className="p-4">Sidebar Nav</nav>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="p-6">Main Content</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
// Collapsible sidebar
|
||||
<div className="flex h-screen">
|
||||
<aside className={cn(
|
||||
"flex-shrink-0 overflow-y-auto border-r bg-gray-50 transition-all",
|
||||
isOpen ? "w-64" : "w-16"
|
||||
)}>
|
||||
<nav className="p-4">...</nav>
|
||||
</aside>
|
||||
<main className="flex-1 overflow-y-auto p-6">...</main>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. Hero Section
|
||||
|
||||
```tsx
|
||||
<section className="relative min-h-[60vh] flex items-center justify-center px-4">
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-purple-700" />
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 max-w-4xl mx-auto text-center text-white">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">
|
||||
Headline Here
|
||||
</h1>
|
||||
<p className="mt-6 text-lg md:text-xl opacity-90 max-w-2xl mx-auto">
|
||||
Subheadline text goes here with more details.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button className="px-8 py-3 bg-white text-blue-600 rounded-lg font-semibold">
|
||||
Primary CTA
|
||||
</button>
|
||||
<button className="px-8 py-3 border-2 border-white rounded-lg font-semibold">
|
||||
Secondary CTA
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
### 5. Masonry Grid
|
||||
|
||||
```tsx
|
||||
// CSS Columns approach
|
||||
<div className="columns-1 sm:columns-2 lg:columns-3 gap-6 space-y-6">
|
||||
{items.map(item => (
|
||||
<div key={item.id} className="break-inside-avoid">
|
||||
<Card {...item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6. Sticky Header
|
||||
|
||||
```tsx
|
||||
<header className="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b">
|
||||
<nav className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
|
||||
<Logo />
|
||||
<NavLinks className="hidden md:flex" />
|
||||
<MobileMenuButton className="md:hidden" />
|
||||
</nav>
|
||||
</header>
|
||||
```
|
||||
|
||||
## Container Queries (Modern)
|
||||
|
||||
```css
|
||||
/* Define container */
|
||||
.card-container {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
/* Query the container */
|
||||
@container (min-width: 400px) {
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Tailwind v3.2+
|
||||
<div className="@container">
|
||||
<div className="flex flex-col @md:flex-row">
|
||||
<img className="w-full @md:w-48" />
|
||||
<div className="p-4">Content</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Flexbox vs Grid Decision
|
||||
|
||||
| Use Flexbox | Use Grid |
|
||||
|-------------|----------|
|
||||
| Navigation bars | Page layouts |
|
||||
| Card content alignment | Card grids |
|
||||
| Centering content | Complex 2D layouts |
|
||||
| Space distribution | Overlapping elements |
|
||||
| Unknown item count | Defined structure |
|
||||
|
||||
## Responsive Typography
|
||||
|
||||
```tsx
|
||||
// Fluid typography with clamp
|
||||
<h1 className="text-[clamp(2rem,5vw,4rem)]">
|
||||
Responsive Heading
|
||||
</h1>
|
||||
|
||||
// Tailwind responsive
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl">
|
||||
Responsive Heading
|
||||
</h1>
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] 320px (small phones)
|
||||
- [ ] 375px (iPhone)
|
||||
- [ ] 768px (tablet portrait)
|
||||
- [ ] 1024px (tablet landscape / laptop)
|
||||
- [ ] 1280px+ (desktop)
|
||||
- [ ] Test with actual content (not lorem ipsum)
|
||||
- [ ] Test with long/short content variations
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
name: test-coverage-improver
|
||||
description: Analyze test coverage gaps and generate tests to improve coverage. Use when improving test coverage, finding untested code, or writing missing tests.
|
||||
---
|
||||
|
||||
# Test Coverage Improver
|
||||
|
||||
## Instructions
|
||||
|
||||
When improving test coverage:
|
||||
|
||||
1. **Run coverage report** to identify gaps
|
||||
2. **Prioritize** critical/complex code paths
|
||||
3. **Write tests** for uncovered code
|
||||
4. **Verify coverage improved**
|
||||
|
||||
## Generate Coverage Report
|
||||
|
||||
```bash
|
||||
# Jest
|
||||
npx jest --coverage
|
||||
|
||||
# Vitest
|
||||
npx vitest --coverage
|
||||
|
||||
# NYC (Istanbul) for any test runner
|
||||
npx nyc npm test
|
||||
|
||||
# View HTML report
|
||||
open coverage/lcov-report/index.html
|
||||
```
|
||||
|
||||
## Coverage Targets
|
||||
|
||||
| Type | Minimum | Good | Excellent |
|
||||
|------|---------|------|-----------|
|
||||
| Lines | 70% | 80% | 90%+ |
|
||||
| Branches | 60% | 75% | 85%+ |
|
||||
| Functions | 70% | 80% | 90%+ |
|
||||
| Statements | 70% | 80% | 90%+ |
|
||||
|
||||
## Test Templates
|
||||
|
||||
### Unit Test (Jest/Vitest)
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { calculateTotal, formatCurrency } from './utils';
|
||||
|
||||
describe('calculateTotal', () => {
|
||||
it('should sum all item prices', () => {
|
||||
const items = [
|
||||
{ price: 10, quantity: 2 },
|
||||
{ price: 5, quantity: 1 },
|
||||
];
|
||||
expect(calculateTotal(items)).toBe(25);
|
||||
});
|
||||
|
||||
it('should return 0 for empty array', () => {
|
||||
expect(calculateTotal([])).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle decimal prices', () => {
|
||||
const items = [{ price: 10.99, quantity: 1 }];
|
||||
expect(calculateTotal(items)).toBeCloseTo(10.99);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Async Functions
|
||||
|
||||
```typescript
|
||||
describe('fetchUser', () => {
|
||||
it('should return user data', async () => {
|
||||
const user = await fetchUser(1);
|
||||
expect(user).toEqual({
|
||||
id: 1,
|
||||
name: expect.any(String),
|
||||
email: expect.stringContaining('@'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw for non-existent user', async () => {
|
||||
await expect(fetchUser(999)).rejects.toThrow('User not found');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking Dependencies
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest';
|
||||
import { sendEmail } from './email';
|
||||
import { createUser } from './user';
|
||||
|
||||
vi.mock('./email', () => ({
|
||||
sendEmail: vi.fn().mockResolvedValue({ success: true }),
|
||||
}));
|
||||
|
||||
describe('createUser', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should send welcome email after creating user', async () => {
|
||||
await createUser({ name: 'John', email: 'john@test.com' });
|
||||
|
||||
expect(sendEmail).toHaveBeenCalledWith({
|
||||
to: 'john@test.com',
|
||||
template: 'welcome',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### React Component Testing
|
||||
|
||||
```tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Button } from './Button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('should render children', () => {
|
||||
render(<Button>Click me</Button>);
|
||||
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClick when clicked', async () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<Button onClick={handleClick}>Click</Button>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should be disabled when loading', () => {
|
||||
render(<Button isLoading>Submit</Button>);
|
||||
expect(screen.getByRole('button')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### API Route Testing
|
||||
|
||||
```typescript
|
||||
import { createMocks } from 'node-mocks-http';
|
||||
import handler from './api/users';
|
||||
|
||||
describe('GET /api/users', () => {
|
||||
it('should return users list', async () => {
|
||||
const { req, res } = createMocks({ method: 'GET' });
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res._getStatusCode()).toBe(200);
|
||||
expect(JSON.parse(res._getData())).toHaveProperty('users');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Branch Coverage Checklist
|
||||
|
||||
Ensure tests cover:
|
||||
|
||||
- [ ] If/else branches
|
||||
- [ ] Ternary operators
|
||||
- [ ] Switch cases (including default)
|
||||
- [ ] Try/catch blocks
|
||||
- [ ] Early returns
|
||||
- [ ] Nullish coalescing (`??`)
|
||||
- [ ] Optional chaining results (`?.`)
|
||||
- [ ] Loop conditions (0, 1, many iterations)
|
||||
|
||||
## Coverage Configuration
|
||||
|
||||
```javascript
|
||||
// vitest.config.ts
|
||||
export default {
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'html', 'lcov'],
|
||||
exclude: [
|
||||
'node_modules/',
|
||||
'**/*.d.ts',
|
||||
'**/*.test.ts',
|
||||
'**/types/',
|
||||
],
|
||||
thresholds: {
|
||||
lines: 80,
|
||||
branches: 75,
|
||||
functions: 80,
|
||||
statements: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Priority Order for Testing
|
||||
|
||||
1. **Critical paths**: Auth, payments, data mutations
|
||||
2. **Complex logic**: Algorithms, state machines, calculations
|
||||
3. **Error handlers**: Catch blocks, error boundaries
|
||||
4. **Edge cases**: Empty arrays, null values, boundaries
|
||||
5. **Integration points**: API calls, database queries
|
||||
Reference in New Issue
Block a user