ai-devx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/bin/cli.js +65 -0
- package/package.json +63 -0
- package/src/commands/init.js +86 -0
- package/src/commands/status.js +60 -0
- package/src/commands/update.js +77 -0
- package/src/config.js +72 -0
- package/src/utils/fileSystem.js +64 -0
- package/src/utils/logger.js +18 -0
- package/templates/.agent/.gitignore +6 -0
- package/templates/.agent/agents/backend-specialist.md +147 -0
- package/templates/.agent/agents/database-architect.md +164 -0
- package/templates/.agent/agents/debugger.md +128 -0
- package/templates/.agent/agents/devops-engineer.md +185 -0
- package/templates/.agent/agents/frontend-specialist.md +122 -0
- package/templates/.agent/agents/orchestrator.md +137 -0
- package/templates/.agent/agents/project-planner.md +127 -0
- package/templates/.agent/agents/security-auditor.md +122 -0
- package/templates/.agent/agents/test-engineer.md +176 -0
- package/templates/.agent/scripts/checklist.js +260 -0
- package/templates/.agent/scripts/security_scan.js +251 -0
- package/templates/.agent/skills/api-patterns/SKILL.md +236 -0
- package/templates/.agent/skills/database-design/SKILL.md +303 -0
- package/templates/.agent/skills/docker-expert/SKILL.md +286 -0
- package/templates/.agent/skills/react-best-practices/SKILL.md +246 -0
- package/templates/.agent/skills/testing-patterns/SKILL.md +262 -0
- package/templates/.agent/workflows/create.md +131 -0
- package/templates/.agent/workflows/debug.md +138 -0
- package/templates/.agent/workflows/deploy.md +163 -0
- package/templates/.agent/workflows/plan.md +153 -0
- package/templates/.agent/workflows/security.md +181 -0
- package/templates/.agent/workflows/test.md +165 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docker-expert
|
|
3
|
+
description: Docker containerization, multi-stage builds, and container orchestration
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
requires: []
|
|
6
|
+
related:
|
|
7
|
+
- deployment-procedures
|
|
8
|
+
- ci-cd
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Docker Expert Skill
|
|
12
|
+
|
|
13
|
+
## Dockerfile Best Practices
|
|
14
|
+
|
|
15
|
+
### Multi-Stage Build
|
|
16
|
+
```dockerfile
|
|
17
|
+
# Build stage
|
|
18
|
+
FROM node:18-alpine AS builder
|
|
19
|
+
WORKDIR /app
|
|
20
|
+
COPY package*.json ./
|
|
21
|
+
RUN npm ci
|
|
22
|
+
COPY . .
|
|
23
|
+
RUN npm run build
|
|
24
|
+
|
|
25
|
+
# Production stage
|
|
26
|
+
FROM node:18-alpine AS production
|
|
27
|
+
WORKDIR /app
|
|
28
|
+
ENV NODE_ENV=production
|
|
29
|
+
COPY package*.json ./
|
|
30
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
31
|
+
COPY --from=builder /app/dist ./dist
|
|
32
|
+
USER node
|
|
33
|
+
EXPOSE 3000
|
|
34
|
+
CMD ["node", "dist/main.js"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Optimization Tips
|
|
38
|
+
|
|
39
|
+
**Use specific tags**
|
|
40
|
+
```dockerfile
|
|
41
|
+
# ✅ Good
|
|
42
|
+
FROM node:18.19.0-alpine3.19
|
|
43
|
+
|
|
44
|
+
# ❌ Avoid
|
|
45
|
+
FROM node:latest
|
|
46
|
+
FROM node:18
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Minimize layers**
|
|
50
|
+
```dockerfile
|
|
51
|
+
# ✅ Good - Single layer
|
|
52
|
+
RUN apt-get update && apt-get install -y \
|
|
53
|
+
package1 \
|
|
54
|
+
package2 \
|
|
55
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
56
|
+
|
|
57
|
+
# ❌ Bad - Multiple layers
|
|
58
|
+
RUN apt-get update
|
|
59
|
+
RUN apt-get install -y package1
|
|
60
|
+
RUN apt-get install -y package2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Leverage build cache**
|
|
64
|
+
```dockerfile
|
|
65
|
+
# Copy dependency files first (cache if unchanged)
|
|
66
|
+
COPY package*.json ./
|
|
67
|
+
RUN npm ci
|
|
68
|
+
|
|
69
|
+
# Copy source code (invalidates cache on change)
|
|
70
|
+
COPY . .
|
|
71
|
+
RUN npm run build
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Use .dockerignore**
|
|
75
|
+
```
|
|
76
|
+
node_modules
|
|
77
|
+
npm-debug.log
|
|
78
|
+
Dockerfile
|
|
79
|
+
.dockerignore
|
|
80
|
+
.git
|
|
81
|
+
.gitignore
|
|
82
|
+
README.md
|
|
83
|
+
.env
|
|
84
|
+
.env.local
|
|
85
|
+
dist
|
|
86
|
+
build
|
|
87
|
+
coverage
|
|
88
|
+
.nyc_output
|
|
89
|
+
.vscode
|
|
90
|
+
.idea
|
|
91
|
+
*.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Docker Compose
|
|
95
|
+
|
|
96
|
+
### Web Application Stack
|
|
97
|
+
```yaml
|
|
98
|
+
version: '3.8'
|
|
99
|
+
|
|
100
|
+
services:
|
|
101
|
+
app:
|
|
102
|
+
build:
|
|
103
|
+
context: .
|
|
104
|
+
dockerfile: Dockerfile
|
|
105
|
+
ports:
|
|
106
|
+
- "3000:3000"
|
|
107
|
+
environment:
|
|
108
|
+
- NODE_ENV=production
|
|
109
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
|
|
110
|
+
- REDIS_URL=redis://redis:6379
|
|
111
|
+
depends_on:
|
|
112
|
+
- db
|
|
113
|
+
- redis
|
|
114
|
+
volumes:
|
|
115
|
+
- ./logs:/app/logs
|
|
116
|
+
restart: unless-stopped
|
|
117
|
+
healthcheck:
|
|
118
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
119
|
+
interval: 30s
|
|
120
|
+
timeout: 10s
|
|
121
|
+
retries: 3
|
|
122
|
+
|
|
123
|
+
db:
|
|
124
|
+
image: postgres:15-alpine
|
|
125
|
+
environment:
|
|
126
|
+
POSTGRES_USER: user
|
|
127
|
+
POSTGRES_PASSWORD: pass
|
|
128
|
+
POSTGRES_DB: mydb
|
|
129
|
+
volumes:
|
|
130
|
+
- postgres_data:/var/lib/postgresql/data
|
|
131
|
+
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
132
|
+
ports:
|
|
133
|
+
- "5432:5432"
|
|
134
|
+
restart: unless-stopped
|
|
135
|
+
|
|
136
|
+
redis:
|
|
137
|
+
image: redis:7-alpine
|
|
138
|
+
volumes:
|
|
139
|
+
- redis_data:/data
|
|
140
|
+
restart: unless-stopped
|
|
141
|
+
|
|
142
|
+
nginx:
|
|
143
|
+
image: nginx:alpine
|
|
144
|
+
ports:
|
|
145
|
+
- "80:80"
|
|
146
|
+
- "443:443"
|
|
147
|
+
volumes:
|
|
148
|
+
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
|
149
|
+
- ./ssl:/etc/nginx/ssl:ro
|
|
150
|
+
depends_on:
|
|
151
|
+
- app
|
|
152
|
+
restart: unless-stopped
|
|
153
|
+
|
|
154
|
+
volumes:
|
|
155
|
+
postgres_data:
|
|
156
|
+
redis_data:
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Development vs Production
|
|
160
|
+
|
|
161
|
+
**docker-compose.yml** (Production)
|
|
162
|
+
```yaml
|
|
163
|
+
version: '3.8'
|
|
164
|
+
services:
|
|
165
|
+
app:
|
|
166
|
+
build: .
|
|
167
|
+
environment:
|
|
168
|
+
- NODE_ENV=production
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**docker-compose.dev.yml** (Development)
|
|
172
|
+
```yaml
|
|
173
|
+
version: '3.8'
|
|
174
|
+
services:
|
|
175
|
+
app:
|
|
176
|
+
build:
|
|
177
|
+
context: .
|
|
178
|
+
target: development
|
|
179
|
+
volumes:
|
|
180
|
+
- .:/app
|
|
181
|
+
- /app/node_modules
|
|
182
|
+
environment:
|
|
183
|
+
- NODE_ENV=development
|
|
184
|
+
command: npm run dev
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Usage:
|
|
188
|
+
```bash
|
|
189
|
+
# Production
|
|
190
|
+
docker-compose up -d
|
|
191
|
+
|
|
192
|
+
# Development
|
|
193
|
+
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Container Security
|
|
197
|
+
|
|
198
|
+
### Non-root User
|
|
199
|
+
```dockerfile
|
|
200
|
+
FROM node:18-alpine
|
|
201
|
+
|
|
202
|
+
# Create app user
|
|
203
|
+
RUN addgroup -g 1001 -S nodejs
|
|
204
|
+
RUN adduser -S nodejs -u 1001
|
|
205
|
+
|
|
206
|
+
WORKDIR /app
|
|
207
|
+
COPY --chown=nodejs:nodejs . .
|
|
208
|
+
RUN npm ci --only=production
|
|
209
|
+
|
|
210
|
+
USER nodejs
|
|
211
|
+
EXPOSE 3000
|
|
212
|
+
CMD ["node", "server.js"]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Read-only Filesystem
|
|
216
|
+
```yaml
|
|
217
|
+
services:
|
|
218
|
+
app:
|
|
219
|
+
read_only: true
|
|
220
|
+
tmpfs:
|
|
221
|
+
- /tmp
|
|
222
|
+
volumes:
|
|
223
|
+
- ./logs:/app/logs
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Scan for Vulnerabilities
|
|
227
|
+
```bash
|
|
228
|
+
# Using Trivy
|
|
229
|
+
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
|
230
|
+
aquasec/trivy image myapp:latest
|
|
231
|
+
|
|
232
|
+
# Using Docker Scout
|
|
233
|
+
docker scout cves myapp:latest
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Common Commands
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Build image
|
|
240
|
+
docker build -t myapp:latest .
|
|
241
|
+
|
|
242
|
+
# Run container
|
|
243
|
+
docker run -d -p 3000:3000 --name myapp myapp:latest
|
|
244
|
+
|
|
245
|
+
# View logs
|
|
246
|
+
docker logs -f myapp
|
|
247
|
+
|
|
248
|
+
# Execute command in container
|
|
249
|
+
docker exec -it myapp sh
|
|
250
|
+
|
|
251
|
+
# Remove stopped containers
|
|
252
|
+
docker container prune
|
|
253
|
+
|
|
254
|
+
# Remove unused images
|
|
255
|
+
docker image prune
|
|
256
|
+
|
|
257
|
+
# Clean everything
|
|
258
|
+
docker system prune -a
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Health Checks
|
|
262
|
+
|
|
263
|
+
```dockerfile
|
|
264
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
265
|
+
CMD curl -f http://localhost:3000/health || exit 1
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Environment Variables
|
|
269
|
+
|
|
270
|
+
```dockerfile
|
|
271
|
+
# Set at build time
|
|
272
|
+
ARG NODE_VERSION=18
|
|
273
|
+
FROM node:${NODE_VERSION}-alpine
|
|
274
|
+
|
|
275
|
+
# Set at runtime
|
|
276
|
+
ENV NODE_ENV=production
|
|
277
|
+
ENV PORT=3000
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Pass at build
|
|
282
|
+
docker build --build-arg NODE_VERSION=20 -t myapp .
|
|
283
|
+
|
|
284
|
+
# Pass at runtime
|
|
285
|
+
docker run -e NODE_ENV=production -e PORT=3000 myapp
|
|
286
|
+
```
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-best-practices
|
|
3
|
+
description: Modern React development patterns, hooks, and performance optimization
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
requires: []
|
|
6
|
+
related:
|
|
7
|
+
- frontend-design
|
|
8
|
+
- testing-patterns
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# React Best Practices Skill
|
|
12
|
+
|
|
13
|
+
## Core Principles
|
|
14
|
+
|
|
15
|
+
### 1. Functional Components
|
|
16
|
+
Always use functional components with hooks instead of class components.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// ✅ Good
|
|
20
|
+
function UserCard({ user }: UserCardProps) {
|
|
21
|
+
return <div>{user.name}</div>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ❌ Avoid
|
|
25
|
+
class UserCard extends React.Component {
|
|
26
|
+
render() {
|
|
27
|
+
return <div>{this.props.user.name}</div>;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Hooks Rules
|
|
33
|
+
- Only call hooks at the top level
|
|
34
|
+
- Only call hooks from React functions
|
|
35
|
+
- Use eslint-plugin-react-hooks
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// ✅ Good
|
|
39
|
+
function Component() {
|
|
40
|
+
const [state, setState] = useState(0);
|
|
41
|
+
useEffect(() => {}, []);
|
|
42
|
+
return <div />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ❌ Bad - Hook inside condition
|
|
46
|
+
function Component() {
|
|
47
|
+
if (condition) {
|
|
48
|
+
useEffect(() => {}, []);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Custom Hooks
|
|
54
|
+
Extract reusable logic into custom hooks.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// useUser.ts
|
|
58
|
+
export function useUser(userId: string) {
|
|
59
|
+
const [user, setUser] = useState<User | null>(null);
|
|
60
|
+
const [loading, setLoading] = useState(true);
|
|
61
|
+
const [error, setError] = useState<Error | null>(null);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
fetchUser(userId)
|
|
65
|
+
.then(setUser)
|
|
66
|
+
.catch(setError)
|
|
67
|
+
.finally(() => setLoading(false));
|
|
68
|
+
}, [userId]);
|
|
69
|
+
|
|
70
|
+
return { user, loading, error };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Usage
|
|
74
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
75
|
+
const { user, loading, error } = useUser(userId);
|
|
76
|
+
|
|
77
|
+
if (loading) return <Spinner />;
|
|
78
|
+
if (error) return <Error message={error.message} />;
|
|
79
|
+
return <div>{user?.name}</div>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## State Management
|
|
84
|
+
|
|
85
|
+
### useState
|
|
86
|
+
- Use for simple, local state
|
|
87
|
+
- Prefer multiple state variables over single object
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// ✅ Good
|
|
91
|
+
const [name, setName] = useState('');
|
|
92
|
+
const [email, setEmail] = useState('');
|
|
93
|
+
|
|
94
|
+
// ❌ Avoid unless related
|
|
95
|
+
const [form, setForm] = useState({ name: '', email: '' });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### useReducer
|
|
99
|
+
- Use for complex state logic
|
|
100
|
+
- Multiple related state transitions
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
interface State {
|
|
104
|
+
loading: boolean;
|
|
105
|
+
data: Data | null;
|
|
106
|
+
error: Error | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Action =
|
|
110
|
+
| { type: 'FETCH_START' }
|
|
111
|
+
| { type: 'FETCH_SUCCESS'; payload: Data }
|
|
112
|
+
| { type: 'FETCH_ERROR'; payload: Error };
|
|
113
|
+
|
|
114
|
+
function reducer(state: State, action: Action): State {
|
|
115
|
+
switch (action.type) {
|
|
116
|
+
case 'FETCH_START':
|
|
117
|
+
return { ...state, loading: true, error: null };
|
|
118
|
+
case 'FETCH_SUCCESS':
|
|
119
|
+
return { ...state, loading: false, data: action.payload };
|
|
120
|
+
case 'FETCH_ERROR':
|
|
121
|
+
return { ...state, loading: false, error: action.payload };
|
|
122
|
+
default:
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Context API
|
|
129
|
+
- Use for global state (theme, auth, user)
|
|
130
|
+
- Avoid prop drilling
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// ThemeContext.tsx
|
|
134
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
135
|
+
|
|
136
|
+
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
137
|
+
const [theme, setTheme] = useState('light');
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
141
|
+
{children}
|
|
142
|
+
</ThemeContext.Provider>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const useTheme = () => {
|
|
147
|
+
const context = useContext(ThemeContext);
|
|
148
|
+
if (!context) throw new Error('useTheme must be used within ThemeProvider');
|
|
149
|
+
return context;
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Performance Optimization
|
|
154
|
+
|
|
155
|
+
### 1. React.memo
|
|
156
|
+
Prevent unnecessary re-renders for pure components.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
|
|
160
|
+
// Only re-renders if data changes
|
|
161
|
+
return <div>{/* expensive rendering */}</div>;
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2. useMemo
|
|
166
|
+
Cache expensive calculations.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
const sortedData = useMemo(() => {
|
|
170
|
+
return data.sort((a, b) => a.value - b.value);
|
|
171
|
+
}, [data]);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. useCallback
|
|
175
|
+
Cache function references.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const handleClick = useCallback(() => {
|
|
179
|
+
doSomething(id);
|
|
180
|
+
}, [id]);
|
|
181
|
+
|
|
182
|
+
// Now Child won't re-render unnecessarily
|
|
183
|
+
<Child onClick={handleClick} />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 4. Code Splitting
|
|
187
|
+
```tsx
|
|
188
|
+
const LazyComponent = lazy(() => import('./HeavyComponent'));
|
|
189
|
+
|
|
190
|
+
function App() {
|
|
191
|
+
return (
|
|
192
|
+
<Suspense fallback={<Spinner />}>
|
|
193
|
+
<LazyComponent />
|
|
194
|
+
</Suspense>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Component Patterns
|
|
200
|
+
|
|
201
|
+
### Compound Components
|
|
202
|
+
```tsx
|
|
203
|
+
function Select({ children }: { children: ReactNode }) {
|
|
204
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
205
|
+
return <SelectContext.Provider value={{ selected, setSelected }}>{children}</SelectContext.Provider>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Select.Option = function Option({ value, children }: OptionProps) {
|
|
209
|
+
const { selected, setSelected } = useSelectContext();
|
|
210
|
+
return <div onClick={() => setSelected(value)}>{children}</div>;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Usage
|
|
214
|
+
<Select>
|
|
215
|
+
<Select.Option value="1">Option 1</Select.Option>
|
|
216
|
+
<Select.Option value="2">Option 2</Select.Option>
|
|
217
|
+
</Select>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## File Organization
|
|
221
|
+
```
|
|
222
|
+
src/
|
|
223
|
+
├── components/ # Reusable UI components
|
|
224
|
+
│ ├── Button/
|
|
225
|
+
│ │ ├── Button.tsx
|
|
226
|
+
│ │ ├── Button.test.tsx
|
|
227
|
+
│ │ └── index.ts
|
|
228
|
+
│ └── Card/
|
|
229
|
+
├── hooks/ # Custom hooks
|
|
230
|
+
│ ├── useAuth.ts
|
|
231
|
+
│ ├── useLocalStorage.ts
|
|
232
|
+
│ └── index.ts
|
|
233
|
+
├── contexts/ # React contexts
|
|
234
|
+
│ ├── AuthContext.tsx
|
|
235
|
+
│ └── ThemeContext.tsx
|
|
236
|
+
├── lib/ # Utilities
|
|
237
|
+
│ ├── api.ts
|
|
238
|
+
│ └── utils.ts
|
|
239
|
+
├── types/ # TypeScript types
|
|
240
|
+
│ └── index.ts
|
|
241
|
+
└── pages/ # Page components
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Related Skills
|
|
245
|
+
- After coding: `frontend-design` for styling
|
|
246
|
+
- Before deployment: `testing-patterns` for tests
|